1
+ from contextlib import contextmanager
2
+ from functools import partial
1
3
import os
4
+ import threading
2
5
6
+ import sublime
3
7
from sublime_plugin import WindowCommand
4
8
9
+ from .status import distinct_until_state_changed
5
10
from ...common import ui , util
6
11
from ..commands import GsNavigate
7
12
from ..commands .log import LogMixin
8
13
from ..git_command import GitCommand
9
14
from ..ui_mixins .quick_panel import show_remote_panel , show_branch_panel
10
15
from ..ui_mixins .input_panel import show_single_line_input_panel
16
+ from GitSavvy .core import store
11
17
from GitSavvy .core .fns import filter_
12
18
from GitSavvy .core .utils import flash
13
19
from GitSavvy .core .runtime import on_worker
39
45
40
46
MYPY = False
41
47
if MYPY :
42
- from typing import List , Optional
43
- from GitSavvy .core .git_mixins .branches import Branch
48
+ from typing import Dict , List , Optional , TypedDict
49
+ from ..git_mixins .active_branch import Commit
50
+ from ..git_mixins .branches import Branch
51
+
52
+ BranchViewState = TypedDict (
53
+ "BranchViewState" ,
54
+ {
55
+ "git_root" : str ,
56
+ "long_status" : str ,
57
+ "branches" : List [Branch ],
58
+ "descriptions" : Dict [str , str ],
59
+ "remotes" : Dict [str , str ],
60
+ "recent_commits" : List [Commit ],
61
+ "show_remotes" : bool ,
62
+ "show_help" : bool ,
63
+ },
64
+ total = False
65
+ )
44
66
45
67
46
68
class gs_show_branch (WindowCommand , GitCommand ):
@@ -62,8 +84,6 @@ class BranchInterface(ui.Interface, GitCommand):
62
84
interface_type = "branch"
63
85
syntax_file = "Packages/GitSavvy/syntax/branch.sublime-syntax"
64
86
65
- show_remotes = None
66
-
67
87
template = """\
68
88
69
89
ROOT: {git_root}
@@ -105,18 +125,80 @@ class BranchInterface(ui.Interface, GitCommand):
105
125
REMOTE ({remote_name}):
106
126
{remote_branch_list}"""
107
127
128
+ def __init__ (self , * args , ** kwargs ):
129
+ self ._lock = threading .Lock ()
130
+ self .state = {
131
+ 'git_root' : '' ,
132
+ 'long_status' : '' ,
133
+ 'recent_commits' : [],
134
+ 'branches' : [],
135
+ 'descriptions' : {},
136
+ 'remotes' : {},
137
+ 'show_remotes' : self .savvy_settings .get ("show_remotes_in_branch_dashboard" ),
138
+ 'show_help' : True ,
139
+ } # type: BranchViewState
140
+ super ().__init__ (* args , ** kwargs )
141
+
108
142
def title (self ):
109
143
return "BRANCHES: {}" .format (os .path .basename (self .repo_path ))
110
144
111
- def pre_render (self ):
145
+ def refresh_view_state (self ):
112
146
sort_by_recent = self .savvy_settings .get ("sort_by_recent_in_branch_dashboard" )
113
- self ._branches = self .get_branches (sort_by_recent = sort_by_recent )
114
- self .descriptions = self .fetch_branch_description_subjects ()
115
- if self .show_remotes is None :
116
- self .show_remotes = self .savvy_settings .get ("show_remotes_in_branch_dashboard" )
117
- self .remotes = self .get_remotes () if self .show_remotes else {}
147
+ for thunk in (
148
+ lambda : {'recent_commits' : self .get_latest_commits ()},
149
+ lambda : {
150
+ 'branches' : self .get_branches (sort_by_recent = sort_by_recent ),
151
+ 'descriptions' : self .fetch_branch_description_subjects (),
152
+ },
153
+ lambda : {'remotes' : self .get_remotes ()},
154
+ ):
155
+ sublime .set_timeout_async (
156
+ partial (self .update_state , thunk , then = self .just_render )
157
+ )
158
+
159
+ self .view .run_command ("gs_update_status" )
160
+ # These are cheap to compute, so we just do it!
161
+ state = store .current_state (self .repo_path )
162
+ status = state .get ("status" )
163
+ if status :
164
+ self .update_state (status ._asdict ())
165
+ self .update_state ({
166
+ 'git_root' : self .short_repo_path ,
167
+ 'branches' : state .get ("branches" , []),
168
+ 'recent_commits' : state .get ("recent_commits" , []),
169
+ 'show_help' : not self .view .settings ().get ("git_savvy.help_hidden" ),
170
+ })
171
+
172
+ def update_state (self , data , then = None ):
173
+ """Update internal view state and maybe invoke a callback.
174
+
175
+ `data` can be a mapping or a callable ("thunk") which returns
176
+ a mapping.
177
+
178
+ Note: We invoke the "sink" without any arguments. TBC.
179
+ """
180
+ if callable (data ):
181
+ data = data ()
182
+
183
+ with self ._lock :
184
+ self .state .update (data )
185
+
186
+ if callable (then ):
187
+ then ()
118
188
119
189
def render (self ):
190
+ """Refresh view state and render."""
191
+ self .refresh_view_state ()
192
+ self .just_render ()
193
+
194
+ @distinct_until_state_changed
195
+ def just_render (self ):
196
+ content , regions = self ._render_template ()
197
+ with self .keep_cursor_on_something ():
198
+ self .draw (self .title (), content , regions )
199
+
200
+ @contextmanager
201
+ def keep_cursor_on_something (self ):
120
202
def cursor_is_on_active_branch ():
121
203
sel = self .view .sel ()
122
204
return (
@@ -128,29 +210,50 @@ def cursor_is_on_active_branch():
128
210
)
129
211
130
212
cursor_was_on_active_branch = cursor_is_on_active_branch ()
131
- super (). render ()
213
+ yield
132
214
if cursor_was_on_active_branch and not cursor_is_on_active_branch ():
133
215
self .view .run_command ("gs_branches_navigate_to_active_branch" )
134
216
217
+ def on_status_update (self , _repo_path , state ):
218
+ try :
219
+ new_state = state ["status" ]._asdict ()
220
+ except KeyError :
221
+ new_state = {}
222
+ new_state ["branches" ] = state .get ("branches" )
223
+ new_state ["recent_commits" ] = state .get ("recent_commits" )
224
+ self .update_state (new_state , then = self .just_render )
225
+
226
+ def on_create (self ):
227
+ self ._unsubscribe = store .subscribe (
228
+ self .repo_path , {"status" , "branches" , "recent_commits" }, self .on_status_update
229
+ )
230
+
231
+ def on_close (self ):
232
+ self ._unsubscribe ()
233
+
135
234
def on_new_dashboard (self ):
136
235
self .view .run_command ("gs_branches_navigate_to_active_branch" )
137
236
138
237
@ui .section ("branch_status" )
139
238
def render_branch_status (self ):
140
- return self .get_working_dir_status (). long_status
239
+ return self .state [ ' long_status' ]
141
240
142
241
@ui .section ("git_root" )
143
242
def render_git_root (self ):
144
- return self .short_repo_path
243
+ return self .state [ 'git_root' ]
145
244
146
245
@ui .section ("head" )
147
246
def render_head (self ):
148
- return self .get_latest_commit_msg_for_head ()
247
+ recent_commits = self .state ['recent_commits' ]
248
+ if not recent_commits :
249
+ return "No commits yet."
250
+
251
+ return "{0.hash} {0.message}" .format (recent_commits [0 ])
149
252
150
253
@ui .section ("branch_list" )
151
254
def render_branch_list (self ):
152
255
# type: () -> str
153
- branches = [branch for branch in self ._branches if not branch .is_remote ]
256
+ branches = [branch for branch in self .state [ "branches" ] if not branch .is_remote ]
154
257
return self ._render_branch_list (None , branches )
155
258
156
259
def _render_branch_list (self , remote_name , branches ):
@@ -161,7 +264,7 @@ def _render_branch_list(self, remote_name, branches):
161
264
indicator = "▸" if branch .active else " " ,
162
265
hash = branch .commit_hash ,
163
266
name = branch .canonical_name [remote_name_l :],
164
- description = (" " + self .descriptions .get (branch .canonical_name , "" )).rstrip (),
267
+ description = (" " + self .state [ " descriptions" ] .get (branch .canonical_name , "" )).rstrip (),
165
268
tracking = (" ({branch}{status})" .format (
166
269
branch = branch .upstream .canonical_name ,
167
270
status = ", " + branch .upstream .status if branch .upstream .status else ""
@@ -172,26 +275,25 @@ def _render_branch_list(self, remote_name, branches):
172
275
@ui .section ("remotes" )
173
276
def render_remotes (self ):
174
277
return (self .render_remotes_on ()
175
- if self .show_remotes else
278
+ if self .state [ " show_remotes" ] else
176
279
self .render_remotes_off ())
177
280
178
281
@ui .section ("help" )
179
282
def render_help (self ):
180
- help_hidden = self .view . settings (). get ( "git_savvy.help_hidden" )
181
- if help_hidden :
283
+ show_help = self .state [ 'show_help' ]
284
+ if not show_help :
182
285
return ""
183
- else :
184
- return self .template_help
286
+ return self .template_help
185
287
186
288
def render_remotes_off (self ):
187
289
return "\n \n ** Press [e] to toggle display of remote branches. **\n "
188
290
189
291
def render_remotes_on (self ):
190
292
output_tmpl = "\n "
191
293
render_fns = []
192
- remote_branches = [b for b in self ._branches if b .is_remote ]
294
+ remote_branches = [b for b in self .state [ "branches" ] if b .is_remote ]
193
295
194
- for remote_name in self .remotes :
296
+ for remote_name in self .state [ " remotes" ] :
195
297
key = "branch_list_" + remote_name
196
298
output_tmpl += "{" + key + "}\n "
197
299
branches = [b for b in remote_branches if b .canonical_name .startswith (remote_name + "/" )]
@@ -228,7 +330,7 @@ def get_selected_branches(self, ignore_current_branch=False):
228
330
def select_branch (remote_name , branch_name ):
229
331
# type: (str, str) -> Branch
230
332
canonical_name = "/" .join (filter_ ((remote_name , branch_name )))
231
- for branch in self .interface ._branches :
333
+ for branch in self .interface .state [ "branches" ] :
232
334
if branch .canonical_name == canonical_name :
233
335
return (
234
336
branch ._replace (
@@ -260,7 +362,7 @@ def select_branch(remote_name, branch_name):
260
362
)
261
363
] + [
262
364
select_branch (remote_name , branch_name )
263
- for remote_name in self .interface .remotes
365
+ for remote_name in self .interface .state [ " remotes" ]
264
366
for branch_name in ui .extract_by_selector (
265
367
self .view ,
266
368
"meta.git-savvy.branches.branch.name" ,
@@ -525,9 +627,9 @@ class gs_branches_toggle_remotes(BranchInterfaceCommand):
525
627
526
628
def run (self , edit , show = None ):
527
629
if show is None :
528
- self .interface .show_remotes = not self .interface .show_remotes
630
+ self .interface .update_state ({ " show_remotes" : not self .interface .state [ " show_remotes" ]})
529
631
else :
530
- self .interface .show_remotes = show
632
+ self .interface .update_state ({ " show_remotes" : show })
531
633
self .interface .render ()
532
634
533
635
0 commit comments