def __init__(self): self._bufName = vim.eval("expand('$VIMRUNTIME/[LeaderF]')") self._winPos = int(vim.eval("g:Lf_WindowPosition")) self._autochdir = 0 self._cli = LfCli() self._explorer = None self._index = 0 self._helpLength = 0 self._showHelp = False self._selections = {} self._initStlVar() self._setStlMode() self._getExplClass()
def __init__(self): self._buffer_name = vim.eval("expand('$VIMRUNTIME/LeaderF')") self._win_pos = int(vim.eval("g:Lf_WindowPosition")) self._autochdir = 0 self._cli = LfCli() self._explorer = None self._content = [] self._index = 0 self._help_length = 0 self._show_help = False self._selections = {} self._highlight_pos = [] self._highlight_refine_pos = [] self._highlight_ids = [] self._initStlVar() self._setStlMode() self._getExplClass()
def __init__(self): self._autochdir = 0 self._instance = None self._cli = LfCli() self._explorer = None self._content = [] self._index = 0 self._help_length = 0 self._show_help = False self._selections = {} self._highlight_pos = [] self._highlight_refine_pos = [] self._highlight_ids = [] self._getExplClass()
class Manager(object): def __init__(self): self._bufName = vim.eval("expand('$VIMRUNTIME/[LeaderF]')") self._winPos = int(vim.eval("g:Lf_WindowPosition")) self._maxLines = int(vim.eval("g:Lf_MaxLines")) self._autochdir = 0 self._cli = LfCli() self._explorer = None self._index = 0 self._helpLength = 0 self._showHelp = False self._selections = {} self._initStlVar() self._setStlMode() self._getExplClass() #***************************************************** # abstract methods, in fact all the functions can be overridden #***************************************************** def _getExplClass(self): ''' this function MUST be overridden return the name of Explorer class ''' raise NotImplementedError("Can't instantiate abstract class Manager with abstract methods _getExplClass") def _defineMaps(self): pass def _cmdExtension(self, cmd): ''' this function can be overridden to add new cmd if return true, exit the input loop ''' pass def _getDigest(self, line): ''' this function can be overridden specify what part to match regex for a line in the match window ''' return line def _createHelp(self): return [] #***************************************************** # private methods #***************************************************** def _createHelpHint(self): help = [] if not self._showHelp: help.append('" Press <F1> for help') help.append('" ---------------------------------------------------') else: help += self._createHelp() self._helpLength = len(help) vim.command("setlocal modifiable") vim.current.buffer.append(help, 0) vim.current.window.cursor = (self._helpLength + 1, 0) vim.command("setlocal nomodifiable") def _hideHelp(self): for i in range(self._helpLength): del vim.current.buffer[0] self._helpLength = 0 def _getExplorer(self): if self._explorer is None: self._explorer = self._getExplClass()() return self._explorer def _initStlVar(self): vim.command("let g:Lf_statusline_function = '-'") vim.command("let g:Lf_statusline_curDir = '-'") vim.command("let g:Lf_statusline_total = '0'") def _setStlMode(self): if self._cli.isFuzzy: if self._cli.isFileNameOnly: vim.command("let g:Lf_statusline_mode = 'NameOnly'") else: vim.command("let g:Lf_statusline_mode = 'FullPath'") else: vim.command("let g:Lf_statusline_mode = 'Regexp'") def _bufwinnr(self, name): nr = 1 for w in vim.windows: if w.buffer.name is not None and os.path.abspath(w.buffer.name) == os.path.abspath(name): return nr nr += 1 return 0 def _gotoBuffer(self): if int(vim.eval("&autochdir")) == 1: self._autochdir = 1 vim.command("set noautochdir") else: self._autochdir = 0 self._origBuf = vim.current.buffer.name self._origWinNr = int(vim.eval("winnr()")) nr = self._bufwinnr(self._bufName) if nr == 0: self._createBufWindow() else: vim.command("exec '%d wincmd w'" % nr) self._setAttributes() self._setStatusline() self._defineMaps() def _createBufWindow(self): if self._winPos == 0: vim.command("silent! noa keepj hide edit %s" % self._bufName) elif self._winPos == 1: vim.command("silent! noa keepj bo sp %s" % self._bufName) elif self._winPos == 2: vim.command("silent! noa keepj to sp %s" % self._bufName) else: vim.command("silent! noa keepj to vsp %s" % self._bufName) def _setAttributes(self): vim.command("setlocal nobuflisted") vim.command("setlocal buftype=nofile") vim.command("setlocal bufhidden=hide") vim.command("setlocal noswapfile") vim.command("setlocal nolist") vim.command("setlocal nonumber") vim.command("setlocal nospell") vim.command("setlocal nowrap") vim.command("setlocal nofoldenable") vim.command("setlocal foldcolumn=1") vim.command("setlocal cursorline") vim.command("setlocal filetype=LeaderF") def _setStatusline(self): vim.command("setlocal statusline=LeaderF:\ [%#Lf_hl_stlFunction#%{g:Lf_statusline_function}%#Lf_hl_none#,") vim.command("setlocal statusline+=\ %#Lf_hl_stlMode#%-9(%{g:Lf_statusline_mode}%#Lf_hl_none#]%)") vim.command("setlocal statusline+=\ \ %<%#Lf_hl_stlCurDir#%{g:Lf_statusline_curDir}%#Lf_hl_none#") vim.command("setlocal statusline+=%=%lL/%-5L\ \ Total:%{g:Lf_statusline_total}\ ") vim.command("redraw!") def _toUp(self): vim.command("norm! k") def _toDown(self): vim.command("norm! j") def _leftClick(self): nr = self._bufwinnr(self._bufName) if nr == int(vim.eval("v:mouse_win")): vim.command("exec v:mouse_lnum") vim.command("exec 'norm!'.v:mouse_col.'|'") self.clearSelections() else: self.quit() self._exitLoop = True def _filter(self, iterable, regex): if self._cli.isFuzzy: if self._cli.isFileNameOnly: if self._cli.isMixed: if regex[0] == '': return [line for line in iterable if re.search(regex[1], self._getDigest(line), re.I)] elif regex[1] == '': return [line for line in iterable if re.search(regex[0], os.path.basename(self._getDigest(line)), re.I)] else: iterable = [line for line in iterable if re.search(regex[0], os.path.basename(self._getDigest(line)), re.I)] return [line for line in iterable if re.search(regex[1], os.path.dirname(self._getDigest(line)), re.I)] else: return [line for line in iterable if re.search(regex, os.path.basename(self._getDigest(line)), re.I)] else: return [line for line in iterable if re.search(regex, self._getDigest(line), re.I)] else: try: if '-2' == vim.eval("g:LfNoErrMsgMatch('', '%s')" % escQuote(regex)): return [] else: return [line for line in iterable if '-1' != vim.eval("g:LfNoErrMsgMatch('%s', '%s')" % (escQuote(self._getDigest(line).strip()), escQuote(regex)))] except vim.error: pass def _isPrefix(self, regex): #assume that there are no \%23l, \%23c, \%23v, \%... pos = self._cli.cursorPos if pos > 1: if regex[pos - 2] != '\\' and (regex[pos - 1].isalnum() or regex[pos - 1] in r'`$%*(-_+[\;:,. /?'): if regex[pos - 2] == '_': if pos > 2 and regex[pos - 3] == '\\': #\_x return False else: return True if regex.endswith(r'\zs') or regex.endswith(r'\ze'): return False return True return False def _search(self, content, regex): self.clearSelections() self._cli.highlightMatches() cb = vim.current.buffer if not regex: if self._maxLines > 0: setBuffer(cb, content[:self._maxLines]) else: setBuffer(cb, content) return if not self._cli.isFuzzy: if not self._isPrefix(regex): self._index = 0 step = int(vim.eval("g:Lf_SearchStep")) length = len(content) if self._index == 0: self._index = step if self._index < length: setBuffer(cb, self._filter(content[:self._index], regex)) else: setBuffer(cb, self._filter(content, regex)) else: setBuffer(cb, self._filter(cb[:], regex)) while len(cb) < 1000 and self._index < length: end = self._index + step startLine = 0 if cb[0] == '' else len(cb) if end < length: appendBuffer(cb, self._filter(content[self._index:end], regex), startLine) else: appendBuffer(cb, self._filter(content[self._index:], regex), startLine) self._index = end if len(cb) > vim.current.window.height * 2: break if self._getExplorer().supportsSort(): num = int(vim.eval("g:Lf_NumberOfSort")) if num == -1: self._sortResult(len(cb)) elif num == 0: pass elif num == 1: self._sortResult(vim.current.window.height) else: self._sortResult(num) vim.current.window.cursor = (self._helpLength + 1, 0) def _getWeight(self, str, t): ''' this function can be overridden to get the weight of the line, so that it can be applied in the sort algorithm ''' pre = str.lower().index(t[0].lower()) s = str[pre:] sl = len(s) tl = len(t) val = [[0 for col in range(tl)] for row in range(sl)] val[0][0] = 1 JJ = 2 for i in range(1, sl): J = JJ for j in range(min(J, tl)): if s[i].lower() != t[j].lower(): val[i][j] = val[i-1][j] else: if j == JJ-1: JJ += 1 if val[i-1][j] == (j+1)*(j+1): # prune val[i][j] = (j+1)*(j+1) continue val[i][j] = val[i-1][j] ii = i jj = j k = 0 while ii >= 0 and jj >= 0: if s[ii].lower() == t[jj].lower(): if i == j and val[i-1][j-1] == i*i: # prune val[i][j] = (i+1)*(i+1) break k += 1 if j-k >= 0: if val[i-k][j-k] + k*k > val[i][j]: val[i][j] = val[i-k][j-k] + k*k else: val[i][j] = k*k ii -= 1 jj -= 1 else: break if val[i][j] == tl*tl: # prune return val[i][j] + 1.0/(2*pre + len(str)) return val[sl-1][tl-1] + 1.0/(2*pre + len(str)) def _sortResult(self, num): ''' this function can be overridden to customize the sort algorithm ''' cb = vim.current.buffer if len(cb) == 1 and cb[0] == '' or num == 0: return if self._cli.isFuzzy: if self._cli.isFileNameOnly: if self._cli.isMixed: pass else: pairs = [(i, self._getWeight(os.path.basename(self._getDigest(cb[i])), self._cli.cmdline)) for i in range(len(cb))] pairs = heapq.nlargest(num, pairs, key = lambda x: x[1]) lines = [cb[i[0]] for i in pairs] append(cb, lines, 0) pairs.sort(key = lambda x: x[0], reverse = True) for i in pairs: del cb[i[0] + len(pairs)] else: pass def _accept(self, file, mode): try: if file: if mode == '': pass elif mode == 'h': vim.command("split") elif mode == 'v': vim.command("vsplit") elif mode == 't': vim.command("tabedit") tabPos = int(vim.eval("g:Lf_TabpagePosition")) if tabPos == 0: vim.command("tabm 0") elif tabPos == 1: vim.command("tabm -1") elif tabPos == 3: vim.command("tabm") self._getExplorer().acceptSelection(file) except vim.error: pass def clearSelections(self): for i in self._selections.values(): vim.command("call matchdelete(%d)" % i) self._selections.clear() #***************************************************** # public methods #***************************************************** def toggleHelp(self): vim.command("setlocal modifiable") self._showHelp = not self._showHelp for i in range(self._helpLength): del vim.current.buffer[0] self._createHelpHint() self.clearSelections() vim.command("setlocal nomodifiable") def accept(self, mode = ''): if vim.current.window.cursor[0] <= self._helpLength: vim.command("norm! j") return if len(self._selections) > 0: files = [] for i in self._selections.keys(): files.append(os.path.abspath(vim.current.buffer[i-1])) self.quit() if mode == '': vim.command("argdelete *") for file in files: vim.command("argadd %s" % escSpecial(file)) self._accept(files[0], mode) else: for file in files: self._accept(file, mode) else: file = os.path.abspath(vim.current.line) self.quit() self._accept(file, mode) def quit(self): self._cli.clear() self.clearSelections() if self._winPos != 0 and len(vim.windows) > 1: vim.command("hide") vim.command("exec '%d wincmd w'" % self._origWinNr) else: if self._origBuf is None or vim.eval("bufexists('%s')" % escQuote(self._origBuf)) == '0': vim.command("bd") else: vim.command("hide edit %s" % escSpecial(self._origBuf)) if self._winPos != 0: vim.command("call getchar(0) | redraw | echo") else: vim.command("call getchar(0)") if self._autochdir == 1: cwd = os.getcwd() vim.command("set autochdir") # I think vim has a bug here os.chdir(cwd) def refresh(self, content = None): rContent = self._getExplorer().getFreshContent() if rContent is None: return if content is None: vim.command("setlocal modifiable") setBuffer(vim.current.buffer, rContent) self._content = rContent if self._cli.regex: self._index = 0 self._search(rContent, self._cli.regex) vim.command("setlocal nomodifiable") else: setBuffer(vim.current.buffer, rContent) content[:] = rContent #use slice to change content if self._cli.regex: self._index = 0 self._search(rContent, self._cli.regex) def addSelections(self): nr = self._bufwinnr(self._bufName) if int(vim.eval("v:mouse_win")) != 0 and nr != int(vim.eval("v:mouse_win")): return elif nr == int(vim.eval("v:mouse_win")): vim.command("exec v:mouse_lnum") vim.command("exec 'norm!'.v:mouse_col.'|'") lineNr = vim.current.window.cursor[0] if lineNr <= self._helpLength: vim.command("norm! j") return if lineNr in self._selections: vim.command("call matchdelete(%d)" % self._selections[lineNr]) del self._selections[lineNr] else: id = int(vim.eval("matchadd('Lf_hl_selection', '\%%%dl.')" % lineNr)) self._selections[lineNr] = id if int(vim.eval("v:mouse_win")) != 0: vim.command('call feedkeys("\<C-@>")') #vim has bug, so add this line def selectMulti(self): origLine = vim.current.window.cursor[0] nr = self._bufwinnr(self._bufName) if int(vim.eval("v:mouse_win")) != 0 and nr != int(vim.eval("v:mouse_win")): return elif nr == int(vim.eval("v:mouse_win")): vim.command("exec v:mouse_lnum") vim.command("exec 'norm!'.v:mouse_col.'|'") self.clearSelections() curLine = vim.current.window.cursor[0] for i in range(min(origLine, curLine), max(origLine, curLine)+1): if i > self._helpLength and i not in self._selections: id = int(vim.eval("matchadd('Lf_hl_selection', '\%%%dl.')" % (i))) self._selections[i] = id vim.command('call feedkeys("\<C-@>")') #vim has bug, so add this line def selectAll(self): for i in range(len(vim.current.buffer)): if i >= self._helpLength and i+1 not in self._selections: id = int(vim.eval("matchadd('Lf_hl_selection', '\%%%dl.')" % (i+1))) self._selections[i+1] = id def startExplorer(self, *args, **kwargs): self._cli.setFullPathFeature(self._getExplorer().supportsFullPath()) vim.command("let g:Lf_statusline_function = '%s'" % self._getExplorer().getStlFunction()) vim.command("echohl WarningMsg | redraw | echo ' searching ...' | echohl NONE") content = self._getExplorer().getContent(*args, **kwargs) if content is None: return self._gotoBuffer() vim.command("let g:Lf_statusline_curDir = '%s'" % self._getExplorer().getStlCurDir()) vim.command("let g:Lf_statusline_total = '%d'" % len(content)) self.startExplAction(content) def startExplAction(self, content = None): vim.command("setlocal modifiable") self._hideHelp() if content is None: content = self._content else: if self._maxLines > 0: setBuffer(vim.current.buffer, content[:self._maxLines]) else: setBuffer(vim.current.buffer, content) self._index = 0 quit = False for cmd in self._cli.input(): if equal(cmd, '<Update>'): self._search(content, self._cli.regex) elif equal(cmd, '<Shorten>'): self._index = 0 self._search(content, self._cli.regex) elif equal(cmd, '<Mode>'): self._setStlMode() self._index = 0 if self._cli.regex: self._search(content, self._cli.regex) elif equal(cmd, '<Up>'): self._toUp() elif equal(cmd, '<Down>'): self._toDown() elif equal(cmd, '<LeftMouse>'): self._exitLoop = False self._leftClick() if self._exitLoop: break elif equal(cmd, '<2-LeftMouse>'): self._leftClick() self.accept() break elif equal(cmd, '<CR>'): self.accept() break elif equal(cmd, '<C-X>'): self.accept('h') break elif equal(cmd, '<C-]>'): self.accept('v') break elif equal(cmd, '<C-T>'): self.accept('t') break elif equal(cmd, '<Quit>'): quit = True break elif equal(cmd, '<Esc>'): self.clearSelections() self._cli.hideCursor() vim.command("setlocal nomodifiable") self._content = content self._createHelpHint() elif equal(cmd, '<F5>'): self.refresh(content) elif equal(cmd, '<C-LeftMouse>'): if self._getExplorer().supportsMulti(): self.addSelections() elif equal(cmd, '<S-LeftMouse>'): if self._getExplorer().supportsMulti(): self.selectMulti() elif equal(cmd, '<C-A>'): if self._getExplorer().supportsMulti(): self.selectAll() elif equal(cmd, '<C-L>'): self.clearSelections() else: if self._cmdExtension(cmd): break if quit: #due to a bug, I have to write this ugly code self.quit()
class Manager(object): def __init__(self): self._autochdir = 0 self._instance = None self._cli = LfCli() self._explorer = None self._content = [] self._index = 0 self._help_length = 0 self._show_help = False self._selections = {} self._highlight_pos = [] self._highlight_refine_pos = [] self._highlight_ids = [] self._getExplClass() #************************************************************** # abstract methods, in fact all the functions can be overridden #************************************************************** def _getExplClass(self): """ this function MUST be overridden return the name of Explorer class """ raise NotImplementedError("Can't instantiate abstract class Manager " "with abstract methods _getExplClass") def _defineMaps(self): pass def _cmdExtension(self, cmd): """ this function can be overridden to add new cmd if return true, exit the input loop """ pass def _argaddFiles(self, files): # It will raise E480 without 'silent!' lfCmd("silent! argdelete *") for file in files: lfCmd("argadd %s" % escSpecial(file)) def _acceptSelection(self, *args, **kwargs): if len(args) == 0: return file = args[0] lfCmd("hide edit %s" % escSpecial(file)) def _getDigest(self, line, mode): """ this function can be overridden specify what part in the line to be processed and highlighted Args: mode: 0, return the full path 1, return the name only 2, return the directory name """ if mode == 0: return line elif mode == 1: return getBasename(line) else: return getDirname(line) def _getDigestStartPos(self, line, mode): """ this function can be overridden return the start position of the digest returned by _getDigest() Args: mode: 0, return the start postion of full path 1, return the start postion of name only 2, return the start postion of directory name """ if mode == 0 or mode == 2: return 0 else: return lfBytesLen(getDirname(line)) def _createHelp(self): return [] def _setStlMode(self): if self._cli.isFuzzy: if self._cli.isFullPath: mode = 'FullPath' else: mode = 'NameOnly' else: mode = 'Regex' self._getInstance().setStlMode(mode) def _beforeEnter(self): self._resetAutochdir() def _afterEnter(self): self._cleanup() self._defineMaps() def _beforeExit(self): self._cleanup() def _afterExit(self): pass #************************************************************** def _getInstance(self): if self._instance is None: self._instance = LfInstance(self._getExplorer().getStlCategory(), self._beforeEnter, self._afterEnter, self._beforeExit, self._afterExit) return self._instance def _createHelpHint(self): help = [] if not self._show_help: help.append('" Press <F1> for help') help.append('" ---------------------------------------------------------') else: help += self._createHelp() self._help_length = len(help) self._getInstance().buffer.append(help, 0) self._getInstance().window.cursor = (self._help_length + 1, 0) def _hideHelp(self): del self._getInstance().buffer[:self._help_length] self._help_length = 0 def _getExplorer(self): if self._explorer is None: self._explorer = self._getExplClass()() return self._explorer def _resetAutochdir(self): if int(lfEval("&autochdir")) == 1: self._autochdir = 1 lfCmd("set noautochdir") else: self._autochdir = 0 def _setAutochdir(self): if self._autochdir == 1: # When autochdir is set, Vim will change the current working directory # to the directory containing the file which was opened or selected. lfCmd("set autochdir") def _toUp(self): lfCmd("norm! k") def _toDown(self): lfCmd("norm! j") def _leftClick(self): if self._getInstance().window.number == int(lfEval("v:mouse_win")): lfCmd("exec v:mouse_lnum") lfCmd("exec 'norm!'.v:mouse_col.'|'") self.clearSelections() exit_loop = False else: self.quit() exit_loop = True return exit_loop def _search(self, content): self.clearSelections() self._cli.highlightMatches() if not self._cli.pattern: # e.g., when <BS> or <Del> is typed self._getInstance().setBuffer(content) self._clearHighlights() self._clearHighlightsPos() return if self._cli.isFuzzy: self._fuzzySearch(content) else: self._regexSearch(content) def _filter(self, step, filter_method, content): """ Construct a list from result of filter_method(content). Args: step: An integer to indicate the number of lines to filter one time. filter_method: A function to apply `content` as parameter and return an iterable. content: The list to be filtered. """ length = len(content) cb = self._getInstance().buffer result = [] if self._index == 0: self._index = step if self._index < length: result.extend(filter_method(content[:self._index])) else: result.extend(filter_method(content)) else: result.extend(filter_method(cb[:])) if self._index < length: end = self._index + step - len(cb) result.extend(filter_method(content[self._index:end])) self._index = end while len(result) < 200 and self._index < length: end = self._index + 5000 result.extend(filter_method(content[self._index:end])) self._index = end return result def _fuzzyFilter(self, is_full_path, get_weight, iterable): """ return a list, each item is a pair (weight, line) """ getDigest = partial(self._getDigest, mode=0 if is_full_path else 1) pairs = ((get_weight(getDigest(line)), line) for line in iterable) return (p for p in pairs if p[0]) def _refineFilter(self, first_get_weight, get_weight, iterable): getDigest = self._getDigest triples = ((first_get_weight(getDigest(line, 1)), get_weight(getDigest(line, 2)), line) for line in iterable) return ((i[0] + i[1], i[2]) for i in triples if i[0] and i[1]) def _fuzzySearch(self, content): encoding = lfEval("&encoding") if self._cli.isRefinement: if self._cli.pattern[1] == '': # e.g. abc; fuzzy_match = FuzzyMatch(self._cli.pattern[0], encoding) filter_method = partial(self._fuzzyFilter, False, fuzzy_match.getWeight) highlight_method = partial(self._highlight, False, fuzzy_match.getHighlights) elif self._cli.pattern[0] == '': # e.g. ;abc fuzzy_match = FuzzyMatch(self._cli.pattern[1], encoding) filter_method = partial(self._fuzzyFilter, True, fuzzy_match.getWeight) highlight_method = partial(self._highlight, True, fuzzy_match.getHighlights) else: fuzzy_match0 = FuzzyMatch(self._cli.pattern[0], encoding) fuzzy_match1 = FuzzyMatch(self._cli.pattern[1], encoding) filter_method = partial(self._refineFilter, fuzzy_match0.getWeight, fuzzy_match1.getWeight) highlight_method = partial(self._highlightRefine, fuzzy_match0.getHighlights, fuzzy_match1.getHighlights) else: fuzzy_match = FuzzyMatch(self._cli.pattern, encoding) filter_method = partial(self._fuzzyFilter, self._cli.isFullPath, fuzzy_match.getWeight) highlight_method = partial(self._highlight, self._cli.isFullPath, fuzzy_match.getHighlights) pairs = self._filter(30000, filter_method, content) pairs.sort(key=operator.itemgetter(0), reverse=True) self._getInstance().setBuffer([p[1] for p in pairs]) highlight_method() def _clearHighlights(self): for i in self._highlight_ids: lfCmd("silent! call matchdelete(%d)" % i) self._highlight_ids = [] def _clearHighlightsPos(self): self._highlight_pos = [] self._highlight_refine_pos = [] def _resetHighlights(self): self._clearHighlights() for i, pos in enumerate(self._highlight_pos, self._help_length + 1): pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int(lfEval("matchaddpos('Lf_hl_match', %s)" % str(pos[j:j+8]))) self._highlight_ids.append(id) for i, pos in enumerate(self._highlight_refine_pos, self._help_length + 1): pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int(lfEval("matchaddpos('Lf_hl_matchRefine', %s)" % str(pos[j:j+8]))) self._highlight_ids.append(id) def _highlight(self, is_full_path, get_highlights): # matchaddpos() is introduced by Patch 7.4.330 if (lfEval("exists('*matchaddpos')") == '0' or lfEval("g:Lf_HighlightIndividual") == '0'): return cb = self._getInstance().buffer if len(cb) == 1 and cb[0] == '': # buffer is empty. return highlight_number = int(lfEval("g:Lf_NumberOfHighlight")) self._clearHighlights() getDigest = partial(self._getDigest, mode=0 if is_full_path else 1) # e.g., self._highlight_pos = [ [ [2,3], [6,2] ], [ [1,4], [7,6], ... ], ... ] # where [2, 3] indicates the highlight starts at the 2nd column with the # length of 3 in bytes self._highlight_pos = [get_highlights(getDigest(line)) for line in cb[:highlight_number]] for i, pos in enumerate(self._highlight_pos, 1): start_pos = self._getDigestStartPos(cb[i-1], 0 if is_full_path else 1) if start_pos > 0: for j in range(len(pos)): pos[j][0] += start_pos pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int(lfEval("matchaddpos('Lf_hl_match', %s)" % str(pos[j:j+8]))) self._highlight_ids.append(id) def _highlightRefine(self, first_get_highlights, get_highlights): # matchaddpos() is introduced by Patch 7.4.330 if (lfEval("exists('*matchaddpos')") == '0' or lfEval("g:Lf_HighlightIndividual") == '0'): return cb = self._getInstance().buffer if len(cb) == 1 and cb[0] == '': # buffer is empty. return highlight_number = int(lfEval("g:Lf_NumberOfHighlight")) self._clearHighlights() getDigest = self._getDigest self._highlight_pos = [first_get_highlights(getDigest(line, 1)) for line in cb[:highlight_number]] for i, pos in enumerate(self._highlight_pos, 1): start_pos = self._getDigestStartPos(cb[i-1], 1) if start_pos > 0: for j in range(len(pos)): pos[j][0] += start_pos pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int(lfEval("matchaddpos('Lf_hl_match', %s)" % str(pos[j:j+8]))) self._highlight_ids.append(id) self._highlight_refine_pos = [get_highlights(getDigest(line, 2)) for line in cb[:highlight_number]] for i, pos in enumerate(self._highlight_refine_pos, 1): start_pos = self._getDigestStartPos(cb[i-1], 2) if start_pos > 0: for j in range(len(pos)): pos[j][0] += start_pos pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int(lfEval("matchaddpos('Lf_hl_matchRefine', %s)" % str(pos[j:j+8]))) self._highlight_ids.append(id) def _regexFilter(self, iterable): try: if ('-2' == lfEval("g:LfNoErrMsgMatch('', '%s')" % escQuote(self._cli.pattern))): return iter([]) else: return (line for line in iterable if '-1' != lfEval("g:LfNoErrMsgMatch('%s', '%s')" % (escQuote(self._getDigest(line, 0).strip()), escQuote(self._cli.pattern)))) except vim.error: return iter([]) def _regexSearch(self, content): if not self._cli.isPrefix: self._index = 0 lines = self._filter(8000, self._regexFilter, content) self._getInstance().setBuffer(lines) def clearSelections(self): for i in self._selections.values(): lfCmd("call matchdelete(%d)" % i) self._selections.clear() def _cleanup(self): self._cli.clear() self.clearSelections() self._clearHighlights() self._clearHighlightsPos() @modifiableController def toggleHelp(self): self._show_help = not self._show_help del self._getInstance().buffer[:self._help_length] self._createHelpHint() self.clearSelections() self._resetHighlights() def _accept(self, file, mode): try: if file: if mode == '': pass elif mode == 'h': lfCmd("split") elif mode == 'v': lfCmd("vsplit") elif mode == 't': lfCmd("tabedit") tab_pos = int(lfEval("g:Lf_TabpagePosition")) if tab_pos == 0: lfCmd("tabm 0") elif tab_pos == 1: lfCmd("tabm -1") elif tab_pos == 3: lfCmd("tabm") self._acceptSelection(file) except (KeyboardInterrupt, vim.error): pass def accept(self, mode=''): if self._getInstance().window.cursor[0] <= self._help_length: lfCmd("norm! j") return if len(self._selections) > 0: files = [] for i in sorted(self._selections.keys()): files.append(self._getInstance().buffer[i-1]) self._getInstance().exitBuffer() if mode == '': self._argaddFiles(files) self._accept(files[0], mode) else: for file in files: self._accept(file, mode) else: file = self._getInstance().currentLine self._getInstance().exitBuffer() self._accept(file, mode) self._setAutochdir() def quit(self): self._getInstance().exitBuffer() self._setAutochdir() def refresh(self, normal_mode=True): self._content = self._getExplorer().getFreshContent() if self._content is None: return if normal_mode: # when called in Normal mode self._getInstance().buffer.options['modifiable'] = True self._getInstance().setBuffer(self._content) if self._cli.pattern: self._index = 0 self._search(self._content) if normal_mode: # when called in Normal mode self._createHelpHint() self._resetHighlights() self._getInstance().buffer.options['modifiable'] = False def addSelections(self): nr = self._getInstance().window.number if (int(lfEval("v:mouse_win")) != 0 and nr != int(lfEval("v:mouse_win"))): return elif nr == int(lfEval("v:mouse_win")): lfCmd("exec v:mouse_lnum") lfCmd("exec 'norm!'.v:mouse_col.'|'") line_nr = self._getInstance().window.cursor[0] if line_nr <= self._help_length: lfCmd("norm! j") return if line_nr in self._selections: lfCmd("call matchdelete(%d)" % self._selections[line_nr]) del self._selections[line_nr] else: id = int(lfEval("matchadd('Lf_hl_selection', '\%%%dl.')" % line_nr)) self._selections[line_nr] = id def selectMulti(self): orig_line = self._getInstance().window.cursor[0] nr = self._getInstance().window.number if (int(lfEval("v:mouse_win")) != 0 and nr != int(lfEval("v:mouse_win"))): return elif nr == int(lfEval("v:mouse_win")): cur_line = int(lfEval("v:mouse_lnum")) self.clearSelections() for i in range(min(orig_line, cur_line), max(orig_line, cur_line)+1): if i > self._help_length and i not in self._selections: id = int(lfEval("matchadd('Lf_hl_selection', '\%%%dl.')" % (i))) self._selections[i] = id def selectAll(self): line_num = len(self._getInstance().buffer) if line_num > 300: lfCmd("echohl Error | redraw | echo ' Too many files selected!' | echohl NONE") lfCmd("sleep 1") return for i in range(line_num): if i >= self._help_length and i+1 not in self._selections: id = int(lfEval("matchadd('Lf_hl_selection', '\%%%dl.')" % (i+1))) self._selections[i+1] = id def startExplorer(self, win_pos, *args, **kwargs): self._cli.setNameOnlyFeature(self._getExplorer().supportsNameOnly()) lfCmd("echohl WarningMsg | redraw |" "echo ' searching ...' | echohl NONE") self._content = self._getExplorer().getContent(*args, **kwargs) if not self._content: lfCmd("echohl Error | redraw | echo ' No content!' | echohl NONE") return self._getInstance().enterBuffer(win_pos) self._getInstance().setStlCategory(self._getExplorer().getStlCategory()) self._setStlMode() self._getInstance().setStlCwd(self._getExplorer().getStlCurDir()) self._getInstance().setStlTotal(len(self._content)) self._getInstance().setBuffer(self._content) lfCmd("normal! gg") self._index = 0 self.input(False) @modifiableController def input(self, normal_mode=True): self._hideHelp() self._resetHighlights() for cmd in self._cli.input(): if equal(cmd, '<Update>'): self._search(self._content) elif equal(cmd, '<Shorten>'): self._index = 0 # search from beginning self._search(self._content) elif equal(cmd, '<Mode>'): self._setStlMode() self._index = 0 # search from beginning if self._cli.pattern: self._search(self._content) elif equal(cmd, '<Up>'): self._toUp() elif equal(cmd, '<Down>'): self._toDown() elif equal(cmd, '<LeftMouse>'): if self._leftClick(): break elif equal(cmd, '<2-LeftMouse>'): self._leftClick() self.accept() break elif equal(cmd, '<CR>'): self.accept() break elif equal(cmd, '<C-X>'): self.accept('h') break elif equal(cmd, '<C-]>'): self.accept('v') break elif equal(cmd, '<C-T>'): self.accept('t') break elif equal(cmd, '<Quit>'): self.quit() break elif equal(cmd, '<Esc>'): # switch to Normal mode self.clearSelections() self._cli.hideCursor() self._createHelpHint() self._resetHighlights() break elif equal(cmd, '<F5>'): self.refresh(False) elif equal(cmd, '<C-LeftMouse>'): if self._getExplorer().supportsMulti(): self.addSelections() elif equal(cmd, '<S-LeftMouse>'): if self._getExplorer().supportsMulti(): self.selectMulti() elif equal(cmd, '<C-A>'): if self._getExplorer().supportsMulti(): self.selectAll() elif equal(cmd, '<C-L>'): self.clearSelections() else: if self._cmdExtension(cmd): break
class Manager(object): def __init__(self): self._buffer_name = vim.eval("expand('$VIMRUNTIME/LeaderF')") self._win_pos = int(vim.eval("g:Lf_WindowPosition")) self._autochdir = 0 self._cli = LfCli() self._explorer = None self._content = [] self._index = 0 self._help_length = 0 self._show_help = False self._selections = {} self._highlight_pos = [] self._highlight_refine_pos = [] self._highlight_ids = [] self._initStlVar() self._setStlMode() self._getExplClass() #************************************************************** # abstract methods, in fact all the functions can be overridden #************************************************************** def _getExplClass(self): """ this function MUST be overridden return the name of Explorer class """ raise NotImplementedError("Can't instantiate abstract class Manager " "with abstract methods _getExplClass") def _defineMaps(self): pass def _cmdExtension(self, cmd): """ this function can be overridden to add new cmd if return true, exit the input loop """ pass def _getDigest(self, line): """ this function can be overridden specify what part to match regex for a line in the match window """ return line def _createHelp(self): return [] #************************************************************** def _createHelpHint(self): help = [] if not self._show_help: help.append('" Press <F1> for help') help.append('" -------------------------------------------------' '--------') else: help += self._createHelp() self._help_length = len(help) vim.current.buffer.append(help, 0) vim.current.window.cursor = (self._help_length + 1, 0) def _hideHelp(self): # should be 'del vim.current.buffer[:self._help_length]', # but there is bug in vim7.3 for i in range(self._help_length): del vim.current.buffer[0] self._help_length = 0 def _getExplorer(self): if self._explorer is None: self._explorer = self._getExplClass()() return self._explorer def _initStlVar(self): vim.command("let g:Lf_statusline_function = '-'") vim.command("let g:Lf_statusline_curDir = '-'") vim.command("let g:Lf_statusline_total = '0'") def _setStlMode(self): if self._cli.isFuzzy: if self._cli.isFullPath: vim.command("let g:Lf_statusline_mode = 'FullPath'") else: vim.command("let g:Lf_statusline_mode = 'NameOnly'") else: vim.command("let g:Lf_statusline_mode = 'Regexp'") def _bufwinnr(self, name): nr = 1 for w in vim.windows: if (w.buffer.name is not None and os.path.abspath(w.buffer.name) == os.path.abspath(name)): return nr nr += 1 return 0 def _resetAutochdir(self): if int(vim.eval("&autochdir")) == 1: self._autochdir = 1 vim.command("set noautochdir") else: self._autochdir = 0 def _setAutochdir(self): if self._autochdir == 1: # When autochdir is set, Vim will change the current working directory # to the directory containing the file which was opened or selected. vim.command("set autochdir") def _gotoBuffer(self): self._resetAutochdir() self._orig_buffer = vim.current.buffer.name self._orig_win_nr = int(vim.eval("winnr()")) nr = self._bufwinnr(self._buffer_name) if nr == 0: self._createBufWindow() else: vim.command("exec '%d wincmd w'" % nr) self._setAttributes() self._setStatusline() self._defineMaps() self._cli.clear() self.clearSelections() self._clearHighlights() self._highlight_pos = [] self._highlight_refine_pos = [] def _createBufWindow(self): if self._win_pos == 0: vim.command("silent! noa keepj hide edit %s" % self._buffer_name) elif self._win_pos == 1: vim.command("silent! noa keepj bo sp %s" % self._buffer_name) elif self._win_pos == 2: vim.command("silent! noa keepj to sp %s" % self._buffer_name) else: vim.command("silent! noa keepj to vsp %s" % self._buffer_name) def _setAttributes(self): vim.command("setlocal nobuflisted") vim.command("setlocal buftype=nofile") vim.command("setlocal bufhidden=hide") vim.command("setlocal noswapfile") vim.command("setlocal nolist") vim.command("setlocal nonumber") vim.command("setlocal norelativenumber") vim.command("setlocal nospell") vim.command("setlocal nowrap") vim.command("setlocal nofoldenable") vim.command("setlocal foldcolumn=1") vim.command("setlocal cursorline") vim.command("setlocal filetype=leaderf") def _setStatusline(self): if vim.eval("exists('g:loaded_airline')") == '1': return vim.command("setlocal statusline=LeaderF:\ [%#Lf_hl_stlFunction#" "%{g:Lf_statusline_function}%#Lf_hl_none#,") vim.command("setlocal statusline+=\ %#Lf_hl_stlMode#%-9(" "%{g:Lf_statusline_mode}%#Lf_hl_none#]%)") vim.command("setlocal statusline+=\ \ %<%#Lf_hl_stlCurDir#" "%{g:Lf_statusline_curDir}%#Lf_hl_none#") vim.command("setlocal statusline+=%=%lL/%-5L\ \ Total:" "%{g:Lf_statusline_total}\ ") vim.command("redraw!") def _toUp(self): vim.command("norm! k") def _toDown(self): vim.command("norm! j") def _leftClick(self): nr = self._bufwinnr(self._buffer_name) if nr == int(vim.eval("v:mouse_win")): vim.command("exec v:mouse_lnum") vim.command("exec 'norm!'.v:mouse_col.'|'") self.clearSelections() else: self.quit() self._exit_loop = True def _search(self, content): self.clearSelections() self._cli.highlightMatches() cb = vim.current.buffer if not self._cli.pattern: # e.g., when <BS> or <Del> is typed setBuffer(cb, content) self._clearHighlights() return if self._cli.isFuzzy: self._fuzzySearch(content) else: self._regexSearch(content) def _filter(self, step, filter_method, content): """ Construct a list from result of filter_method(content). Args: step: An integer to indicate the number of lines to filter one time. filter_method: A function to apply `content` as parameter and return an iterable. content: The list to be filtered. """ length = len(content) cb = vim.current.buffer result = [] if self._index == 0: self._index = step if self._index < length: result.extend(filter_method(content[:self._index])) else: result.extend(filter_method(content)) else: result.extend(filter_method(cb[:])) if self._index < length: end = self._index + step - len(cb) result.extend(filter_method(content[self._index:end])) self._index = end while len(result) < 200 and self._index < length: end = self._index + 5000 result.extend(filter_method(content[self._index:end])) self._index = end return result def _fuzzyFilter(self, is_full_path, get_weight, iterable): """ return a list, each item is a pair (weight, line) """ getDigest = self._getDigest if is_full_path: pairs = ((get_weight(getDigest(line)), line) for line in iterable) else: pairs = ((get_weight(getBasename(getDigest(line))), line) for line in iterable) return (p for p in pairs if p[0]) def _refineFilter(self, first_get_weight, get_weight, iterable): getDigest = self._getDigest triples = ((first_get_weight(getBasename(getDigest(line))), get_weight(getDirname(getDigest(line))), line) for line in iterable) return ((i[0] + i[1], i[2]) for i in triples if i[0] and i[1]) def _fuzzySearch(self, content): encoding = vim.eval("&encoding") if self._cli.isRefinement: if self._cli.pattern[1] == '': # e.g. abc; fuzzy_match = FuzzyMatch(self._cli.pattern[0], encoding) filter_method = partial(self._fuzzyFilter, False, fuzzy_match.getWeight) highlight_method = partial(self._highlight, False, fuzzy_match.getHighlights) elif self._cli.pattern[0] == '': # e.g. ;abc fuzzy_match = FuzzyMatch(self._cli.pattern[1], encoding) filter_method = partial(self._fuzzyFilter, True, fuzzy_match.getWeight) highlight_method = partial(self._highlight, True, fuzzy_match.getHighlights) else: fuzzy_match0 = FuzzyMatch(self._cli.pattern[0], encoding) fuzzy_match1 = FuzzyMatch(self._cli.pattern[1], encoding) filter_method = partial(self._refineFilter, fuzzy_match0.getWeight, fuzzy_match1.getWeight) highlight_method = partial(self._highlightRefine, fuzzy_match0.getHighlights, fuzzy_match1.getHighlights) else: fuzzy_match = FuzzyMatch(self._cli.pattern, encoding) filter_method = partial(self._fuzzyFilter, self._cli.isFullPath, fuzzy_match.getWeight) highlight_method = partial(self._highlight, self._cli.isFullPath, fuzzy_match.getHighlights) pairs = self._filter(30000, filter_method, content) pairs.sort(key=operator.itemgetter(0), reverse=True) setBuffer(vim.current.buffer, [p[1] for p in pairs]) highlight_method() def _clearHighlights(self): for i in self._highlight_ids: vim.command("silent! call matchdelete(%d)" % i) self._highlight_ids = [] def _resetHighlights(self): self._clearHighlights() for i, pos in enumerate(self._highlight_pos, self._help_length + 1): pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int( vim.eval("matchaddpos('Lf_hl_match', %s)" % str(pos[j:j + 8]))) self._highlight_ids.append(id) for i, pos in enumerate(self._highlight_refine_pos, self._help_length + 1): pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int( vim.eval("matchaddpos('Lf_hl_match_refine', %s)" % str(pos[j:j + 8]))) self._highlight_ids.append(id) def _highlight(self, is_full_path, get_highlights): # matchaddpos() is introduced by Patch 7.4.330 if (vim.eval("exists('*matchaddpos')") == '0' or vim.eval("g:Lf_HighlightIndividual") == '0'): return cb = vim.current.buffer if len(cb) == 1 and cb[0] == '': # buffer is empty. return highlight_number = int(vim.eval("g:Lf_NumberOfHighlight")) self._clearHighlights() getDigest = self._getDigest if is_full_path: # e.g., self._highlight_pos = [ [ [2,3], [6,2] ], [ [1,4], [7,6], ... ], ... ] # where [2, 3] indicates the highlight starts at the 2nd column with the # length of 3 in bytes self._highlight_pos = [ get_highlights(getDigest(line)) for line in cb[:highlight_number] ] for i, pos in enumerate(self._highlight_pos, 1): pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int( vim.eval("matchaddpos('Lf_hl_match', %s)" % str(pos[j:j + 8]))) self._highlight_ids.append(id) else: self._highlight_pos = [ get_highlights(getBasename(getDigest(line))) for line in cb[:highlight_number] ] for i, pos in enumerate(self._highlight_pos, 1): dir_len = len(getDirname(cb[i - 1])) if dir_len > 0: for j in range(len(pos)): pos[j][0] += dir_len + 1 pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int( vim.eval("matchaddpos('Lf_hl_match', %s)" % str(pos[j:j + 8]))) self._highlight_ids.append(id) def _highlightRefine(self, first_get_highlights, get_highlights): # matchaddpos() is introduced by Patch 7.4.330 if (vim.eval("exists('*matchaddpos')") == '0' or vim.eval("g:Lf_HighlightIndividual") == '0'): return cb = vim.current.buffer if len(cb) == 1 and cb[0] == '': # buffer is empty. return highlight_number = int(vim.eval("g:Lf_NumberOfHighlight")) self._clearHighlights() getDigest = self._getDigest self._highlight_pos = [ first_get_highlights(getBasename(getDigest(line))) for line in cb[:highlight_number] ] for i, pos in enumerate(self._highlight_pos, 1): dir_len = len(getDirname(cb[i - 1])) if dir_len > 0: for j in range(len(pos)): pos[j][0] += dir_len + 1 pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int( vim.eval("matchaddpos('Lf_hl_match', %s)" % str(pos[j:j + 8]))) self._highlight_ids.append(id) self._highlight_refine_pos = [ get_highlights(getDirname(getDigest(line))) for line in cb[:highlight_number] ] for i, pos in enumerate(self._highlight_refine_pos, 1): pos = [[i] + p for p in pos] # The maximum number of positions is 8 in matchaddpos(). for j in range(0, len(pos), 8): id = int( vim.eval("matchaddpos('Lf_hl_match_refine', %s)" % str(pos[j:j + 8]))) self._highlight_ids.append(id) def _regexFilter(self, iterable): try: if ('-2' == vim.eval("g:LfNoErrMsgMatch('', '%s')" % escQuote(self._cli.pattern))): return iter([]) else: return (line for line in iterable if '-1' != vim.eval("g:LfNoErrMsgMatch('%s', '%s')" % (escQuote(self._getDigest(line).strip()), escQuote(self._cli.pattern)))) except vim.error: return iter([]) def _regexSearch(self, content): if not self._cli.isPrefix: self._index = 0 lines = self._filter(8000, self._regexFilter, content) setBuffer(vim.current.buffer, lines) def clearSelections(self): for i in self._selections.values(): vim.command("call matchdelete(%d)" % i) self._selections.clear() def toggleHelp(self): vim.command("setlocal modifiable") self._show_help = not self._show_help for i in range(self._help_length): del vim.current.buffer[0] self._createHelpHint() self.clearSelections() self._resetHighlights() vim.command("setlocal nomodifiable") def _accept(self, file, mode): try: if file: if mode == '': pass elif mode == 'h': vim.command("split") elif mode == 'v': vim.command("vsplit") elif mode == 't': vim.command("tabedit") tab_pos = int(vim.eval("g:Lf_TabpagePosition")) if tab_pos == 0: vim.command("tabm 0") elif tab_pos == 1: vim.command("tabm -1") elif tab_pos == 3: vim.command("tabm") self._getExplorer().acceptSelection(file) except (KeyboardInterrupt, vim.error): pass def accept(self, mode=''): if vim.current.window.cursor[0] <= self._help_length: vim.command("norm! j") return if len(self._selections) > 0: files = [] for i in self._selections: files.append(vim.current.buffer[i - 1]) self._quit() if mode == '': # It will raise E480 without 'silent!' vim.command("silent! argdelete *") for file in files: vim.command("argadd %s" % escSpecial(file)) self._accept(files[0], mode) else: for file in files: self._accept(file, mode) else: file = '' if vim.current.line == '' else vim.current.line self._quit() self._accept(file, mode) self._setAutochdir() def _quit(self): self._cli.clear() self.clearSelections() self._clearHighlights() if self._win_pos != 0 and len(vim.windows) > 1: vim.command("hide") # 'silent!' is used to skip error E16. vim.command("silent! exec '%d wincmd w'" % self._orig_win_nr) else: if self._orig_buffer is None or vim.eval( "bufexists('%s')" % escQuote(self._orig_buffer)) == '0': vim.command("bd") else: vim.command("hide edit %s" % escSpecial(self._orig_buffer)) if self._win_pos != 0: vim.command("call getchar(0) | redraw | echo") else: vim.command("call getchar(0)") def quit(self): self._quit() self._setAutochdir() def refresh(self, normal_mode=True): self._content = self._getExplorer().getFreshContent() if self._content is None: return if normal_mode: # when called in Normal mode vim.command("setlocal modifiable") setBuffer(vim.current.buffer, self._content) if self._cli.pattern: self._index = 0 self._search(self._content) if normal_mode: # when called in Normal mode self._createHelpHint() self._resetHighlights() vim.command("setlocal nomodifiable") def addSelections(self): nr = self._bufwinnr(self._buffer_name) if (int(vim.eval("v:mouse_win")) != 0 and nr != int(vim.eval("v:mouse_win"))): return elif nr == int(vim.eval("v:mouse_win")): vim.command("exec v:mouse_lnum") vim.command("exec 'norm!'.v:mouse_col.'|'") line_nr = vim.current.window.cursor[0] if line_nr <= self._help_length: vim.command("norm! j") return if line_nr in self._selections: vim.command("call matchdelete(%d)" % self._selections[line_nr]) del self._selections[line_nr] else: id = int( vim.eval("matchadd('Lf_hl_selection', '\%%%dl.')" % line_nr)) self._selections[line_nr] = id def selectMulti(self): orig_line = vim.current.window.cursor[0] nr = self._bufwinnr(self._buffer_name) if (int(vim.eval("v:mouse_win")) != 0 and nr != int(vim.eval("v:mouse_win"))): return elif nr == int(vim.eval("v:mouse_win")): cur_line = int(vim.eval("v:mouse_lnum")) self.clearSelections() for i in range(min(orig_line, cur_line), max(orig_line, cur_line) + 1): if i > self._help_length and i not in self._selections: id = int( vim.eval("matchadd('Lf_hl_selection', '\%%%dl.')" % (i))) self._selections[i] = id def selectAll(self): for i in range(len(vim.current.buffer)): if i >= self._help_length and i + 1 not in self._selections: id = int( vim.eval("matchadd('Lf_hl_selection', '\%%%dl.')" % (i + 1))) self._selections[i + 1] = id def startExplorer(self, *args, **kwargs): self._cli.setNameOnlyFeature(self._getExplorer().supportsNameOnly()) vim.command("let g:Lf_statusline_function = '%s'" % self._getExplorer().getStlFunction()) vim.command("echohl WarningMsg | redraw |" "echo ' searching ...' | echohl NONE") self._content = self._getExplorer().getContent(*args, **kwargs) if not self._content: vim.command("echohl Error | redraw | echo ' No content!'") return vim.command("let g:Lf_statusline_curDir = '%s'" % self._getExplorer().getStlCurDir()) vim.command("let g:Lf_statusline_total = '%d'" % len(self._content)) self._gotoBuffer() self.input(False) def input(self, normal_mode=True): vim.command("setlocal modifiable") self._hideHelp() self._resetHighlights() if not normal_mode: setBuffer(vim.current.buffer, self._content) self._index = 0 quit = False for cmd in self._cli.input(): if equal(cmd, '<Update>'): self._search(self._content) elif equal(cmd, '<Shorten>'): self._index = 0 # search from beginning self._search(self._content) elif equal(cmd, '<Mode>'): self._setStlMode() self._index = 0 # search from beginning if self._cli.pattern: self._search(self._content) elif equal(cmd, '<Up>'): self._toUp() elif equal(cmd, '<Down>'): self._toDown() elif equal(cmd, '<LeftMouse>'): self._exit_loop = False self._leftClick() if self._exit_loop: break elif equal(cmd, '<2-LeftMouse>'): self._leftClick() self.accept() break elif equal(cmd, '<CR>'): self.accept() break elif equal(cmd, '<C-X>'): self.accept('h') break elif equal(cmd, '<C-]>'): self.accept('v') break elif equal(cmd, '<C-T>'): self.accept('t') break elif equal(cmd, '<Quit>'): quit = True break elif equal(cmd, '<Esc>'): # switch to Normal mode self.clearSelections() self._cli.hideCursor() self._createHelpHint() self._resetHighlights() vim.command("setlocal nomodifiable") break elif equal(cmd, '<F5>'): self.refresh(False) elif equal(cmd, '<C-LeftMouse>'): if self._getExplorer().supportsMulti(): self.addSelections() elif equal(cmd, '<S-LeftMouse>'): if self._getExplorer().supportsMulti(): self.selectMulti() elif equal(cmd, '<C-A>'): if self._getExplorer().supportsMulti(): self.selectAll() elif equal(cmd, '<C-L>'): self.clearSelections() else: if self._cmdExtension(cmd): break if quit: # due to a bug which is fixed by Patch 7.4.084, I have to write this ugly code self.quit()
class Manager(object): def __init__(self): self._bufName = vim.eval("expand('$VIMRUNTIME/[LeaderF]')") self._winPos = int(vim.eval("g:Lf_WindowPosition")) self._winHeight = int(vim.eval("g:Lf_WindowHeight")) self._maxLines = int(vim.eval("g:Lf_MaxLines")) self._autochdir = 0 self._cli = LfCli() self._explorer = None self._index = 0 self._helpLength = 0 self._showHelp = False self._selections = {} self._initStlVar() self._setStlMode() self._getExplClass() #***************************************************** # abstract methods, in fact all the functions can be overridden #***************************************************** def _getExplClass(self): ''' this function MUST be overridden return the name of Explorer class ''' raise NotImplementedError( "Can't instantiate abstract class Manager with abstract methods _getExplClass" ) def _defineMaps(self): pass def _cmdExtension(self, cmd): ''' this function can be overridden to add new cmd if return true, exit the input loop ''' pass def _getDigest(self, line): ''' this function can be overridden specify what part to match regex for a line in the match window ''' return line def _createHelp(self): return [] #***************************************************** # private methods #***************************************************** def _createHelpHint(self): help = [] if not self._showHelp: help.append('" Press <F1> for help') help.append( '" ---------------------------------------------------') else: help += self._createHelp() self._helpLength = len(help) vim.command("setlocal modifiable") vim.current.buffer.append(help, 0) vim.current.window.cursor = (self._helpLength + 1, 0) vim.command("setlocal nomodifiable") def _hideHelp(self): for i in range(self._helpLength): del vim.current.buffer[0] self._helpLength = 0 def _getExplorer(self): if self._explorer is None: self._explorer = self._getExplClass()() return self._explorer def _initStlVar(self): vim.command("let g:Lf_statusline_function = '-'") vim.command("let g:Lf_statusline_curDir = '-'") vim.command("let g:Lf_statusline_total = '0'") def _setStlMode(self): if self._cli.isFuzzy: if self._cli.isFileNameOnly: vim.command("let g:Lf_statusline_mode = 'NameOnly'") else: vim.command("let g:Lf_statusline_mode = 'FullPath'") else: vim.command("let g:Lf_statusline_mode = 'Regexp'") def _bufwinnr(self, name): nr = 1 for w in vim.windows: if w.buffer.name is not None and os.path.abspath( w.buffer.name) == os.path.abspath(name): return nr nr += 1 return 0 def _gotoBuffer(self): if int(vim.eval("&autochdir")) == 1: self._autochdir = 1 vim.command("set noautochdir") else: self._autochdir = 0 self._origBuf = vim.current.buffer.name self._origWinNr = int(vim.eval("winnr()")) nr = self._bufwinnr(self._bufName) if nr == 0: self._createBufWindow() else: vim.command("exec '%d wincmd w'" % nr) self._setAttributes() self._setStatusline() self._defineMaps() def _createBufWindow(self): if self._winPos == 0: vim.command("silent! noa keepj hide edit %s" % self._bufName) elif self._winPos == 1: vim.command("silent! noa keepj bo %d sp %s" % (self._winHeight, self._bufName)) elif self._winPos == 2: vim.command("silent! noa keepj to %d sp %s" % (self._winHeight, self._bufName)) else: vim.command("silent! noa keepj to %d vsp %s" % (self._winHeight, self._bufName)) def _setAttributes(self): vim.command("setlocal nobuflisted") vim.command("setlocal buftype=nofile") vim.command("setlocal bufhidden=hide") vim.command("setlocal noswapfile") vim.command("setlocal nolist") vim.command("setlocal nonumber") vim.command("setlocal nospell") vim.command("setlocal nowrap") vim.command("setlocal nofoldenable") vim.command("setlocal foldcolumn=1") vim.command("setlocal cursorline") vim.command("setlocal filetype=LeaderF") def _setStatusline(self): vim.command( "setlocal statusline=LeaderF:\ [%#Lf_hl_stlFunction#%{g:Lf_statusline_function}%#Lf_hl_none#," ) vim.command( "setlocal statusline+=\ %#Lf_hl_stlMode#%-9(%{g:Lf_statusline_mode}%#Lf_hl_none#]%)" ) vim.command( "setlocal statusline+=\ \ %<%#Lf_hl_stlCurDir#%{g:Lf_statusline_curDir}%#Lf_hl_none#" ) vim.command( "setlocal statusline+=%=%lL/%-5L\ \ Total:%{g:Lf_statusline_total}\ " ) vim.command("redraw!") def _toUp(self): vim.command("norm! k") def _toDown(self): vim.command("norm! j") def _leftClick(self): nr = self._bufwinnr(self._bufName) if nr == int(vim.eval("v:mouse_win")): vim.command("exec v:mouse_lnum") vim.command("exec 'norm!'.v:mouse_col.'|'") self.clearSelections() else: self.quit() self._exitLoop = True def _filter(self, iterable, regex): if self._cli.isFuzzy: if self._cli.isFileNameOnly: if self._cli.isMixed: if regex[0] == '': return [ line for line in iterable if re.search(regex[1], self._getDigest(line), re.I) ] elif regex[1] == '': return [ line for line in iterable if re.search( regex[0], os.path.basename(self._getDigest(line)), re.I) ] else: iterable = [ line for line in iterable if re.search( regex[0], os.path.basename(self._getDigest(line)), re.I) ] return [ line for line in iterable if re.search( regex[1], os.path.dirname(self._getDigest( line)), re.I) ] else: return [ line for line in iterable if re.search( regex, os.path.basename(self._getDigest(line)), re.I) ] else: return [ line for line in iterable if re.search(regex, self._getDigest(line), re.I) ] else: try: if '-2' == vim.eval("g:LfNoErrMsgMatch('', '%s')" % escQuote(regex)): return [] else: return [ line for line in iterable if '-1' != vim.eval("g:LfNoErrMsgMatch('%s', '%s')" % (escQuote(self._getDigest(line).strip()), escQuote(regex))) ] except vim.error: pass def _isPrefix(self, regex): #assume that there are no \%23l, \%23c, \%23v, \%... pos = self._cli.cursorPos if pos > 1: if regex[pos - 2] != '\\' and (regex[pos - 1].isalnum() or regex[pos - 1] in r'`$%*(-_+[\;:,. /?'): if regex[pos - 2] == '_': if pos > 2 and regex[pos - 3] == '\\': #\_x return False else: return True if regex.endswith(r'\zs') or regex.endswith(r'\ze'): return False return True return False def _search(self, content, regex): self.clearSelections() self._cli.highlightMatches() cb = vim.current.buffer if not regex: if self._maxLines > 0: setBuffer(cb, content[:self._maxLines]) else: setBuffer(cb, content) return if not self._cli.isFuzzy: if not self._isPrefix(regex): self._index = 0 step = int(vim.eval("g:Lf_SearchStep")) length = len(content) if self._index == 0: self._index = step if self._index < length: setBuffer(cb, self._filter(content[:self._index], regex)) else: setBuffer(cb, self._filter(content, regex)) else: setBuffer(cb, self._filter(cb[:], regex)) while len(cb) < 1000 and self._index < length: end = self._index + step startLine = 0 if cb[0] == '' else len(cb) if end < length: appendBuffer(cb, self._filter(content[self._index:end], regex), startLine) else: appendBuffer(cb, self._filter(content[self._index:], regex), startLine) self._index = end if len(cb) > vim.current.window.height * 2: break if self._getExplorer().supportsSort(): num = int(vim.eval("g:Lf_NumberOfSort")) if num == -1: self._sortResult(len(cb)) elif num == 0: pass elif num == 1: self._sortResult(vim.current.window.height) else: self._sortResult(num) vim.current.window.cursor = (self._helpLength + 1, 0) def _getWeight(self, str, t): ''' this function can be overridden to get the weight of the line, so that it can be applied in the sort algorithm ''' pre = str.lower().index(t[0].lower()) s = str[pre:] sl = len(s) tl = len(t) val = [[0 for col in range(tl)] for row in range(sl)] val[0][0] = 1 JJ = 2 for i in range(1, sl): J = JJ for j in range(min(J, tl)): if s[i].lower() != t[j].lower(): val[i][j] = val[i - 1][j] else: if j == JJ - 1: JJ += 1 if val[i - 1][j] == (j + 1) * (j + 1): # prune val[i][j] = (j + 1) * (j + 1) continue val[i][j] = val[i - 1][j] ii = i jj = j k = 0 while ii >= 0 and jj >= 0: if s[ii].lower() == t[jj].lower(): if i == j and val[i - 1][j - 1] == i * i: # prune val[i][j] = (i + 1) * (i + 1) break k += 1 if j - k >= 0: if val[i - k][j - k] + k * k > val[i][j]: val[i][j] = val[i - k][j - k] + k * k else: val[i][j] = k * k ii -= 1 jj -= 1 else: break if val[i][j] == tl * tl: # prune return val[i][j] + 1.0 / (2 * pre + len(str)) return val[sl - 1][tl - 1] + 1.0 / (2 * pre + len(str)) def _sortResult(self, num): ''' this function can be overridden to customize the sort algorithm ''' cb = vim.current.buffer if len(cb) == 1 and cb[0] == '' or num == 0: return if self._cli.isFuzzy: if self._cli.isFileNameOnly: if self._cli.isMixed: pass else: pairs = [(i, self._getWeight( os.path.basename(self._getDigest(cb[i])), self._cli.cmdline)) for i in range(len(cb))] pairs = heapq.nlargest(num, pairs, key=lambda x: x[1]) lines = [cb[i[0]] for i in pairs] append(cb, lines, 0) pairs.sort(key=lambda x: x[0], reverse=True) for i in pairs: del cb[i[0] + len(pairs)] else: pass def _accept(self, file, mode): try: if file: if mode == '': pass elif mode == 'h': vim.command("split") elif mode == 'v': vim.command("vsplit") elif mode == 't': vim.command("tabedit") tabPos = int(vim.eval("g:Lf_TabpagePosition")) if tabPos == 0: vim.command("tabm 0") elif tabPos == 1: vim.command("tabm -1") elif tabPos == 3: vim.command("tabm") self._getExplorer().acceptSelection(file) except (KeyboardInterrupt, vim.error): pass def clearSelections(self): for i in self._selections.values(): vim.command("call matchdelete(%d)" % i) self._selections.clear() #***************************************************** # public methods #***************************************************** def toggleHelp(self): vim.command("setlocal modifiable") self._showHelp = not self._showHelp for i in range(self._helpLength): del vim.current.buffer[0] self._createHelpHint() self.clearSelections() vim.command("setlocal nomodifiable") def accept(self, mode=''): if vim.current.window.cursor[0] <= self._helpLength: vim.command("norm! j") return if len(self._selections) > 0: files = [] for i in self._selections.keys(): files.append(os.path.abspath(vim.current.buffer[i - 1])) self.quit() if mode == '': vim.command("argdelete *") for file in files: vim.command("argadd %s" % escSpecial(file)) self._accept(files[0], mode) else: for file in files: self._accept(file, mode) else: file = os.path.abspath(vim.current.line) self.quit() self._accept(file, mode) def quit(self): self._cli.clear() self.clearSelections() if self._winPos != 0 and len(vim.windows) > 1: vim.command("hide") vim.command("exec '%d wincmd w'" % self._origWinNr) else: if self._origBuf is None or vim.eval( "bufexists('%s')" % escQuote(self._origBuf)) == '0': vim.command("bd") else: vim.command("hide edit %s" % escSpecial(self._origBuf)) if self._winPos != 0: vim.command("call getchar(0) | redraw | echo") else: vim.command("call getchar(0)") if self._autochdir == 1: cwd = os.getcwd() vim.command("set autochdir") # I think vim has a bug here os.chdir(cwd) def refresh(self, content=None): rContent = self._getExplorer().getFreshContent() if rContent is None: return if content is None: vim.command("setlocal modifiable") setBuffer(vim.current.buffer, rContent) self._content = rContent if self._cli.regex: self._index = 0 self._search(rContent, self._cli.regex) vim.command("setlocal nomodifiable") else: setBuffer(vim.current.buffer, rContent) content[:] = rContent #use slice to change content if self._cli.regex: self._index = 0 self._search(rContent, self._cli.regex) def addSelections(self): nr = self._bufwinnr(self._bufName) if int(vim.eval("v:mouse_win")) != 0 and nr != int( vim.eval("v:mouse_win")): return elif nr == int(vim.eval("v:mouse_win")): vim.command("exec v:mouse_lnum") vim.command("exec 'norm!'.v:mouse_col.'|'") lineNr = vim.current.window.cursor[0] if lineNr <= self._helpLength: vim.command("norm! j") return if lineNr in self._selections: vim.command("call matchdelete(%d)" % self._selections[lineNr]) del self._selections[lineNr] else: id = int( vim.eval("matchadd('Lf_hl_selection', '\%%%dl.')" % lineNr)) self._selections[lineNr] = id if int(vim.eval("v:mouse_win")) != 0: vim.command( 'call feedkeys("\<C-@>")') #vim has bug, so add this line def selectMulti(self): origLine = vim.current.window.cursor[0] nr = self._bufwinnr(self._bufName) if int(vim.eval("v:mouse_win")) != 0 and nr != int( vim.eval("v:mouse_win")): return elif nr == int(vim.eval("v:mouse_win")): vim.command("exec v:mouse_lnum") vim.command("exec 'norm!'.v:mouse_col.'|'") self.clearSelections() curLine = vim.current.window.cursor[0] for i in range(min(origLine, curLine), max(origLine, curLine) + 1): if i > self._helpLength and i not in self._selections: id = int( vim.eval("matchadd('Lf_hl_selection', '\%%%dl.')" % (i))) self._selections[i] = id vim.command('call feedkeys("\<C-@>")') #vim has bug, so add this line def selectAll(self): for i in range(len(vim.current.buffer)): if i >= self._helpLength and i + 1 not in self._selections: id = int( vim.eval("matchadd('Lf_hl_selection', '\%%%dl.')" % (i + 1))) self._selections[i + 1] = id def startExplorer(self, *args, **kwargs): self._cli.setFullPathFeature(self._getExplorer().supportsFullPath()) vim.command("let g:Lf_statusline_function = '%s'" % self._getExplorer().getStlFunction()) vim.command( "echohl WarningMsg | redraw | echo ' searching ...' | echohl NONE") content = self._getExplorer().getContent(*args, **kwargs) if content is None: return self._gotoBuffer() vim.command("let g:Lf_statusline_curDir = '%s'" % self._getExplorer().getStlCurDir()) vim.command("let g:Lf_statusline_total = '%d'" % len(content)) self.startExplAction(content) def startExplAction(self, content=None): vim.command("setlocal modifiable") self._hideHelp() if content is None: content = self._content else: if self._maxLines > 0: setBuffer(vim.current.buffer, content[:self._maxLines]) else: setBuffer(vim.current.buffer, content) self._index = 0 quit = False for cmd in self._cli.input(): if equal(cmd, '<Update>'): self._search(content, self._cli.regex) elif equal(cmd, '<Shorten>'): self._index = 0 self._search(content, self._cli.regex) elif equal(cmd, '<Mode>'): self._setStlMode() self._index = 0 if self._cli.regex: self._search(content, self._cli.regex) elif equal(cmd, '<Up>'): self._toUp() elif equal(cmd, '<Down>'): self._toDown() elif equal(cmd, '<LeftMouse>'): self._exitLoop = False self._leftClick() if self._exitLoop: break elif equal(cmd, '<2-LeftMouse>'): self._leftClick() self.accept() break elif equal(cmd, '<CR>'): self.accept() break elif equal(cmd, '<C-X>'): self.accept('h') break elif equal(cmd, '<C-]>'): self.accept('v') break elif equal(cmd, '<C-T>'): self.accept('t') break elif equal(cmd, '<Quit>'): quit = True break elif equal(cmd, '<Esc>'): self.clearSelections() self._cli.hideCursor() vim.command("setlocal nomodifiable") self._content = content self._createHelpHint() elif equal(cmd, '<F5>'): self.refresh(content) elif equal(cmd, '<C-LeftMouse>'): if self._getExplorer().supportsMulti(): self.addSelections() elif equal(cmd, '<S-LeftMouse>'): if self._getExplorer().supportsMulti(): self.selectMulti() elif equal(cmd, '<C-A>'): if self._getExplorer().supportsMulti(): self.selectAll() elif equal(cmd, '<C-L>'): self.clearSelections() else: if self._cmdExtension(cmd): break if quit: #due to a bug, I have to write this ugly code self.quit()