import re from sublime import Region RE_FILE = re.compile(r'^([^\\// ].*)$') def first(seq, pred): # I can't comprehend how this isn't built-in. return next((item for item in seq if pred(item)), None) class DiredBaseCommand: """ Convenience functions for dired TextCommands """ @property def path(self): return self.view.settings().get('dired_path') def filecount(self): """ Returns the number of files and directories in the view. """ return self.view.settings().get('dired_count', 0) def move(self, forward=None): """ Moves the cursor one line forward or backwards. Clears all sections. """ assert forward in (True, False), 'forward must be set to True or False' files = self.fileregion() if files.empty(): return pt = self.view.sel()[0].a if files.contains(pt): # Try moving by one line. line = self.view.line(pt) pt = forward and (line.b + 1) or (line.a - 1) if not files.contains(pt): # Not (or no longer) in the list of files, so move to the closest edge. pt = (pt > files.b) and files.b or files.a line = self.view.line(pt) self.view.sel().clear() self.view.sel().add(Region(line.a, line.a)) def fileregion(self): """ Returns a region containing the lines containing filenames. If there are no filenames Region(0,0) is returned. """ count = self.filecount() if count == 0: return Region(0, 0) return Region(self.view.text_point(2, 0), self.view.text_point(count+2, 0)-1) def get_all(self): """ Returns a list of all filenames in the view. """ return [ RE_FILE.match(self.view.substr(l)).group(1) for l in self.view.lines(self.fileregion()) ] def get_selected(self): """ Returns a list of selected filenames. """ names = set() fileregion = self.fileregion() for sel in self.view.sel(): lines = self.view.lines(sel) for line in lines: if fileregion.contains(line): text = self.view.substr(line) names.add(RE_FILE.match(text).group(1)) return sorted(list(names)) def get_marked(self): lines = [] if self.filecount(): for region in self.view.get_regions('marked'): lines.extend(self.view.lines(region)) return [ RE_FILE.match(self.view.substr(line)).group(1) for line in lines ] def _mark(self, mark=None, regions=None): """ Marks the requested files. mark True, False, or a function with signature `func(oldmark, filename)`. The function should return True or False. regions Either a single region or a sequence of regions. Only files within the region will be modified. """ # Allow the user to pass a single region or a collection (like view.sel()). if isinstance(regions, Region): regions = [ regions ] filergn = self.fileregion() # We can't update regions for a key, only replace, so we need to record the existing # marks. previous = self.view.get_regions('marked') marked = { RE_FILE.match(self.view.substr(r)).group(1): r for r in previous } for region in regions: for line in self.view.lines(region): if filergn.contains(line): text = self.view.substr(line) filename = RE_FILE.match(text).group(1) if mark not in (True, False): newmark = mark(filename in marked, filename) assert newmark in (True, False), 'Invalid mark: {}'.format(newmark) else: newmark = mark if newmark: marked[filename] = line else: marked.pop(filename, None) if marked: r = sorted(list(marked.values()), key=lambda region: region.a) self.view.add_regions('marked', r, 'dired.marked', 'dot', 0) else: self.view.erase_regions('marked') def set_help_text(self, edit, text): # There is only 1 help text area, but the scope selector will skip blank lines # so use the union of all of the regions. regions = self.view.find_by_selector('comment.dired.help') region = regions[0] for other in regions[1:]: region = region.cover(other) start = region.begin() self.view.erase(edit, region) self.view.insert(edit, start, text)