maint: format code using black
[git-cola.git] / cola / widgets / stash.py
blobad451ddf24ebd2d6020c7f6960d64f5708e62124
1 """Widgets for manipulating git stashes"""
2 from __future__ import division, absolute_import, unicode_literals
4 from qtpy import QtCore
5 from qtpy.QtCore import Qt
7 from ..i18n import N_
8 from ..interaction import Interaction
9 from ..models import stash
10 from ..qtutils import get
11 from .. import cmds
12 from .. import hotkeys
13 from .. import icons
14 from .. import qtutils
15 from .. import utils
16 from . import defs
17 from . import diff
18 from . import standard
21 def view(context, show=True):
22 """Launches a stash dialog using the provided model + view"""
23 model = stash.StashModel(context)
24 stash_view = StashView(context, model, parent=qtutils.active_window())
25 if show:
26 stash_view.show()
27 stash_view.raise_()
28 return stash_view
31 class StashView(standard.Dialog):
32 def __init__(self, context, model, parent=None):
33 standard.Dialog.__init__(self, parent=parent)
34 self.context = context
35 self.model = model
36 self.stashes = []
37 self.revids = []
38 self.names = []
40 self.setWindowTitle(N_('Stash'))
41 if parent is not None:
42 self.setWindowModality(Qt.WindowModal)
44 self.stash_list = standard.ListWidget(parent=self)
45 self.stash_text = diff.DiffTextEdit(context, self)
47 self.button_apply = qtutils.create_button(
48 text=N_('Apply'), tooltip=N_('Apply the selected stash'), icon=icons.ok()
51 self.button_save = qtutils.create_button(
52 text=N_('Save'),
53 tooltip=N_('Save modified state to new stash'),
54 icon=icons.save(),
55 default=True,
58 self.button_drop = qtutils.create_button(
59 text=N_('Drop'), tooltip=N_('Drop the selected stash'), icon=icons.discard()
62 self.button_pop = qtutils.create_button(
63 text=N_('Pop'),
64 tooltip=N_('Apply and drop the selected stash (git stash pop)'),
65 icon=icons.discard(),
68 self.button_close = qtutils.close_button()
70 self.keep_index = qtutils.checkbox(
71 text=N_('Keep Index'),
72 checked=True,
73 tooltip=N_('Stash unstaged changes only, keeping staged changes'),
76 self.stash_index = qtutils.checkbox(
77 text=N_('Stash Index'), tooltip=N_('Stash staged changes only')
80 # Arrange layouts
81 self.splitter = qtutils.splitter(
82 Qt.Horizontal, self.stash_list, self.stash_text
84 self.splitter.setChildrenCollapsible(False)
86 self.btn_layt = qtutils.hbox(
87 defs.no_margin,
88 defs.button_spacing,
89 self.button_close,
90 qtutils.STRETCH,
91 self.stash_index,
92 self.keep_index,
93 self.button_save,
94 self.button_apply,
95 self.button_pop,
96 self.button_drop,
99 self.main_layt = qtutils.vbox(
100 defs.margin, defs.spacing, self.splitter, self.btn_layt
102 self.setLayout(self.main_layt)
103 self.splitter.setSizes([self.width() // 3, self.width() * 2 // 3])
105 # Apply stash with Ctrl+Enter
106 self.apply_action = qtutils.add_action(
107 self, N_('Apply'), self.stash_apply, hotkeys.APPLY
109 # Pop stash with Ctrl+Backspace
110 self.pop_action = qtutils.add_action(
111 self, N_('Pop'), self.stash_pop, hotkeys.DELETE_FILE_SECONDARY
113 # Drop stash with Ctrl+Shift+Backspace
114 self.drop_action = qtutils.add_action(
115 self, N_('Pop'), self.stash_drop, hotkeys.DELETE_FILE
118 # pylint: disable=no-member
119 self.stash_list.itemSelectionChanged.connect(self.item_selected)
121 qtutils.connect_button(self.button_save, self.stash_save)
122 qtutils.connect_button(self.button_apply, self.stash_apply)
123 qtutils.connect_button(self.button_pop, self.stash_pop)
124 qtutils.connect_button(self.button_drop, self.stash_drop)
125 qtutils.connect_button(self.button_close, self.close_and_rescan)
127 qtutils.connect_checkbox(self.stash_index, self.stash_index_clicked)
128 qtutils.connect_checkbox(self.keep_index, self.keep_index_clicked)
130 self.init_size(parent=parent)
132 self.update_from_model()
133 self.update_actions()
135 def close_and_rescan(self):
136 cmds.do(cmds.Rescan, self.context)
137 self.reject()
139 # "stash" and "keep" index are mutually disable, but we don't
140 # want a radio button because we'd have to add a 3rd "default" option.
141 def stash_index_clicked(self, clicked):
142 if clicked:
143 self.keep_index.setChecked(False)
145 def keep_index_clicked(self, clicked):
146 if clicked:
147 self.stash_index.setChecked(False)
149 def selected_stash(self):
150 """Returns the stash name of the currently selected stash"""
151 list_widget = self.stash_list
152 stash_list = self.revids
153 return qtutils.selected_item(list_widget, stash_list)
155 def selected_name(self):
156 list_widget = self.stash_list
157 stash_list = self.names
158 return qtutils.selected_item(list_widget, stash_list)
160 def item_selected(self):
161 """Shows the current stash in the main view."""
162 self.update_actions()
163 selection = self.selected_stash()
164 if not selection:
165 return
166 diff_text = self.model.stash_diff(selection)
167 self.stash_text.setPlainText(diff_text)
169 def update_actions(self):
170 is_staged = self.model.is_staged()
171 self.stash_index.setEnabled(is_staged)
173 is_changed = self.model.is_changed()
174 self.keep_index.setEnabled(is_changed)
175 self.button_save.setEnabled(is_changed)
177 is_selected = bool(self.selected_stash())
178 self.apply_action.setEnabled(is_selected)
179 self.drop_action.setEnabled(is_selected)
180 self.pop_action.setEnabled(is_selected)
181 self.button_apply.setEnabled(is_selected)
182 self.button_drop.setEnabled(is_selected)
183 self.button_pop.setEnabled(is_selected)
185 def update_from_model(self):
186 """Initiates git queries on the model and updates the view"""
187 stashes, revids, author_dates, names = self.model.stash_info()
188 self.stashes = stashes
189 self.revids = revids
190 self.names = names
192 self.stash_list.clear()
193 self.stash_list.addItems(self.stashes)
194 if self.stash_list.count() > 0:
195 for i in range(self.stash_list.count()):
196 self.stash_list.item(i).setToolTip(author_dates[i])
197 item = self.stash_list.item(0)
198 self.stash_list.setCurrentItem(item)
200 # "Stash Index" depends on staged changes, so disable this option
201 # if there are no staged changes.
202 is_staged = self.model.is_staged()
203 if get(self.stash_index) and not is_staged:
204 self.stash_index.setChecked(False)
206 def stash_pop(self):
207 self.stash_apply(pop=True)
209 def stash_apply(self, pop=False):
210 """Applies the currently selected stash"""
211 selection = self.selected_stash()
212 if not selection:
213 return
214 context = self.context
215 index = get(self.keep_index)
216 cmds.do(stash.ApplyStash, context, selection, index, pop)
217 QtCore.QTimer.singleShot(1, self.accept)
219 def stash_save(self):
220 """Saves the worktree in a stash
222 This prompts the user for a stash name and creates
223 a git stash named accordingly.
226 stash_name, ok = qtutils.prompt(
227 N_('Enter a name for the stash'), title=N_('Save Stash'), parent=self
229 if not ok or not stash_name:
230 return
231 # Sanitize the stash name
232 stash_name = utils.sanitize(stash_name)
233 if stash_name in self.names:
234 Interaction.critical(
235 N_('Error: Stash exists'),
236 N_('A stash named "%s" already exists') % stash_name,
238 return
239 context = self.context
240 keep_index = get(self.keep_index)
241 stash_index = get(self.stash_index)
242 if stash_index:
243 cmds.do(stash.StashIndex, context, stash_name)
244 else:
245 cmds.do(stash.SaveStash, context, stash_name, keep_index)
246 QtCore.QTimer.singleShot(1, self.accept)
248 def stash_drop(self):
249 """Drops the currently selected stash"""
250 selection = self.selected_stash()
251 name = self.selected_name()
252 if not selection:
253 return
254 if not Interaction.confirm(
255 N_('Drop Stash?'),
256 N_('Recovering a dropped stash is not possible.'),
257 N_('Drop the "%s" stash?') % name,
258 N_('Drop Stash'),
259 default=True,
260 icon=icons.discard(),
262 return
263 cmds.do(stash.DropStash, self.context, selection)
264 self.update_from_model()
265 self.stash_text.setPlainText('')
267 def export_state(self):
268 """Export persistent settings"""
269 state = super(StashView, self).export_state()
270 state['keep_index'] = get(self.keep_index)
271 state['stash_index'] = get(self.stash_index)
272 state['sizes'] = get(self.splitter)
273 return state
275 def apply_state(self, state):
276 """Apply persistent settings"""
277 result = super(StashView, self).apply_state(state)
278 keep_index = bool(state.get('keep_index', True))
279 stash_index = bool(state.get('stash_index', False))
280 self.keep_index.setChecked(keep_index)
281 self.stash_index.setChecked(stash_index)
282 try:
283 self.splitter.setSizes(state['sizes'])
284 except (KeyError, ValueError, AttributeError):
285 pass
286 return result