4 from qtpy
.QtCore
import Qt
5 from qtpy
.QtCore
import Signal
11 from .. import qtutils
12 from ..git
import STDOUT
17 """Defines columns in the worktree browser"""
25 ALL
= (NAME
, STATUS
, MESSAGE
, AUTHOR
, AGE
)
26 ATTRS
= ('name', 'status', 'message', 'author', 'age')
32 [N_('Name'), N_('Status'), N_('Message'), N_('Author'), N_('Age')]
42 def text(cls
, column
):
44 value
= cls
.TEXT
[column
]
46 # Defer translation until runtime
48 value
= cls
.TEXT
[column
]
52 def attr(cls
, column
):
53 """Return the attribute for the column"""
54 return cls
.ATTRS
[column
]
57 class GitRepoModel(QtGui
.QStandardItemModel
):
58 """Provides an interface into a git repository for browsing purposes."""
62 def __init__(self
, context
, parent
):
63 QtGui
.QStandardItemModel
.__init
__(self
, parent
)
64 self
.setColumnCount(len(Columns
.ALL
))
66 self
.context
= context
67 self
.model
= context
.model
70 self
.turbo
= cfg
.get('cola.turbo', False)
71 self
.default_author
= cfg
.get('user.name', N_('Author'))
72 self
._interesting
_paths
= set()
73 self
._interesting
_files
= set()
74 self
._runtask
= qtutils
.RunTask(parent
=parent
)
76 self
.model
.updated
.connect(self
.refresh
, type=Qt
.QueuedConnection
)
78 self
.file_icon
= icons
.file_text()
79 self
.dir_icon
= icons
.directory()
81 def mimeData(self
, indexes
):
82 paths
= qtutils
.paths_from_indexes(
83 self
, indexes
, item_type
=GitRepoNameItem
.TYPE
85 return qtutils
.mimedata_from_paths(self
.context
, paths
)
88 return qtutils
.path_mimetypes()
94 def hasChildren(self
, index
):
96 item
= self
.itemFromIndex(index
)
97 result
= item
.hasChildren()
102 def get(self
, path
, default
=None):
104 item
= self
.invisibleRootItem()
106 item
= self
.entries
.get(path
, default
)
109 def create_row(self
, path
, create
=True, is_dir
=False):
111 row
= self
.entries
[path
]
114 column
= create_column
115 row
= self
.entries
[path
] = [
116 column(c
, path
, is_dir
) for c
in Columns
.ALL
122 def populate(self
, item
):
123 self
.populate_dir(item
, item
.path
+ '/')
125 def add_directory(self
, parent
, path
):
126 """Add a directory entry to the model."""
127 # First, try returning an existing item
128 current_item
= self
.get(path
)
129 if current_item
is not None:
130 return current_item
[0]
133 row_items
= self
.create_row(path
, is_dir
=True)
135 # Use a standard directory icon
136 name_item
= row_items
[0]
137 name_item
.setIcon(self
.dir_icon
)
138 parent
.appendRow(row_items
)
142 def add_file(self
, parent
, path
):
143 """Add a file entry to the model."""
145 file_entry
= self
.get(path
)
146 if file_entry
is not None:
150 row_items
= self
.create_row(path
)
151 name_item
= row_items
[0]
153 # Use a standard file icon for the name field
154 name_item
.setIcon(self
.file_icon
)
156 # Add file paths at the end of the list
157 parent
.appendRow(row_items
)
161 def populate_dir(self
, parent
, path
):
162 """Populate a subtree"""
163 context
= self
.context
164 dirs
, paths
= gitcmds
.listdir(context
, path
)
166 # Insert directories before file paths
170 dir_parent
= self
.add_parent_directories(parent
, dirname
)
171 self
.add_directory(dir_parent
, dirname
)
172 self
.update_entry(dirname
)
174 for filename
in paths
:
177 file_parent
= self
.add_parent_directories(parent
, filename
)
178 self
.add_file(file_parent
, filename
)
179 self
.update_entry(filename
)
181 def add_parent_directories(self
, parent
, dirname
):
182 """Ensure that all parent directory entries exist"""
184 parent_dir
= utils
.dirname(dirname
)
185 for path
in utils
.pathset(parent_dir
):
186 sub_parent
= self
.add_directory(sub_parent
, path
)
189 def path_is_interesting(self
, path
):
190 """Return True if path has a status."""
191 return path
in self
._interesting
_paths
193 def get_paths(self
, files
=None):
194 """Return paths of interest; e.g. paths with a status."""
196 files
= self
.get_files()
197 return utils
.add_parents(files
)
201 return set(model
.staged
+ model
.unstaged
)
204 old_files
= self
._interesting
_files
205 old_paths
= self
._interesting
_paths
206 new_files
= self
.get_files()
207 new_paths
= self
.get_paths(files
=new_files
)
209 if new_files
!= old_files
or not old_paths
:
215 for path
in sorted(new_paths
.union(old_paths
)):
216 self
.update_entry(path
)
218 self
._interesting
_files
= new_files
219 self
._interesting
_paths
= new_paths
221 def _initialize(self
):
222 self
.setHorizontalHeaderLabels(Columns
.text_values())
224 self
._interesting
_files
= files
= self
.get_files()
225 self
._interesting
_paths
= self
.get_paths(files
=files
)
227 root
= self
.invisibleRootItem()
228 self
.populate_dir(root
, './')
230 def update_entry(self
, path
):
231 if self
.turbo
or path
not in self
.entries
:
232 return # entry doesn't currently exist
233 context
= self
.context
234 task
= GitRepoInfoTask(context
, path
, self
.default_author
)
235 task
.connect(self
.apply_data
)
236 self
._runtask
.start(task
)
238 def apply_data(self
, data
):
239 entry
= self
.get(data
[0])
241 entry
[1].set_status(data
[1])
242 entry
[2].setText(data
[2])
243 entry
[3].setText(data
[3])
244 entry
[4].setText(data
[4])
247 def create_column(col
, path
, is_dir
):
248 """Creates a StandardItem for use in a treeview cell."""
249 # GitRepoNameItem is the only one that returns a custom type()
250 # and is used to infer selections.
251 if col
== Columns
.NAME
:
252 item
= GitRepoNameItem(path
, is_dir
)
254 item
= GitRepoItem(path
)
258 class GitRepoInfoTask(qtutils
.Task
):
259 """Handles expensive git lookups for a path."""
261 def __init__(self
, context
, path
, default_author
):
262 qtutils
.Task
.__init
__(self
)
263 self
.context
= context
265 self
._default
_author
= default_author
269 """Return git data for a path
271 Supported keys are 'date', 'message', and 'author'
274 git
= self
.context
.git
281 pretty
=r
'format:%ar%x01%s%x01%an',
285 date
, message
, author
= log_line
.split(chr(0x01), 2)
286 self
._data
['date'] = date
287 self
._data
['message'] = message
288 self
._data
['author'] = author
290 self
._data
['date'] = self
.date()
291 self
._data
['message'] = '-'
292 self
._data
['author'] = self
._default
_author
294 return self
._data
[key
]
297 """Returns a relative date for a file path
299 This is typically used for new entries that do not have
300 'git log' information.
304 st
= core
.stat(self
.path
)
306 return N_('%d minutes ago') % 0
307 elapsed
= time
.time() - st
.st_mtime
308 minutes
= int(elapsed
/ 60)
310 return N_('%d minutes ago') % minutes
311 hours
= int(elapsed
/ 60 / 60)
313 return N_('%d hours ago') % hours
314 return N_('%d days ago') % int(elapsed
/ 60 / 60 / 24)
317 """Return the status for the entry's path."""
318 model
= self
.context
.model
319 unmerged
= utils
.add_parents(model
.unmerged
)
320 modified
= utils
.add_parents(model
.modified
)
321 staged
= utils
.add_parents(model
.staged
)
322 untracked
= utils
.add_parents(model
.untracked
)
323 upstream_changed
= utils
.add_parents(model
.upstream_changed
)
327 status
= (icons
.modified_name(), N_('Unmerged'))
328 elif path
in modified
and self
.path
in staged
:
329 status
= (icons
.partial_name(), N_('Partially Staged'))
330 elif path
in modified
:
331 status
= (icons
.modified_name(), N_('Modified'))
333 status
= (icons
.staged_name(), N_('Staged'))
334 elif path
in upstream_changed
:
335 status
= (icons
.upstream_name(), N_('Changed Upstream'))
336 elif path
in untracked
:
343 """Perform expensive lookups and post corresponding events."""
347 self
.data('message'),
354 class GitRepoItem(QtGui
.QStandardItem
):
355 """Represents a cell in a treeview.
357 Many GitRepoItems map to a single repository path.
358 Each GitRepoItem manages a different cell in the tree view.
359 One is created for each column -- Name, Status, Age, etc.
363 def __init__(self
, path
):
364 QtGui
.QStandardItem
.__init
__(self
)
367 self
.setDragEnabled(False)
368 self
.setEditable(False)
370 def set_status(self
, data
):
373 self
.setIcon(QtGui
.QIcon(icon
))
375 self
.setIcon(QtGui
.QIcon())
379 class GitRepoNameItem(GitRepoItem
):
380 """Subclass GitRepoItem to provide a custom type()."""
382 TYPE
= qtutils
.standard_item_type_value(1)
384 def __init__(self
, path
, is_dir
):
385 GitRepoItem
.__init
__(self
, path
)
387 self
.setDragEnabled(True)
388 self
.setText(utils
.basename(path
))
392 Indicate that this item is of a special user-defined type.
394 'name' is the only column that registers a user-defined type.
395 This is done to allow filtering out other columns when determining
396 which paths are selected.
401 def hasChildren(self
):