diff --git a/README.md b/README.md index 3e570b3b..b0cdc1ac 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ An efficient fuzzy finder that helps to locate files, buffers, mrus, gtags, etc. - Written in Python. - Support fuzzy and regex searching. - Full-featured. - - [Well-designed fuzzy matching algorithm](https://github.com/Yggdroot/testFuzzyMatch). + - Well-designed fuzzy matching algorithm. - [Extensible](https://github.com/Yggdroot/LeaderF/wiki/Extensions). Changelog @@ -36,7 +36,7 @@ Requirements - vim7.3 or higher. Only support 7.4.1126+ after [v1.01](https://github.com/Yggdroot/LeaderF/releases/tag/v1.01). - Python2.7+ or Python3.1+. - - To use the popup mode, neovim 0.42+ or vim 8.1.1615+ are required. + - To use the popup mode, neovim 0.5.0+ or vim 8.1.1615+ are required. Installation ------------ @@ -75,7 +75,8 @@ Usage usage: Leaderf[!] [-h] [--reverse] [--stayOpen] [--input | --cword] [--top | --bottom | --left | --right | --belowright | --aboveleft | --fullScreen | --popup] [--nameOnly | --fullPath | --fuzzy | --regexMode] [--nowrap] [--next | --previous] - [--recall] [--popup-height ] [--popup-width ] + [--recall] [--popup-height ] [--popup-width ] [--no-sort] + [--case-insensitive] [--auto-preview | --no-auto-preview] {file,tag,function,mru,searchHistory,cmdHistory,help,line,colorscheme,gtags, self,bufTag,buffer,rg,filetype,command,window,quickfix,loclist,jumps} @@ -108,6 +109,9 @@ optional arguments: --popup-width specifies the width of popup window, only available in popup mode. --no-sort do not sort the result. + --case-insensitive fuzzy search case insensitively. + --auto-preview open preview window automatically. + --no-auto-preview don't open preview window automatically. subcommands: @@ -231,12 +235,6 @@ or add `--popup` after each subcommand, e.g., Leaderf file --popup ``` -It's better to set -```vim -let g:Lf_PreviewInPopup = 1 -``` -, so that you can also preview the result in a popup window. - Customization ------------- @@ -293,7 +291,7 @@ Customization " Show icons, icons are shown by default let g:Lf_ShowDevIcons = 1 " For GUI vim, the icon font can be specify like this, for example - let g:Lf_DevIconsFont = "DroidSansMono Nerd Font Mono" + let g:Lf_DevIconsFont = "DroidSansM Nerd Font Mono" " If needs set ambiwidth=double ``` @@ -310,7 +308,6 @@ let g:Lf_UseVersionControlTool = 0 let g:Lf_IgnoreCurrentBufferName = 1 " popup mode let g:Lf_WindowPosition = 'popup' -let g:Lf_PreviewInPopup = 1 let g:Lf_StlSeparator = { 'left': "\ue0b0", 'right': "\ue0b2", 'font': "DejaVu Sans Mono for Powerline" } let g:Lf_PreviewResult = {'Function': 0, 'BufTag': 0 } diff --git a/autoload/leaderf.vim b/autoload/leaderf.vim index aaa5996d..88927da5 100644 --- a/autoload/leaderf.vim +++ b/autoload/leaderf.vim @@ -11,9 +11,11 @@ if !exists("g:Lf_PythonVersion") if has("python3") let g:Lf_PythonVersion = 3 let g:Lf_py = "py3 " + let g:Lf_PyEval = function("py3eval") elseif has("python") let g:Lf_PythonVersion = 2 let g:Lf_py = "py " + let g:Lf_PyEval = function("pyeval") else echoe "Error: LeaderF requires vim compiled with +python or +python3" finish @@ -22,6 +24,7 @@ else if g:Lf_PythonVersion == 2 if has("python") let g:Lf_py = "py " + let g:Lf_PyEval = function("pyeval") else echoe 'LeaderF Error: has("python") == 0' finish @@ -29,6 +32,7 @@ else else if has("python3") let g:Lf_py = "py3 " + let g:Lf_PyEval = function("py3eval") else echoe 'LeaderF Error: has("python3") == 0' finish @@ -67,10 +71,10 @@ function! s:InitDict(var, dict) endfunction call s:InitVar('g:Lf_WindowHeight', '0.5') -call s:InitVar('g:Lf_TabpagePosition', 2) +call s:InitVar('g:Lf_TabpagePosition', 3) call s:InitVar('g:Lf_ShowRelativePath', 1) call s:InitVar('g:Lf_DefaultMode', 'FullPath') -call s:InitVar('g:Lf_CursorBlink', 1) +call s:InitVar('g:Lf_CursorBlink', 0) call s:InitVar('g:Lf_NeedCacheTime', '1.5') call s:InitVar('g:Lf_NumberOfCache', 5) call s:InitVar('g:Lf_UseMemoryCache', 1) @@ -117,14 +121,15 @@ call s:InitVar('g:Lf_WorkingDirectoryMode', 'c') call s:InitVar('g:Lf_WorkingDirectory', '') call s:InitVar('g:Lf_ShowHidden', 0) call s:InitDict('g:Lf_PreviewResult', { - \ 'File': 0, - \ 'Buffer': 0, - \ 'Mru': 0, - \ 'Tag': 0, + \ 'File': 1, + \ 'Buffer': 1, + \ 'Mru': 1, + \ 'Tag': 1, \ 'BufTag': 1, \ 'Function': 1, - \ 'Line': 0, + \ 'Line': 1, \ 'Colorscheme': 0, + \ 'Rg': 1, \ 'Jumps': 1 \}) call s:InitDict('g:Lf_NormalMap', {}) @@ -137,6 +142,16 @@ call s:InitDict('g:Lf_GtagsfilesCmd', { \ 'default': 'rg --no-messages --files' \}) call s:InitVar('g:Lf_HistoryEditPromptIfEmpty', 1) +call s:InitVar('g:Lf_PopupBorders', ["─","│","─","│","╭","╮","╯","╰"]) +call s:InitVar('g:Lf_GitFolderIcons', { + \ 'open': '', + \ 'closed': '', + \}) +call s:InitVar('g:Lf_GitKeyMap', { + \ 'previous_change': '[c', + \ 'next_change': ']c', + \ 'edit_file': '', + \}) let s:Lf_CommandMap = { \ '': [''], @@ -326,6 +341,17 @@ endfunction call s:InitCommandMap('g:Lf_CommandMap', s:Lf_CommandMap) +function! leaderf#execute(cmd) + if exists('*execute') + return execute(a:cmd) + else + redir => l:output + silent! execute a:cmd + redir END + return l:output + endif +endfunction + function! leaderf#versionCheck() if g:Lf_PythonVersion == 2 && pyeval("sys.version_info < (2, 7)") echohl Error @@ -363,21 +389,13 @@ function! leaderf#visual() abort endfunction function! leaderf#popupModePreviewFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) - if key ==? "" - noautocmd call popup_close(a:winid) - redraw - return 0 - elseif key ==? "" - noautocmd call popup_close(a:winid) - " https://github.com/vim/vim/issues/5216 - "redraw - return 0 - elseif key ==? "" + let key = get(g:Lf_KeyMap, a:key, a:key) + if key ==? "" if exists("*getmousepos") let pos = getmousepos() if pos.winid == a:winid call win_execute(pos.winid, "call cursor([pos.line, pos.column])") + redraw return 1 endif elseif has('patch-8.1.2266') @@ -403,58 +421,20 @@ function! leaderf#popupModePreviewFilter(winid, key) abort return 1 endif endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - redraw - return 1 - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - redraw - return 1 - endif - return 0 -endfunction - -function! leaderf#normalModePreviewFilter(id, winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) - if key ==? "" - noautocmd call popup_close(a:winid) - redraw - return 1 - elseif key ==? "" - noautocmd call popup_close(a:winid) - " https://github.com/vim/vim/issues/5216 - "redraw - return 0 - elseif key ==? "" && has('patch-8.1.2266') + elseif key ==? "" && exists("*getmousepos") let pos = getmousepos() if pos.winid == a:winid - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") + call win_execute(a:winid, "norm! 3\") + redraw return 1 - else - noautocmd call popup_close(a:winid) + endif + elseif key ==? "" && exists("*getmousepos") + let pos = getmousepos() + if pos.winid == a:winid + call win_execute(a:winid, "norm! 3\") redraw - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "import ctypes" - exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._previewResult(False)", a:id) return 1 endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - redraw - return 1 - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - redraw - return 1 - elseif key ==? "" - call win_execute(a:winid, "norm! k") - redraw - return 1 - elseif key ==? "" - call win_execute(a:winid, "norm! j") - redraw - return 1 endif return 0 endfunction @@ -463,23 +443,63 @@ function! leaderf#PopupFilter(winid, key) abort return 0 endfunction +function! leaderf#RemapKey(id, key) abort + exec g:Lf_py "import ctypes" + + let normal_map = get(g:, 'Lf_NormalCommandMap', {}) + let key_map = get(normal_map, '*', {}) + let category = g:Lf_PyEval(printf("ctypes.cast(%d, ctypes.py_object).value._getExplorer().getStlCategory()", a:id)) + for [old, new] in items(get(normal_map, category, {})) + let has_key = 0 + for [k, v] in items(key_map) + if old =~ '\m<.\{-}>' && old ==? k + let key_map[k] = new + let has_key = 1 + break + endif + endfor + if has_key == 0 + let key_map[old] = new + endif + endfor + + let key = a:key + let is_old = 0 + let is_new = 0 + for [old, new] in items(key_map) + if key =~ '\m<.\{-}>' && key ==? new || key ==# new + let key = old + let is_new = 1 + endif + if key =~ '\m<.\{-}>' && key ==? old || key ==# old + let is_old = 1 + endif + endfor + + if is_old && is_new == 0 + let key = '' + endif + + return key +endfunction + function! leaderf#NormalModeFilter(id, winid, key) abort exec g:Lf_py "import ctypes" - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) + let key = leaderf#RemapKey(a:id, get(g:Lf_KeyMap, a:key, a:key)) if key !=# "g" call win_execute(a:winid, printf("let g:Lf_%d_is_g_pressed = 0", a:id)) endif if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.move('j')", a:id) exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._cli._buildPopupPrompt()", a:id) "redraw exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._getInstance().refreshPopupStatusline()", a:id) exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._previewResult(False)", a:id) elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.move('k')", a:id) exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._cli._buildPopupPrompt()", a:id) "redraw exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._getInstance().refreshPopupStatusline()", a:id) @@ -518,10 +538,15 @@ function! leaderf#NormalModeFilter(id, winid, key) abort elseif key ==? "" if exists("*getmousepos") let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._cli._buildPopupPrompt()", a:id) - redraw - exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._previewResult(False)", a:id) + if pos.winid == a:winid + call win_execute(pos.winid, "call cursor([pos.line, pos.column])") + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._cli._buildPopupPrompt()", a:id) + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._previewResult(False)", a:id) + redraw + return 1 + else + return 0 + endif elseif has('patch-8.1.2266') call win_execute(a:winid, "exec v:mouse_lnum") call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") @@ -529,17 +554,31 @@ function! leaderf#NormalModeFilter(id, winid, key) abort redraw exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._previewResult(False)", a:id) endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._cli._buildPopupPrompt()", a:id) - redraw - exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._getInstance().refreshPopupStatusline()", a:id) - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._cli._buildPopupPrompt()", a:id) - redraw - exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._getInstance().refreshPopupStatusline()", a:id) - elseif key ==# "q" || key ==? "" + elseif key ==? "" && exists("*getmousepos") + let pos = getmousepos() + if pos.winid == a:winid + call win_execute(a:winid, "norm! 3\") + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._cli._buildPopupPrompt()", a:id) + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._getInstance().refreshPopupStatusline()", a:id) + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._previewResult(False)", a:id) + redraw + return 1 + else + return 0 + endif + elseif key ==? "" && exists("*getmousepos") + let pos = getmousepos() + if pos.winid == a:winid + call win_execute(a:winid, "norm! 3\") + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._cli._buildPopupPrompt()", a:id) + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._getInstance().refreshPopupStatusline()", a:id) + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._previewResult(False)", a:id) + redraw + return 1 + else + return 0 + endif + elseif key ==# "q" exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.quit()", a:id) elseif key ==# "i" || key ==? "" call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') @@ -552,12 +591,6 @@ function! leaderf#NormalModeFilter(id, winid, key) abort exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.accept('v')", a:id) elseif key ==# "t" exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.accept('t')", a:id) - elseif key ==# "s" - exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.addSelections()", a:id) - elseif key ==# "a" - exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.selectAll()", a:id) - elseif key ==# "c" - exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.clearSelections()", a:id) elseif key ==# "p" exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._previewResult(True)", a:id) elseif key ==? "" @@ -566,6 +599,8 @@ function! leaderf#NormalModeFilter(id, winid, key) abort exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._toUpInPopup()", a:id) elseif key ==? "" exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._toDownInPopup()", a:id) + elseif key ==? "" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.closePreviewPopupOrQuit()", a:id) endif return 1 @@ -575,6 +610,7 @@ function! leaderf#PopupClosed(id_list, manager_id, winid, result) abort " result is -1 if CTRL-C was pressed, if a:result == -1 exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.is_ctrl_c = True", a:manager_id) exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.quit()", a:manager_id) for id in a:id_list if id != a:winid @@ -584,6 +620,18 @@ function! leaderf#PopupClosed(id_list, manager_id, winid, result) abort endif endfunction +function! leaderf#Quit(manager_id) abort +exec g:Lf_py "<< EOF" +import ctypes +manager = ctypes.cast(int(vim.eval("a:manager_id")), ctypes.py_object).value +if manager.is_ctrl_c == False: + manager.is_autocmd = True + manager.quit() + manager.is_autocmd = False +manager.is_ctrl_c = False +EOF +endfunction + function! leaderf#ResetPopupOptions(winid, option, value) abort let opts = popup_getoptions(a:winid) " https://github.com/vim/vim/issues/5081 diff --git a/autoload/leaderf/Any.vim b/autoload/leaderf/Any.vim index cec62d01..72ff9e62 100644 --- a/autoload/leaderf/Any.vim +++ b/autoload/leaderf/Any.vim @@ -27,6 +27,16 @@ function! leaderf#Any#Maps(category) nnoremap i :exec g:Lf_py b:Lf_AnyExplManager."input()" nnoremap :exec g:Lf_py b:Lf_AnyExplManager."input()" nnoremap :exec g:Lf_py b:Lf_AnyExplManager."toggleHelp()" + nnoremap p :exec g:Lf_py b:Lf_AnyExplManager."_previewResult(True)" + nnoremap j :exec g:Lf_py b:Lf_AnyExplManager."moveAndPreview('j')" + nnoremap k :exec g:Lf_py b:Lf_AnyExplManager."moveAndPreview('k')" + nnoremap :exec g:Lf_py b:Lf_AnyExplManager."moveAndPreview('Up')" + nnoremap :exec g:Lf_py b:Lf_AnyExplManager."moveAndPreview('Down')" + nnoremap :exec g:Lf_py b:Lf_AnyExplManager."moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py b:Lf_AnyExplManager."moveAndPreview('PageDown')" + nnoremap :exec g:Lf_py b:Lf_AnyExplManager."_toUpInPopup()" + nnoremap :exec g:Lf_py b:Lf_AnyExplManager."_toDownInPopup()" + nnoremap :exec g:Lf_py b:Lf_AnyExplManager."closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, a:category) for i in g:Lf_NormalMap[a:category] exec 'nnoremap '.i[0].' '.i[1] @@ -55,12 +65,18 @@ let g:Lf_Helps = { \ "quickfix": "navigate quickfix", \ "loclist": "navigate location list", \ "jumps": "navigate jumps list", + \ "git": "use git", + \ "git-status": "show git status", + \ "git-log": "show the commit logs", + \ "git-diff": "show changes between commits, commit and working tree, etc", + \ "git-blame": "show what revision and author last modified each line of a file", + \ "coc": "execute coc's commands", \ } let g:Lf_Arguments = { \ "file":[ \ [ - \ {"name": ["directory"], "nargs": "?", "help": "serarch files under "}, + \ {"name": ["directory"], "nargs": "*", "help": "serarch files under "}, \ {"name": ["--file"], "nargs": "+", "help": "read file list from the specified file."}, \ ], \ {"name": ["--no-ignore"], "nargs": 0, "help": "don't respect ignore files (.gitignore, .ignore, etc.)."}, @@ -70,8 +86,13 @@ let g:Lf_Arguments = { \ {"name": ["--tabpage"], "nargs": 0, "help": "search buffers in current tabpage"}, \ ], \ "mru":[ - \ {"name": ["--cwd"], "nargs": 0, "help": "search MRU in current working directory"}, + \ [ + \ {"name": ["--cwd"], "nargs": 0, "help": "search MRU in current working directory"}, + \ {"name": ["--project"], "nargs": 0, "help": "search MRU in the project"}, + \ ], \ {"name": ["--no-split-path"], "nargs": 0, "help": "do not split the path"}, + \ {"name": ["--absolute-path"], "nargs": 0, "help": "show absolute path"}, + \ {"name": ["--frecency"], "nargs": 0, "help": "enable the frecency algorithm"}, \ ], \ "tag":[], \ "bufTag":[ @@ -93,9 +114,13 @@ let g:Lf_Arguments = { \ {"name": ["-B", "--before-context"], "nargs": 1, "metavar": "", "help": "Show NUM lines before each match."}, \ {"name": ["-C", "--context"], "nargs": 1, "metavar": "", "help": "Show NUM lines before and after each match."}, \ {"name": ["--context-separator"], "nargs": 1, "metavar": "", "help": "The string used to separate non-contiguous context lines in the output."}, + \ {"name": ["--crlf"], "nargs": 0, "help": "ripgrep will treat CRLF ('\r\n') as a line terminator instead of just '\n'."}, \ {"name": ["-e", "--regexp"], "action": "append", "metavar": "...", \ "help": "A pattern to search for. This option can be provided multiple times, where all patterns given are searched."}, - \ {"name": ["-F", "--fixed-strings"], "nargs": 0, "help": "Treat the pattern as a literal string instead of a regular expression."}, + \ [ + \ {"name": ["-F", "--fixed-strings"], "nargs": 0, "help": "Treat the pattern as a literal string instead of a regular expression."}, + \ {"name": ["--no-fixed-strings"], "nargs": 0, "help": "Treat the pattern as a regular expression."}, + \ ], \ {"name": ["-i", "--ignore-case"], "nargs": 0, "help": "Searches case insensitively."}, \ {"name": ["-L", "--follow"], "nargs": 0, "help": "Follow symbolic links while traversing directories."}, \ {"name": ["-P", "--pcre2"], "nargs": 0, "help": "When this flag is present, rg will use the PCRE2 regex engine instead of its default regex engine."}, @@ -104,6 +129,8 @@ let g:Lf_Arguments = { \ {"name": ["-v", "--invert-match"], "nargs": 0, "help": "Invert matching. Show lines that do not match the given patterns."}, \ {"name": ["-w", "--word-regexp"], "nargs": 0, "help": "Only show matches surrounded by word boundaries. This is roughly equivalent to putting \\b before and after all of the search patterns."}, \ {"name": ["-x", "--line-regexp"], "nargs": 0, "help": "Only show matches surrounded by line boundaries."}, + \ {"name": ["--binary"], "nargs": 0, "help": "Enabling this flag will cause ripgrep to search binary files."}, + \ {"name": ["--column"], "nargs": 0, "help": "Show column numbers (1-based). This only shows the column numbers for the first match on each line."}, \ {"name": ["--hidden"], "nargs": 0, "help": "Search hidden files and directories. By default, hidden files and directories are skipped."}, \ {"name": ["--heading"], "nargs": 0, "help": "Prints the file path above clusters of matches from each file instead of printing the file path as a prefix for each matched line."}, \ {"name": ["--no-config"], "nargs": 0, "help": "Never read configuration files. When this flag is present, rg will not respect the RIPGREP_CONFIG_PATH environment variable."}, @@ -147,6 +174,7 @@ let g:Lf_Arguments = { \ {"name": ["--append"], "nargs": 0, "help": "Append to the previous search results."}, \ {"name": ["--match-path"], "nargs": 0, "help": "Match the file path when fuzzy searching."}, \ {"name": ["--wd-mode"], "nargs": 1, "metavar": "", "help": "Specify the working directory mode, value has the same meaning as g:Lf_WorkingDirectoryMode."}, + \ {"name": ["--live"], "nargs": 0, "help": "Perform the so called live grep. This option implies `-F`"}, \ ], \ "gtags":[ \ [ @@ -189,6 +217,87 @@ let g:Lf_Arguments = { \ "quickfix": [], \ "loclist": [], \ "jumps": [], + \ "git":{ + \ "log": [ + \ [ + \ {"name": ["--current-file"], "nargs": 0, "help": "show logs of current file"}, + \ {"name": ["--current-line"], "nargs": 0, "help": "show logs of current line"}, + \ ], + \ [ + \ {"name": ["--directly"], "nargs": 0, "help": "output the logs directly"}, + \ {"name": ["--explorer"], "nargs": 0, "help": "view changed files of one commit in a tree explorer"}, + \ ], + \ {"name": ["--position"], "nargs": 1, "choices": ["top", "right", "bottom", "left"], "metavar": "", + \ "help": "specifies the position of the logs window"}, + \ {"name": ["--navigation-position"], "nargs": 1, "choices": ["top", "right", "bottom", "left"], "metavar": "", + \ "help": "specifies the position of the navigation panel"}, + \ [ + \ {"name": ["-s", "--side-by-side"], "nargs": 0, "help": "show diffs in a side-by-side view"}, + \ {"name": ["-u", "--unified"], "nargs": 0, "help": "show diffs in a unified view"}, + \ ], + \ {"name": ["-n", "--max-count"], "nargs": 1, "metavar": "", "help": "Limit the number of commits to output."}, + \ {"name": ["--skip"], "nargs": 1, "metavar": "", "help": "Skip number commits before starting to show the commit output."}, + \ {"name": ["--since", "--after"], "nargs": 1, "metavar": "", "help": "Show commits more recent than a specific date."}, + \ {"name": ["--until", "--before"], "nargs": 1, "metavar": "", "help": "Show commits older than a specific date."}, + \ {"name": ["--author"], "nargs": 1, "metavar": "", "help": "Limit the commits output to ones with author header lines that match the specified pattern (regular expression)."}, + \ {"name": ["--committer"], "nargs": 1, "metavar": "", "help": "Limit the commits output to ones with committer header lines that match the specified pattern (regular expression)."}, + \ {"name": ["--no-merges"], "nargs": 0, "help": "Do not print commits with more than one parent."}, + \ {"name": ["--all"], "nargs": 0, "help": "Pretend as if all the refs in refs/, along with HEAD, are listed on the command line as ."}, + \ {"name": ["--graph"], "nargs": 0, "help": "Draw a text-based graphical representation of the commit history on the left hand side of the output."}, + \ {"name": ["--reverse-order"], "nargs": 0, "help": "Output the commits chosen to be shown in reverse order."}, + \ {"name": ["--find-copies-harder"], "nargs": 0, "help": "This flag makes the command inspect unmodified files as candidates for the source of copy."}, + \ {"name": ["extra"], "nargs": "*", "help": "extra arguments of git log"}, + \ ], + \ "diff": [ + \ {"name": ["--cached", "--staged"], "nargs": 0, "help": "run 'git diff --cached'"}, + \ [ + \ {"name": ["--directly"], "nargs": 0, "help": "output the diffs directly"}, + \ {"name": ["--explorer"], "nargs": 0, "help": "view changed files in a tree explorer"}, + \ ], + \ {"name": ["--position"], "nargs": 1, "choices": ["top", "right", "bottom", "left"], "metavar": "", + \ "help": "specifies the position of the diffs window"}, + \ {"name": ["--navigation-position"], "nargs": 1, "choices": ["top", "right", "bottom", "left"], "metavar": "", + \ "help": "specifies the position of the navigation panel"}, + \ [ + \ {"name": ["-s", "--side-by-side"], "nargs": 0, "help": "show diffs in a side-by-side view"}, + \ {"name": ["-u", "--unified"], "nargs": 0, "help": "show diffs in a unified view"}, + \ ], + \ {"name": ["--current-file"], "nargs": 0, "help": "show diffs of current file"}, + \ {"name": ["extra"], "nargs": "*", "help": "extra arguments of git diff"}, + \ ], + \ "blame": [ + \ {"name": ["-w"], "nargs": 0, "help": "Ignore whitespace when comparing the parent’s version and the child’s to find where the lines came from."}, + \ {"name": ["--date"], "nargs": 1, "choices": ["relative", "local", "iso", "iso-strict", "rfc", "short", "default"], + \ "metavar": "", "help": "Specifies the format used to output dates. .i.e, git blame --date=. can be one of ['relative', 'local', 'iso', 'iso-strict', 'rfc', 'short', 'default']"}, + \ {"name": ["--inline"], "nargs": 0, "help": "Display inline git blame information."}, + \ ], + \ "status": [ + \ {"name": ["--navigation-position"], "nargs": 1, "choices": ["top", "right", "bottom", "left"], "metavar": "", + \ "help": "specifies the position of the navigation panel"}, + \ [ + \ {"name": ["-s", "--side-by-side"], "nargs": 0, "help": "show diffs in a side-by-side view"}, + \ {"name": ["-u", "--unified"], "nargs": 0, "help": "show diffs in a unified view"}, + \ ], + \ ], + \ }, + \ "coc":{ + \ "definitions": [ + \ {"name": ["--auto-jump"], "nargs": "?", "metavar": "", "help": "Jump to the target directly when there is only one match. can be 'h', 'v' or 't', which mean jump to a horizontally, vertically split window, or a new tabpage respectively. If is omitted, jump to a position in current window."}, + \ ], + \ "declarations": [ + \ {"name": ["--auto-jump"], "nargs": "?", "metavar": "", "help": "Jump to the target directly when there is only one match. can be 'h', 'v' or 't', which mean jump to a horizontally, vertically split window, or a new tabpage respectively. If is omitted, jump to a position in current window."}, + \ ], + \ "implementations": [ + \ {"name": ["--auto-jump"], "nargs": "?", "metavar": "", "help": "Jump to the target directly when there is only one match. can be 'h', 'v' or 't', which mean jump to a horizontally, vertically split window, or a new tabpage respectively. If is omitted, jump to a position in current window."}, + \ ], + \ "typeDefinitions": [ + \ {"name": ["--auto-jump"], "nargs": "?", "metavar": "", "help": "Jump to the target directly when there is only one match. can be 'h', 'v' or 't', which mean jump to a horizontally, vertically split window, or a new tabpage respectively. If is omitted, jump to a position in current window."}, + \ ], + \ "references": [ + \ {"name": ["--auto-jump"], "nargs": "?", "metavar": "", "help": "Jump to the target directly when there is only one match. can be 'h', 'v' or 't', which mean jump to a horizontally, vertically split window, or a new tabpage respectively. If is omitted, jump to a position in current window."}, + \ {"name": ["--exclude-declaration"], "nargs": 0, "help": "Exclude declaration locations."}, + \ ], + \ }, \} let g:Lf_CommonArguments = [ @@ -223,6 +332,13 @@ let g:Lf_CommonArguments = [ \ {"name": ["--popup-height"], "nargs": 1, "help": "specifies the maximum height of popup window, only available in popup mode."}, \ {"name": ["--popup-width"], "nargs": 1, "help": "specifies the width of popup window, only available in popup mode."}, \ {"name": ["--no-sort"], "nargs": 0, "help": "do not sort the result."}, + \ {"name": ["--case-insensitive"], "nargs": 0, "help": "fuzzy search case insensitively."}, + \ [ + \ {"name": ["--auto-preview"], "nargs": 0, "help": "open preview window automatically."}, + \ {"name": ["--no-auto-preview"], "nargs": 0, "help": "don't open preview window automatically."}, + \ ], + \ {"name": ["--quick-select"], "nargs": "?", "choices":[0, 1], "metavar": "", "help": "Enable quick-select mode or not. can be '1' or '0', which means 'true' or 'false' respectively. If is omitted, it means enable quick-select mode."}, + \ {"name": ["--preview-position"], "nargs": 1, "choices": ["top", "topleft", "topright", "right", "bottom", "left", "cursor"], "metavar": "", "help": "Specify where to place the preview window."}, \] " arguments is something like g:Lf_CommonArguments @@ -317,6 +433,21 @@ function! leaderf#Any#parseArguments(argLead, cmdline, cursorPos) abort else let arguments = [] endif + + if type(arguments) == type({}) + if argNum == 2 || argNum == 3 && a:argLead != "" + return filter(keys(arguments), "s:Lf_FuzzyMatch(a:argLead, v:val)") + else + let arguments = arguments[argList[2]] + endif + endif + + if argNum > 3 && argList[1] == "git" && argList[2] == "blame" + if get(existingOptions, -1, "") == "--date" + return ["relative", "local", "iso", "iso-strict", "rfc", "short", "default"] + endif + endif + let argDict = s:Lf_GenDict(arguments + g:Lf_CommonArguments) for opt in s:Lf_Refine(arguments + g:Lf_CommonArguments) if type(opt) == type([]) diff --git a/autoload/leaderf/BufTag.vim b/autoload/leaderf/BufTag.vim index 96f5eb4a..fdbbf10e 100644 --- a/autoload/leaderf/BufTag.vim +++ b/autoload/leaderf/BufTag.vim @@ -27,17 +27,15 @@ function! leaderf#BufTag#Maps() nnoremap :exec g:Lf_py "bufTagExplManager.input()" nnoremap :exec g:Lf_py "bufTagExplManager.toggleHelp()" nnoremap p :exec g:Lf_py "bufTagExplManager._previewResult(True)" - nnoremap j j:exec g:Lf_py "bufTagExplManager._previewResult(False)" - nnoremap k k:exec g:Lf_py "bufTagExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "bufTagExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "bufTagExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "bufTagExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "bufTagExplManager._previewResult(False)" - if has("nvim") - nnoremap :exec g:Lf_py "bufTagExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "bufTagExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "bufTagExplManager._closePreviewPopup()" - endif + nnoremap j :exec g:Lf_py "bufTagExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "bufTagExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "bufTagExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "bufTagExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "bufTagExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "bufTagExplManager.moveAndPreview('PageDown')" + nnoremap :exec g:Lf_py "bufTagExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "bufTagExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "bufTagExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "BufTag") for i in g:Lf_NormalMap["BufTag"] exec 'nnoremap '.i[0].' '.i[1] diff --git a/autoload/leaderf/Buffer.vim b/autoload/leaderf/Buffer.vim index 7675139c..3f1d5462 100644 --- a/autoload/leaderf/Buffer.vim +++ b/autoload/leaderf/Buffer.vim @@ -28,11 +28,16 @@ function! leaderf#Buffer#Maps() nnoremap :exec g:Lf_py "bufExplManager.toggleHelp()" nnoremap d :exec g:Lf_py "bufExplManager.deleteBuffer(1)" nnoremap D :exec g:Lf_py "bufExplManager.deleteBuffer()" - if has("nvim") - nnoremap :exec g:Lf_py "bufExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "bufExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "bufExplManager._closePreviewPopup()" - endif + nnoremap p :exec g:Lf_py "bufExplManager._previewResult(True)" + nnoremap j :exec g:Lf_py "bufExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "bufExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "bufExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "bufExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "bufExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "bufExplManager.moveAndPreview('PageDown')" + nnoremap :exec g:Lf_py "bufExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "bufExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "bufExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "Buffer") for i in g:Lf_NormalMap["Buffer"] exec 'nnoremap '.i[0].' '.i[1] @@ -41,98 +46,14 @@ function! leaderf#Buffer#Maps() endfunction function! leaderf#Buffer#NormalModeFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) - - if key !=# "g" - call win_execute(a:winid, "let g:Lf_Buffer_is_g_pressed = 0") - endif + let key = leaderf#RemapKey(g:Lf_PyEval("id(bufExplManager)"), get(g:Lf_KeyMap, a:key, a:key)) - if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "bufExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "bufExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "bufExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "bufExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "g" - if get(g:, "Lf_Buffer_is_g_pressed", 0) == 0 - let g:Lf_Buffer_is_g_pressed = 1 - else - let g:Lf_Buffer_is_g_pressed = 0 - call win_execute(a:winid, "norm! gg") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==# "G" - call win_execute(a:winid, "norm! G") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - if exists("*getmousepos") - let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - redraw - elseif has('patch-8.1.2266') - call win_execute(a:winid, "exec v:mouse_lnum") - call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "bufExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py "bufExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "bufExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "q" || key ==? "" - exec g:Lf_py "bufExplManager.quit()" - elseif key ==# "i" || key ==? "" - call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') - exec g:Lf_py "bufExplManager.input()" - elseif key ==# "o" || key ==? "" || key ==? "<2-LeftMouse>" - exec g:Lf_py "bufExplManager.accept()" - elseif key ==# "x" - exec g:Lf_py "bufExplManager.accept('h')" - elseif key ==# "v" - exec g:Lf_py "bufExplManager.accept('v')" - elseif key ==# "t" - exec g:Lf_py "bufExplManager.accept('t')" - elseif key ==# "p" - exec g:Lf_py "bufExplManager._previewResult(True)" - elseif key ==? "" - exec g:Lf_py "bufExplManager.toggleHelp()" - elseif key ==# "d" + if key ==# "d" exec g:Lf_py "bufExplManager.deleteBuffer(1)" elseif key ==# "D" exec g:Lf_py "bufExplManager.deleteBuffer()" - elseif key ==? "" - exec g:Lf_py "bufExplManager._toUpInPopup()" - elseif key ==? "" - exec g:Lf_py "bufExplManager._toDownInPopup()" + else + return leaderf#NormalModeFilter(g:Lf_PyEval("id(bufExplManager)"), a:winid, a:key) endif return 1 diff --git a/autoload/leaderf/Coc.vim b/autoload/leaderf/Coc.vim new file mode 100644 index 00000000..2cc2f94a --- /dev/null +++ b/autoload/leaderf/Coc.vim @@ -0,0 +1,55 @@ +" ============================================================================ +" File: Coc.vim +" Description: +" Author: Yggdroot +" Website: https://github.com/Yggdroot +" Note: +" License: Apache License, Version 2.0 +" ============================================================================ + +if leaderf#versionCheck() == 0 " this check is necessary + finish +endif + +exec g:Lf_py "from leaderf.cocExpl import *" + +function! leaderf#Coc#Maps(id) abort + nmapclear + exec g:Lf_py "import ctypes" + let manager = printf("ctypes.cast(%d, ctypes.py_object).value", a:id) + exec printf('nnoremap :exec g:Lf_py "%s.accept()"', manager) + exec printf('nnoremap o :exec g:Lf_py "%s.accept()"', manager) + exec printf('nnoremap <2-LeftMouse> :exec g:Lf_py "%s.accept()"', manager) + exec printf('nnoremap x :exec g:Lf_py "%s.accept(''h'')"', manager) + exec printf('nnoremap v :exec g:Lf_py "%s.accept(''v'')"', manager) + exec printf('nnoremap t :exec g:Lf_py "%s.accept(''t'')"', manager) + exec printf('nnoremap p :exec g:Lf_py "%s._previewResult(True)"', manager) + exec printf('nnoremap j :exec g:Lf_py "%s.moveAndPreview(''j'')"', manager) + exec printf('nnoremap k :exec g:Lf_py "%s.moveAndPreview(''k'')"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.moveAndPreview(''Up'')"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.moveAndPreview(''Down'')"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.moveAndPreview(''PageUp'')"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.moveAndPreview(''PageDown'')"', manager) + exec printf('nnoremap q :exec g:Lf_py "%s.quit()"', manager) + exec printf('nnoremap i :exec g:Lf_py "%s.input()"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.input()"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.toggleHelp()"', manager) + exec printf('nnoremap :exec g:Lf_py "%s._toUpInPopup()"', manager) + exec printf('nnoremap :exec g:Lf_py "%s._toDownInPopup()"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.closePreviewPopupOrQuit()"', manager) +endfunction + +function! leaderf#Coc#Commands() abort + if !exists("g:Lf_CocCommands") + let g:Lf_CocCommands = [ + \ {"Leaderf! coc definitions --auto-jump": "run CocAction('jumpDefinition')"}, + \ {"Leaderf! coc references --auto-jump": "run CocAction('jumpReferences')"}, + \ {"Leaderf! coc references --auto-jump --exclude-declaration": "run CocAction('jumpUsed')"}, + \ {"Leaderf! coc declarations --auto-jump": "run CocAction('jumpDeclaration')"}, + \ {"Leaderf! coc implementations --auto-jump": "run CocAction('jumpImplementation')"}, + \ {"Leaderf! coc typeDefinitions --auto-jump": "run CocAction('jumpTypeDefinition')"}, + \ ] + endif + + return g:Lf_CocCommands +endfunction diff --git a/autoload/leaderf/Colors.vim b/autoload/leaderf/Colors.vim index 6f443c9a..07f63b59 100644 --- a/autoload/leaderf/Colors.vim +++ b/autoload/leaderf/Colors.vim @@ -24,12 +24,12 @@ function! leaderf#Colors#Maps() nnoremap :exec g:Lf_py "colorschemeExplManager.input()" nnoremap :exec g:Lf_py "colorschemeExplManager.toggleHelp()" nnoremap p :exec g:Lf_py "colorschemeExplManager._previewResult(True)" - nnoremap j j:exec g:Lf_py "colorschemeExplManager._previewResult(False)" - nnoremap k k:exec g:Lf_py "colorschemeExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "colorschemeExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "colorschemeExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "colorschemeExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "colorschemeExplManager._previewResult(False)" + nnoremap j :exec g:Lf_py "colorschemeExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "colorschemeExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "colorschemeExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "colorschemeExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "colorschemeExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "colorschemeExplManager.moveAndPreview('PageDown')" if has_key(g:Lf_NormalMap, "Colorscheme") for i in g:Lf_NormalMap["Colorscheme"] exec 'nnoremap '.i[0].' '.i[1] diff --git a/autoload/leaderf/Command.vim b/autoload/leaderf/Command.vim index d2ab6916..9e25eb49 100644 --- a/autoload/leaderf/Command.vim +++ b/autoload/leaderf/Command.vim @@ -19,7 +19,7 @@ function! leaderf#Command#Maps() nnoremap o :exec g:Lf_py "commandExplManager.accept()" nnoremap <2-LeftMouse> :exec g:Lf_py "commandExplManager.accept()" nnoremap q :exec g:Lf_py "commandExplManager.quit()" - nnoremap :exec g:Lf_pI "commandExplManager.input()" + nnoremap :exec g:Lf_py "commandExplManager.input()" nnoremap e :exec g:Lf_py "commandExplManager.editCommand()" nnoremap :exec g:Lf_py "commandExplManager.toggleHelp()" if has_key(g:Lf_NormalMap, "Command") @@ -30,84 +30,18 @@ function! leaderf#Command#Maps() endfunction function! leaderf#Command#NormalModeFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) + let key = leaderf#RemapKey(g:Lf_PyEval("id(commandExplManager)"), get(g:Lf_KeyMap, a:key, a:key)) - if key !=# "g" - call win_execute(a:winid, "let g:Lf_Command_is_g_pressed = 0") - endif - - if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "commandExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "commandExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "commandExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "commandExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "g" - if get(g:, "Lf_Command_is_g_pressed", 0) == 0 - let g:Lf_Command_is_g_pressed = 1 - else - let g:Lf_Command_is_g_pressed = 0 - call win_execute(a:winid, "norm! gg") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==# "G" - call win_execute(a:winid, "norm! G") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - if exists("*getmousepos") - let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - redraw - elseif has('patch-8.1.2266') - call win_execute(a:winid, "exec v:mouse_lnum") - call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "commandExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py "commandExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "commandExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "q" || key ==? "" - exec g:Lf_py "commandExplManager.quit()" - elseif key ==# "i" || key ==? "" - call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') - exec g:Lf_py "commandExplManager.input()" - elseif key ==# "o" || key ==? "" || key ==? "<2-LeftMouse>" - exec g:Lf_py "commandExplManager.accept()" - elseif key ==? "" - exec g:Lf_py "commandExplManager.toggleHelp()" - elseif key ==? "e" + if key ==# "x" + elseif key ==# "v" + elseif key ==# "t" + elseif key ==# "p" + elseif key ==? "" + elseif key ==? "" + elseif key ==# "e" exec g:Lf_py "commandExplManager.editCommand()" + else + return leaderf#NormalModeFilter(g:Lf_PyEval("id(commandExplManager)"), a:winid, a:key) endif return 1 diff --git a/autoload/leaderf/File.vim b/autoload/leaderf/File.vim index 912390b0..e901c1e9 100644 --- a/autoload/leaderf/File.vim +++ b/autoload/leaderf/File.vim @@ -31,17 +31,15 @@ function! leaderf#File#Maps() nnoremap a :exec g:Lf_py "fileExplManager.selectAll()" nnoremap c :exec g:Lf_py "fileExplManager.clearSelections()" nnoremap p :exec g:Lf_py "fileExplManager._previewResult(True)" - nnoremap j j:exec g:Lf_py "fileExplManager._previewResult(False)" - nnoremap k k:exec g:Lf_py "fileExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "fileExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "fileExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "fileExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "fileExplManager._previewResult(False)" - if has("nvim") - nnoremap :exec g:Lf_py "fileExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "fileExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "fileExplManager._closePreviewPopup()" - endif + nnoremap j :exec g:Lf_py "fileExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "fileExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "fileExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "fileExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "fileExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "fileExplManager.moveAndPreview('PageDown')" + nnoremap :exec g:Lf_py "fileExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "fileExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "fileExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "File") for i in g:Lf_NormalMap["File"] exec 'nnoremap '.i[0].' '.i[1] @@ -58,108 +56,18 @@ function! leaderf#File#TimerCallback(id) endfunction function! leaderf#File#NormalModeFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) - - if key !=# "g" - call win_execute(a:winid, "let g:Lf_File_is_g_pressed = 0") - endif + let key = leaderf#RemapKey(g:Lf_PyEval("id(fileExplManager)"), get(g:Lf_KeyMap, a:key, a:key)) - if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "fileExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "fileExplManager._previewResult(False)" - elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "fileExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "fileExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "fileExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "fileExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "fileExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "fileExplManager._previewResult(False)" - elseif key ==# "g" - if get(g:, "Lf_File_is_g_pressed", 0) == 0 - let g:Lf_File_is_g_pressed = 1 - else - let g:Lf_File_is_g_pressed = 0 - call win_execute(a:winid, "norm! gg") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==# "G" - call win_execute(a:winid, "norm! G") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - if exists("*getmousepos") - let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "fileExplManager._previewResult(False)" - elseif has('patch-8.1.2266') - call win_execute(a:winid, "exec v:mouse_lnum") - call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "fileExplManager._previewResult(False)" - endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "fileExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py "fileExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "fileExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "q" || key ==? "" - exec g:Lf_py "fileExplManager.quit()" - elseif key ==# "i" || key ==? "" - call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') - exec g:Lf_py "fileExplManager.input()" - elseif key ==# "o" || key ==? "" || key ==? "<2-LeftMouse>" - exec g:Lf_py "fileExplManager.accept()" - elseif key ==# "x" - exec g:Lf_py "fileExplManager.accept('h')" - elseif key ==# "v" - exec g:Lf_py "fileExplManager.accept('v')" - elseif key ==# "t" - exec g:Lf_py "fileExplManager.accept('t')" + if key ==? "" + exec g:Lf_py "fileExplManager.refresh()" elseif key ==# "s" exec g:Lf_py "fileExplManager.addSelections()" elseif key ==# "a" exec g:Lf_py "fileExplManager.selectAll()" elseif key ==# "c" exec g:Lf_py "fileExplManager.clearSelections()" - elseif key ==# "p" - exec g:Lf_py "fileExplManager._previewResult(True)" - elseif key ==? "" - exec g:Lf_py "fileExplManager.toggleHelp()" - elseif key ==? "" - exec g:Lf_py "fileExplManager.refresh()" - elseif key ==? "" - exec g:Lf_py "fileExplManager._toUpInPopup()" - elseif key ==? "" - exec g:Lf_py "fileExplManager._toDownInPopup()" + else + return leaderf#NormalModeFilter(g:Lf_PyEval("id(fileExplManager)"), a:winid, a:key) endif return 1 diff --git a/autoload/leaderf/Function.vim b/autoload/leaderf/Function.vim index 6948adbd..151eccd4 100644 --- a/autoload/leaderf/Function.vim +++ b/autoload/leaderf/Function.vim @@ -27,17 +27,15 @@ function! leaderf#Function#Maps() nnoremap :exec g:Lf_py "functionExplManager.input()" nnoremap :exec g:Lf_py "functionExplManager.toggleHelp()" nnoremap p :exec g:Lf_py "functionExplManager._previewResult(True)" - nnoremap j j:exec g:Lf_py "functionExplManager._previewResult(False)" - nnoremap k k:exec g:Lf_py "functionExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "functionExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "functionExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "functionExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "functionExplManager._previewResult(False)" - if has("nvim") - nnoremap :exec g:Lf_py "functionExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "functionExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "functionExplManager._closePreviewPopup()" - endif + nnoremap j :exec g:Lf_py "functionExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "functionExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "functionExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "functionExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "functionExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "functionExplManager.moveAndPreview('PageDown')" + nnoremap :exec g:Lf_py "functionExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "functionExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "functionExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "Function") for i in g:Lf_NormalMap["Function"] exec 'nnoremap '.i[0].' '.i[1] diff --git a/autoload/leaderf/Git.vim b/autoload/leaderf/Git.vim new file mode 100644 index 00000000..63e2bb6c --- /dev/null +++ b/autoload/leaderf/Git.vim @@ -0,0 +1,891 @@ +" ============================================================================ +" File: Git.vim +" Description: +" Author: Yggdroot +" Website: https://github.com/Yggdroot +" Note: +" License: Apache License, Version 2.0 +" ============================================================================ + +if leaderf#versionCheck() == 0 " this check is necessary + finish +endif + +exec g:Lf_py "from leaderf.gitExpl import *" + +function! leaderf#Git#Maps(id) abort + nmapclear + exec g:Lf_py "import ctypes" + let manager = printf("ctypes.cast(%d, ctypes.py_object).value", a:id) + exec printf('nnoremap :exec g:Lf_py "%s.accept()"', manager) + exec printf('nnoremap o :exec g:Lf_py "%s.accept()"', manager) + exec printf('nnoremap <2-LeftMouse> :exec g:Lf_py "%s.accept()"', manager) + exec printf('nnoremap x :exec g:Lf_py "%s.accept(''h'')"', manager) + exec printf('nnoremap v :exec g:Lf_py "%s.accept(''v'')"', manager) + exec printf('nnoremap t :exec g:Lf_py "%s.accept(''t'')"', manager) + exec printf('nnoremap p :exec g:Lf_py "%s._previewResult(True)"', manager) + exec printf('nnoremap j :exec g:Lf_py "%s.moveAndPreview(''j'')"', manager) + exec printf('nnoremap k :exec g:Lf_py "%s.moveAndPreview(''k'')"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.moveAndPreview(''Up'')"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.moveAndPreview(''Down'')"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.moveAndPreview(''PageUp'')"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.moveAndPreview(''PageDown'')"', manager) + exec printf('nnoremap q :exec g:Lf_py "%s.quit()"', manager) + exec printf('nnoremap i :exec g:Lf_py "%s.input()"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.input()"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.toggleHelp()"', manager) + exec printf('nnoremap :exec g:Lf_py "%s._toUpInPopup()"', manager) + exec printf('nnoremap :exec g:Lf_py "%s._toDownInPopup()"', manager) + exec printf('nnoremap :exec g:Lf_py "%s.closePreviewPopupOrQuit()"', manager) +endfunction + +" direction: +" 0, backward +" 1, forward +function! leaderf#Git#OuterIndent(direction) abort + let spaces = substitute(getline('.'), '^\(\s*\).*', '\1', '') + let width = strdisplaywidth(spaces) + if width == 0 + return 0 + endif + if a:direction == 0 + let flags = 'sbW' + else + let flags = 'sW' + endif + return search(printf('^\s\{,%d}\zs\S', width-1), flags) +endfunction + +" direction: +" 0, backward +" 1, forward +function! leaderf#Git#SameIndent(direction) abort + let spaces = substitute(getline('.'), '^\(\s*\).*', '\1', '') + let width = strdisplaywidth(spaces) + if a:direction == 0 + let flags = 'sbW' + else + let flags = 'sW' + endif + if width == 0 + let stopline = 0 + else + let stopline = search(printf('^\s\{,%d}\zs\S', width-1), flags[1:].'n') + endif + + noautocmd norm! ^ + call search(printf('^\s\{%d}\zs\S', width), flags, stopline) +endfunction + +function! leaderf#Git#SpecificMaps(id) abort + exec g:Lf_py "import ctypes" + let manager = printf("ctypes.cast(%d, ctypes.py_object).value", a:id) + exec printf('nnoremap e :exec g:Lf_py "%s.editCommand()"', manager) +endfunction + +" direction: +" 0, backward +" 1, forward +function! leaderf#Git#OuterBlock(direction) abort + let column = col('.') + if column >= match(getline('.'), '\S') + 1 + noautocmd norm! ^ + let column = col('.') - 1 + endif + let width = (column - 1) / 2 * 2 + if a:direction == 0 + let flags = 'sbW' + else + let flags = 'sW' + endif + call search(printf('^\s\{%d}\zs\S', width), flags) +endfunction + +let s:help = { + \ "tree": [ + \ "o: open the folder or open the diffs of current file", + \ ": open the folder or open the diffs of current file", + \ "<2-LeftMouse>: open the folder or open the diffs of current file", + \ "O: open the folder recursively", + \ "t: open the diffs in a new tabpage", + \ "s: toggle between side by side diff view and unified diff view", + \ "i: toggle between ignoring whitespace and not ignoring whitespace", + \ "p: preview the diffs, i.e., like 'o', but leave the cursor in the current panel", + \ "x: collapse the parent folder", + \ "X: collapse all the children of the current folder", + \ "f: fuzzy search files", + \ "F: resume the previous fuzzy searching", + \ "m: show the commit message", + \ "-: go to the parent folder", + \ "+: go to the next sibling of the parent folder", + \ ": go to the next sibling of the current folder", + \ ": go to the previous sibling of the current folder", + \ "(: go to the start of the current indent level", + \ "): go to the end of the current indent level", + \ "q: quit the navigation window", + \ ":LeaderfGitNavigationOpen", + \ " open the navigation window", + \ ], + \ "blame": [ + \ ": toggle the help", + \ "o: show the details of current commit in an explorer page", + \ ": show the details of current commit in an explorer page", + \ "<2-LeftMouse>: show the details of current commit in an explorer page", + \ "h: blame the parent commit of this line", + \ "l: go to the previous blame status", + \ "m: show the commit message", + \ "p: preview the diffs around the current line", + \ "q: quit the blame window", + \ ], + \} + +function s:HelpFilter(winid, key) abort + if a:key == "\" || a:key == "\" + call popup_close(a:winid) + return 1 + elseif a:key == "\" || a:key == "\" + return 0 + endif + + return 1 +endfunction + +function! s:GetRowCol(width, height) abort + let win_width = &columns + let win_height = &lines + + let row = (win_height - a:height) / 2 - 1 + let col = (win_width - a:width) / 2 + + return {'row': row, 'col': col} +endfunction + +function! leaderf#Git#ShowHelp(type) abort + if has("nvim") + let borderchars = [ + \ [g:Lf_PopupBorders[4], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[0], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[5], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[1], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[6], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[2], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[7], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[3], "Lf_hl_popupBorder"] + \] + let width = 100 + let height = len(s:help[a:type]) + let row_col = s:GetRowCol(width, height) + let options = { + \ "title": " Help ", + \ "title_pos": "center", + \ "relative": "editor", + \ "row": row_col["row"], + \ "col": row_col["col"], + \ "width": width, + \ "height": height, + \ "zindex": 20482, + \ "noautocmd": 1, + \ "border": borderchars, + \ "style": "minimal", + \} + let scratch_buffer = nvim_create_buf(v:false, v:true) + call nvim_buf_set_option(scratch_buffer, 'bufhidden', 'wipe') + call nvim_buf_set_lines(scratch_buffer, 0, -1, v:false, s:help[a:type]) + call nvim_buf_set_option(scratch_buffer, 'modifiable', v:false) + let id = nvim_open_win(scratch_buffer, 1, options) + call nvim_win_set_option(id, 'winhighlight', 'Normal:Lf_hl_popup_window') + call win_execute(id, 'call matchadd("Special", ''^.\{-}\(:\)\@='')') + call win_execute(id, 'call matchadd("Special", ''^:\w\+'')') + call win_execute(id, 'call matchadd("Comment", ''\(^.\{-1,}:\s*\)\@<=.*'')') + call win_execute(id, 'call matchadd("Comment", ''\(^\s\+\)\@<=.*'')') + call win_execute(id, 'nnoremap c') + call win_execute(id, 'nnoremap c') + else + let options = { + \ "title": " Help ", + \ "zindex": 20482, + \ "scrollbar": 1, + \ "padding": [0, 0, 0, 0], + \ "border": [1, 1, 1, 1], + \ "borderchars": g:Lf_PopupBorders, + \ "borderhighlight": ["Lf_hl_popupBorder"], + \ "filter": "s:HelpFilter", + \ "mapping": 0, + \} + + let id = popup_create(s:help[a:type], options) + call win_execute(id, 'setlocal wincolor=Lf_hl_popup_window') + call win_execute(id, 'call matchadd("Special", ''^.\{-}\(:\)\@='')') + call win_execute(id, 'call matchadd("Special", ''^:\w\+'')') + call win_execute(id, 'call matchadd("Comment", ''\(^.\{-1,}:\s*\)\@<=.*'')') + call win_execute(id, 'call matchadd("Comment", ''\(^\s\+\)\@<=.*'')') + endif +endfunction + + +function! leaderf#Git#CloseFloatWinMouse() abort + if exists("b:blame_cursorline") && exists("*getmousepos") + let pos = getmousepos() + if pos.winid == b:lf_blame_winid && b:blame_cursorline != pos["line"] + if exists("b:commit_msg_winid") && winbufnr(b:commit_msg_winid) != -1 + call nvim_win_close(b:commit_msg_winid, 1) + endif + endif + endif + if exists("b:lf_blame_preview_cursorline") && exists("*getmousepos") + let pos = getmousepos() + if pos.winid == b:lf_blame_winid && b:lf_blame_preview_cursorline != pos["line"] + if exists("b:lf_preview_winid") && winbufnr(b:lf_preview_winid) != -1 + call nvim_win_close(b:lf_preview_winid, 1) + endif + endif + endif +endfunction + +function! leaderf#Git#ShowCommitMessage(message) abort + let b:blame_cursorline = line('.') + let b:lf_blame_winid = win_getid() + if has("nvim") + let borderchars = [ + \ [g:Lf_PopupBorders[4], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[0], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[5], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[1], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[6], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[2], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[7], "Lf_hl_popupBorder"], + \ [g:Lf_PopupBorders[3], "Lf_hl_popupBorder"] + \] + let width = 80 + let height = len(a:message) + let row_col = s:GetRowCol(width, height) + let options = { + \ "title": " Commit Message ", + \ "title_pos": "center", + \ "relative": "editor", + \ "row": row_col["row"], + \ "col": row_col["col"], + \ "width": width, + \ "height": height, + \ "zindex": 20482, + \ "noautocmd": 1, + \ "border": borderchars, + \ "style": "minimal", + \} + let scratch_buffer = nvim_create_buf(v:false, v:true) + call nvim_buf_set_option(scratch_buffer, 'bufhidden', 'wipe') + call nvim_buf_set_lines(scratch_buffer, 0, -1, v:false, a:message) + call nvim_buf_set_option(scratch_buffer, 'modifiable', v:false) + if exists("b:commit_msg_winid") && winbufnr(b:commit_msg_winid) != -1 + call nvim_win_close(b:commit_msg_winid, 1) + endif + let b:commit_msg_winid = nvim_open_win(scratch_buffer, 0, options) + call nvim_win_set_option(b:commit_msg_winid, 'winhighlight', 'Normal:Lf_hl_popup_window') + call win_execute(b:commit_msg_winid, 'nnoremap c') + call win_execute(b:commit_msg_winid, 'setlocal filetype=git') + else + let maxheight = &lines / 3 + let options = { + \ "title": " Commit Message ", + \ "maxwidth": 80, + \ "minwidth": 80, + \ "maxheight": maxheight, + \ "minheight": maxheight, + \ "zindex": 20482, + \ "scrollbar": 1, + \ "padding": [0, 0, 0, 0], + \ "border": [1, 1, 1, 1], + \ "borderchars": g:Lf_PopupBorders, + \ "borderhighlight": ["Lf_hl_popupBorder"], + \ "filter": "leaderf#Git#ShowMsgFilter", + \ "mapping": 0, + \} + + let id = popup_create(a:message, options) + call win_execute(id, 'setlocal wincolor=Lf_hl_popup_window') + call win_execute(id, 'setlocal filetype=git') + endif +endfunction + +function leaderf#Git#PreviewFilter(winid, key) abort + if a:key == "\" + if exists("*getmousepos") + let pos = getmousepos() + if pos.winid != a:winid + call popup_close(a:winid) + endif + endif + endif + + if a:key == "\" + call popup_close(a:winid) + return 1 + elseif a:key == "j" || a:key == "k" + call popup_close(a:winid) + return 0 + elseif a:key == "\" + call popup_close(a:winid) + call feedkeys("\", 't') + return 1 + elseif a:key == "q" + call popup_close(a:winid) + call feedkeys("q", 't') + return 1 + elseif a:key == "h" + let manager_id = getbufvar(winbufnr(a:winid), 'lf_blame_manager_id') + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.blamePrevious()", manager_id) + return 1 + elseif a:key == "l" + let manager_id = getbufvar(winbufnr(a:winid), 'lf_blame_manager_id') + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.blameNext()", manager_id) + return 1 + elseif a:key == "\" + call win_execute(a:winid, "norm! j") + return 1 + elseif a:key == "\" + call win_execute(a:winid, "norm! k") + return 1 + else + return leaderf#popupModePreviewFilter(a:winid, a:key) + endif + + return 1 +endfunction + +function leaderf#Git#ShowMsgFilter(winid, key) abort + if a:key == "\" + if exists("*getmousepos") + let pos = getmousepos() + if pos.winid != a:winid + call popup_close(a:winid) + endif + endif + endif + + if a:key == "\" + call popup_close(a:winid) + return 1 + elseif a:key == "j" || a:key == "k" + call popup_close(a:winid) + return 0 + elseif a:key == "\" + call popup_close(a:winid) + call feedkeys("\", 't') + return 1 + elseif a:key == "\" + call win_execute(a:winid, "norm! j") + return 1 + elseif a:key == "\" + call win_execute(a:winid, "norm! k") + return 1 + else + return leaderf#popupModePreviewFilter(a:winid, a:key) + endif + + return 1 +endfunction + +function! leaderf#Git#TreeViewMaps(id) abort + exec g:Lf_py "import ctypes" + let tree_view = printf("ctypes.cast(%d, ctypes.py_object).value", a:id) + nnoremap :call leaderf#Git#ShowHelp("tree") + nnoremap - :call leaderf#Git#OuterIndent(0) + nnoremap + :call leaderf#Git#OuterIndent(1) + nnoremap :call leaderf#Git#SameIndent(0) + nnoremap :call leaderf#Git#SameIndent(1) + nnoremap ( :call leaderf#Git#OuterBlock(0) + nnoremap ) :call leaderf#Git#OuterBlock(1) +endfunction + +function! leaderf#Git#CollapseParent(navigation_panel) abort + if leaderf#Git#OuterIndent(0) != 0 + exec g:Lf_py printf("%s.openDiffView(False)", a:navigation_panel) + endif +endfunction + +function! leaderf#Git#NavigationPanelMaps(id) abort + exec g:Lf_py "import ctypes" + let navigation_panel = printf("ctypes.cast(%d, ctypes.py_object).value", a:id) + exec printf('nnoremap o :exec g:Lf_py "%s.openDiffView(False)"', navigation_panel) + exec printf('nnoremap <2-LeftMouse> :exec g:Lf_py "%s.openDiffView(False)"', navigation_panel) + exec printf('nnoremap :exec g:Lf_py "%s.openDiffView(False)"', navigation_panel) + exec printf('nnoremap O :exec g:Lf_py "%s.openDiffView(True)"', navigation_panel) + exec printf('nnoremap t :exec g:Lf_py "%s.openDiffView(False, mode=''t'')"', navigation_panel) + exec printf('nnoremap s :exec g:Lf_py "%s.toggleDiffViewMode()"', navigation_panel) + exec printf('nnoremap i :exec g:Lf_py "%s.toggleIgnoreWhitespace()"', navigation_panel) + exec printf('nnoremap p :exec g:Lf_py "%s.openDiffView(False, preview=True)"', navigation_panel) + exec printf('nnoremap x :call leaderf#Git#CollapseParent("%s")', navigation_panel) + exec printf('nnoremap X :exec g:Lf_py "%s.collapseChildren()"', navigation_panel) + exec printf('nnoremap f :exec g:Lf_py "%s.fuzzySearch()"', navigation_panel) + exec printf('nnoremap F :exec g:Lf_py "%s.fuzzySearch(True)"', navigation_panel) + exec printf('nnoremap m :exec g:Lf_py "%s.showCommitMessage()"', navigation_panel) + exec printf('nnoremap :exec g:Lf_py "%s.selectOption()"', navigation_panel) + nnoremap q :q +endfunction + +function! leaderf#Git#CloseFloatWin() abort + if exists("b:commit_msg_winid") && winbufnr(b:commit_msg_winid) != -1 + call nvim_win_close(b:commit_msg_winid, 1) + endif + if exists("b:lf_preview_winid") && winbufnr(b:lf_preview_winid) != -1 + call nvim_win_close(b:lf_preview_winid, 1) + endif +endfunction + +function! leaderf#Git#BlameMaps(id) abort + exec g:Lf_py "import ctypes" + let blame_manager = printf("ctypes.cast(%d, ctypes.py_object).value", a:id) + exec printf('nnoremap o :exec g:Lf_py "%s.open()"', blame_manager) + exec printf('nnoremap <2-LeftMouse> :exec g:Lf_py "%s.open()"', blame_manager) + exec printf('nnoremap :exec g:Lf_py "%s.open()"', blame_manager) + exec printf('nnoremap h :exec g:Lf_py "%s.blamePrevious()"', blame_manager) + exec printf('nnoremap l :exec g:Lf_py "%s.blameNext()"', blame_manager) + exec printf('nnoremap m :exec g:Lf_py "%s.showCommitMessage()"', blame_manager) + exec printf('nnoremap p :exec g:Lf_py "%s.preview()"', blame_manager) + exec printf('nnoremap q :exec g:Lf_py "%s.quit()"', blame_manager) + if has("nvim") + nnoremap j :call leaderf#Git#CloseFloatWin()j + nnoremap k :call leaderf#Git#CloseFloatWin()k + nnoremap :call leaderf#Git#CloseFloatWin() + nnoremap :call leaderf#Git#CloseFloatWinMouse() + endif + nnoremap :call leaderf#Git#ShowHelp("blame") +endfunction + +function! leaderf#Git#TimerCallback(manager_id, id) abort + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value._callback(bang=True)", a:manager_id) +endfunction + +function! leaderf#Git#WriteBuffer(view_id, id) abort + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.writeBuffer()", a:view_id) +endfunction + +function! leaderf#Git#Cleanup(owner_id, id) abort + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.cleanup()", a:owner_id) +endfunction + +function! leaderf#Git#Suicide(view_id) abort + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.suicide()", a:view_id) +endfunction + +function! leaderf#Git#Bufhidden(view_id) abort + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.bufHidden()", a:view_id) +endfunction + +function! leaderf#Git#UpdateInlineBlame(manager_id) abort + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.updateInlineBlame()", a:manager_id) +endfunction + +function! leaderf#Git#InlineBlame(manager_id, id) abort + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.inlineBlame()", a:manager_id) +endfunction + +function! leaderf#Git#StartInlineBlameImpl(timer) abort + call leaderf#LfPy("gitExplManager.startExplorer('--bottom', arguments={'arg_line': 'git blame', '--inline': [], 'autocmd': True})") +endfunction + +function! leaderf#Git#StartInlineBlame() abort + call timer_start(0, 'leaderf#Git#StartInlineBlameImpl') +endfunction + +function! leaderf#Git#DisableInlineBlame() abort + if !exists("g:lf_blame_manager_id") || g:lf_blame_manager_id == 0 + return + endif + + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.disableInlineBlame()", g:lf_blame_manager_id) +endfunction + +function! leaderf#Git#RestartInlineBlame() abort + call leaderf#Git#DisableInlineBlame() + call leaderf#Git#StartInlineBlame() +endfunction + +function! leaderf#Git#HideInlineBlame(manager_id) abort + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.hideInlineBlame()", a:manager_id) +endfunction + +function! leaderf#Git#ShowInlineBlame(manager_id) abort + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.showInlineBlame()", a:manager_id) +endfunction + +function! leaderf#Git#ToggleInlineBlame() abort + if !exists("g:Lf_git_inline_blame_enabled") || g:Lf_git_inline_blame_enabled == 0 + call leaderf#Git#StartInlineBlame() + else + call leaderf#Git#DisableInlineBlame() + endif +endfunction + +function! leaderf#Git#ClearMatches() abort + for m in b:Leaderf_matches + call matchdelete(m.id) + endfor +endfunction + +function! leaderf#Git#SetMatches() abort + call setmatches(b:Leaderf_matches) +endfunction + +function! leaderf#Git#Commands() abort + if !exists("g:Lf_GitCommands") + let g:Lf_GitCommands = [ + \ {"Leaderf git status": "show git status"}, + \ {"Leaderf git diff": "fuzzy search and view the diffs"}, + \ {"Leaderf git diff --side-by-side": "fuzzy search and view the side-by-side diffs"}, + \ {"Leaderf git diff --side-by-side --current-file":"view the side-by-side diffs of the current file"}, + \ {"Leaderf git diff --explorer": "view the diffs in an explorer tabpage"}, + \ {"Leaderf git diff --explorer --side-by-side":"view the diffs in an explorer tabpage, the diffs is shown in a side-by-side view"}, + \ {"Leaderf git diff --directly": "view the diffs directly"}, + \ {"Leaderf git diff --directly --position right":"view the diffs in the right split window"}, + \ {"Leaderf git diff --cached": "fuzzy search and view `git diff --cached`"}, + \ {"Leaderf git diff --cached --side-by-side": "fuzzy search and view the side-by-side diffs of `git diff --cached`"}, + \ {"Leaderf git diff --cached --explorer": "view `git diff --cached` in an explorer tabpage"}, + \ {"Leaderf git diff --cached --directly": "view `git diff --cached` directly"}, + \ {"Leaderf git diff --cached --directly --position right": "view `git diff --cached` directly in the right split window"}, + \ {"Leaderf git diff HEAD": "fuzzy search and view `git diff HEAD`"}, + \ {"Leaderf git diff HEAD --side-by-side": "fuzzy search and view the side-by-side diffs of `git diff HEAD`"}, + \ {"Leaderf git diff HEAD --explorer": "view `git diff HEAD` in an explorer tabpage"}, + \ {"Leaderf git diff HEAD --directly": "view `git diff HEAD` directly"}, + \ {"Leaderf git diff HEAD --directly --position right": "view `git diff HEAD` directly in the right split window"}, + \ {"Leaderf git log": "fuzzy search and view the log"}, + \ {"Leaderf git log --directly": "view the logs directly"}, + \ {"Leaderf git log --explorer": "fuzzy search and view the log in an explorer tabpage"}, + \ {"Leaderf git log --explorer --side-by-side":"fuzzy search and view the log in an explorer tabpage, the diffs is shown in a side-by-side view"}, + \ {"Leaderf git log --explorer --navigation-position bottom": "specify the position of navigation panel in explorer tabpage"}, + \ {"Leaderf git log --current-file": "fuzzy search and view the log of current file"}, + \ {"Leaderf git log --current-file --explorer":"fuzzy search and view the log of current file in explorer tabpage"}, + \ {"Leaderf git log --current-line": "fuzzy search and view the log of current line"}, + \ {"Leaderf git log --current-line --explorer":"fuzzy search and view the log of current line in explorer tabpage"}, + \ {"Leaderf git blame": "git blame current file"}, + \ {"Leaderf git blame -w": "ignore whitespace when git blame current file"}, + \ {"Leaderf git blame --date relative": "show relative date when git blame current file"}, + \ {"LeaderfGitInlineBlameEnable": "Enable inline blame. This command is a shortcut of `:Leaderf git blame --inline`."}, + \ {"LeaderfGitInlineBlameDisable": "Disable inline blame."}, + \ {"LeaderfGitInlineBlameToggle": "Toggle inline blame."}, + \ {"LeaderfGitInlineBlameUpdate": "If the file is updated in the git repository, we need to use this command to update the inline blame."}, + \ ] + endif + + return g:Lf_GitCommands +endfunction + +function! leaderf#Git#NormalModeFilter(winid, key) abort + let key = leaderf#RemapKey(g:Lf_PyEval("id(gitExplManager)"), get(g:Lf_KeyMap, a:key, a:key)) + + if key ==# "e" + exec g:Lf_py "gitExplManager.editCommand()" + else + return leaderf#NormalModeFilter(g:Lf_PyEval("id(gitExplManager)"), a:winid, a:key) + endif + + return 1 +endfunction + +function! leaderf#Git#DefineSyntax() abort + syntax region Lf_hl_gitStat start=/^---$/ end=/^ \d\+ files\? changed,/ + syn match Lf_hl_gitStatPath /^ \S*\%(\s*|\s*\d\+\s*+*-*$\)\@=/ display containedin=Lf_hl_gitStat contained + syn match Lf_hl_gitStatPath /^ \S*\%(\s*|\s*Bin\%( \d\+ -> \d\+ bytes\?\)\?$\)\@=/ display containedin=Lf_hl_gitStat contained + syn match Lf_hl_gitStatPath /^ \S*\%( => \S*\s*|\s*\d\+\s*+*-*$\)\@=/ display containedin=Lf_hl_gitStat contained + syn match Lf_hl_gitStatPath /^ \S*\%( => \S*\s*|\s*Bin\%( \d\+ -> \d\+ bytes\?\)\?$\)\@=/ display containedin=Lf_hl_gitStat contained + syn match Lf_hl_gitStatPath /\%(^ \S* => \)\@<=\S*\%(\s*|\s*\d\+\s*+*-*$\)\@=/ display containedin=Lf_hl_gitStat contained + syn match Lf_hl_gitStatPath /\%(^ \S* => \)\@<=\S*\%(\s*|\s*Bin\%( \d\+ -> \d\+ bytes\?\)\?$\)\@=/ display containedin=Lf_hl_gitStat contained + syn match Lf_hl_gitStatNumber /\%(^ \S*\%( => \S*\)\?\s*|\s*\)\@<=\d\+\%(\s*+*-*$\)\@=/ display containedin=Lf_hl_gitStat contained + syn match Lf_hl_gitStatNumber /\%(^ \S*\%( => \S*\)\?\s*|\s*Bin \)\@<=\d\+ -> \d\+\%( bytes\?$\)\@=/ display containedin=Lf_hl_gitStat contained + syn match Lf_hl_gitStatPlus /\%(^ \S*\%( => \S*\)\?\s*|\s*\d\+\s*\)\@<=+*\%(-*$\)\@=/ display containedin=Lf_hl_gitStat contained + syn match Lf_hl_gitStatMinus /\%(^ \S*\%( => \S*\)\?\s*|\s*\d\+\s*+*\)\@<=-*$/ display containedin=Lf_hl_gitStat contained + syn match gitIdentityHeader /^Committer:/ contained containedin=gitHead nextgroup=gitIdentity skipwhite contains=@NoSpell +endfunction + +function! leaderf#Git#DiffOff(win_ids) abort + for id in a:win_ids + call win_execute(id, "diffoff") + endfor +endfunction + +function! leaderf#Git#FoldExpr() abort + return has_key(b:Leaderf_fold_ranges_dict, v:lnum) +endfunction + +function! leaderf#Git#SetLineNumberWin(line_num_content, buffer_num) abort + if len(a:line_num_content) == 0 + return + endif + + let line = a:line_num_content[0] + let line_len = strlen(line) + + let hi_line_num = get(g:, 'Lf_GitHightlightLineNumber', 1) + let delimiter = get(g:, 'Lf_GitDelimiter', '│') + let delimiter_len = len(delimiter) + let ns_id = nvim_create_namespace('LeaderF') + + for i in range(len(a:line_num_content)) + let line = a:line_num_content[i] + let last_part = strcharpart(line, line_len - (delimiter_len + 1), 2) + + if last_part[0] == '-' + if hi_line_num == 1 + let hl_group1 = "Lf_hl_gitDiffDelete" + else + let hl_group1 = "Lf_hl_LineNr" + endif + let hl_group2 = "Lf_hl_gitDiffDelete" + let first_part = strcharpart(line, 0, line_len - (delimiter_len + 1)) + call nvim_buf_set_extmark(a:buffer_num, ns_id, i, 0, {'virt_text': [[first_part, hl_group1], [last_part, hl_group2]], 'virt_text_pos': 'inline'}) + elseif last_part[0] == '+' + if hi_line_num == 1 + let hl_group1 = "Lf_hl_gitDiffAdd" + else + let hl_group1 = "Lf_hl_LineNr" + endif + let hl_group2 = "Lf_hl_gitDiffAdd" + let first_part = strcharpart(line, 0, line_len - (delimiter_len + 1)) + call nvim_buf_set_extmark(a:buffer_num, ns_id, i, 0, {'virt_text': [[first_part, hl_group1], [last_part, hl_group2]], 'virt_text_pos': 'inline'}) + else + let hl_group = "Lf_hl_LineNr" + call nvim_buf_set_extmark(a:buffer_num, ns_id, i, 0, {'virt_text': [[line, hl_group]], 'virt_text_pos': 'inline'}) + endif + endfor +endfunction + +function! leaderf#Git#SignPlace(added_line_nums, deleted_line_nums, buf_number) abort + for i in a:added_line_nums + call sign_place(0, "LeaderF", "Leaderf_diff_add", a:buf_number, {'lnum': i}) + endfor + + for i in a:deleted_line_nums + call sign_place(0, "LeaderF", "Leaderf_diff_delete", a:buf_number, {'lnum': i}) + endfor +endfunction + +function! leaderf#Git#PreviousChange(tag) abort + if a:tag == 0 + let n = line('.') + let low = 0 + let high = len(b:lf_change_start_lines) + while low < high + let mid = (low + high)/2 + if b:lf_change_start_lines[mid] < n + let low = mid + 1 + else + let high = mid + endif + endwhile + + if low - 1 >= 0 + exec printf("norm! %dG0", b:lf_change_start_lines[low - 1]) + endif + else + call s:PreviousChange() + endif +endfunction + +function! s:PreviousChange() abort +exec g:Lf_py "<< EOF" +cur_line = vim.current.window.cursor[0] +flag = False +for i, line in enumerate(reversed(vim.current.buffer[:cur_line])): + if len(line) > 0 and line[0] in '-+': + if flag == True: + vim.current.window.cursor = [cur_line - i, 0] + break + else: + flag = True + +EOF +endfunction + +function! leaderf#Git#NextChange(tag) abort + if a:tag == 0 + let n = line('.') + let low = 0 + let size = len(b:lf_change_start_lines) + let high = size + while low < high + let mid = (low + high)/2 + if b:lf_change_start_lines[mid] <= n + let low = mid + 1 + else + let high = mid + endif + endwhile + + if high < size + exec printf("norm! %dG0", b:lf_change_start_lines[high]) + endif + else + call s:NextChange() + endif +endfunction + +function! s:NextChange() abort +exec g:Lf_py "<< EOF" +cur_line = vim.current.window.cursor[0] - 1 +flag = False +for i, line in enumerate(vim.current.buffer[cur_line:], cur_line): + if len(line) > 0 and line[0] in '-+': + if flag == True: + vim.current.window.cursor = [i+1, 0] + break + else: + flag = True + +EOF +endfunction + +function! s:GoToFile(file_name) abort + if !filereadable(a:file_name) + echohl WarningMsg + echo a:file_name .. " does not exist." + echohl None + return + endif + + let buffer_num = bufnr(fnamemodify(a:file_name, ':p')) + if buffer_num == -1 + exec "tabedit " . a:file_name + else + let buf_ids = win_findbuf(buffer_num) + if len(buf_ids) == 0 + exec "tabedit " . a:file_name + else + call win_gotoid(buf_ids[0]) + endif + endif +endfunction + +function! leaderf#Git#EditFile(tag) abort + if a:tag == 0 + if !filereadable(b:lf_git_buffer_name) + echohl WarningMsg + echo b:lf_git_buffer_name .. " does not exist." + echohl None + return + endif + + let start_line_num = line('.') + let line_num_content = b:lf_git_line_num_content + if len(line_num_content) == 0 + return + endif + + let line = line_num_content[0] + let line_len = strlen(line) + + let delimiter = get(g:, 'Lf_GitDelimiter', '│') + let delimiter_len = len(delimiter) + + let buffer_num = bufnr(b:lf_git_buffer_name) + if buffer_num == -1 + exec "tabedit " . b:lf_git_buffer_name + else + let buf_ids = win_findbuf(buffer_num) + if len(buf_ids) == 0 + exec "tabedit " . b:lf_git_buffer_name + else + call win_gotoid(buf_ids[0]) + endif + endif + + let i = start_line_num - 1 + while i < len(line_num_content) + let line = line_num_content[i] + let last_part = strcharpart(line, line_len - (delimiter_len + 1), 2) + if last_part[0] != '-' + let numbers = split(line, ' ') + if len(numbers) == 2 + let line_num = numbers[0] + else + let line_num = numbers[1] + endif + exec "norm! " . line_num . "G" + return + endif + let i += 1 + endwhile + norm! G + elseif a:tag == 1 + let cur_line = getline('.') + if cur_line =~ '^diff --git a/\S* b/\S*' + let file_name = split(getline('.'))[3][2:] + call s:GoToFile(file_name) + return + endif + + let diff_line_num = search('^diff --git a/\S* b/\S*', 'bnW') + if diff_line_num == 0 + return + endif + let diff_line = getline(diff_line_num) + let file_name = split(diff_line)[3][2:] + let at_line_num = search('^@@', 'bnW') + if at_line_num < diff_line_num || cur_line =~ '^@@' + call s:GoToFile(file_name) + return + endif + + let start_line_num = matchstr(getline(at_line_num), '+\zs\(\d\+\)') + let i = at_line_num + 1 + let cur_line_num = line('.') + let delta = 0 + while i <= cur_line_num + if getline(i) !~ '^-' + let delta += 1 + endif + let i += 1 + endwhile + + if cur_line !~ '^-' + let line_num = start_line_num + delta - 1 + else + let line_num = start_line_num + delta + endif + call s:GoToFile(file_name) + exec "norm! " . line_num . "G" + setlocal cursorline! | redraw | sleep 150m | setlocal cursorline! + else + let file_name = b:lf_git_buffer_name + if b:lf_git_diff_win_pos == 1 + let line_num = line('.') + else + let line_num = getcurpos(b:lf_git_diff_win_id)[1] + endif + call s:GoToFile(file_name) + exec "norm! " . line_num . "G" + setlocal cursorline! | redraw | sleep 150m | setlocal cursorline! + endif +endfunction + +function! leaderf#Git#OpenNavigationPanel() abort + if !exists("b:lf_explorer_page_id") || b:lf_explorer_page_id == 0 + return + endif + + exec g:Lf_py "import ctypes" + exec g:Lf_py printf("ctypes.cast(%d, ctypes.py_object).value.openNavigationPanel()", b:lf_explorer_page_id) +endfunction + +function! leaderf#Git#RemoveExtmarks(buffer_num) abort + let ns_id_0 = nvim_create_namespace('LeaderF_Git_Blame_0') + for [mark_id, row, col] in nvim_buf_get_extmarks(a:buffer_num, ns_id_0, 0, -1, {}) + call nvim_buf_del_extmark(a:buffer_num, ns_id_0, mark_id) + endfor + + let ns_id_1 = nvim_create_namespace('LeaderF_Git_Blame_1') + for [mark_id, row, col] in nvim_buf_get_extmarks(a:buffer_num, ns_id_1, 0, -1, {}) + call nvim_buf_del_extmark(a:buffer_num, ns_id_1, mark_id) + endfor +endfunction diff --git a/autoload/leaderf/Gtags.vim b/autoload/leaderf/Gtags.vim index 3f08b02b..89fddc96 100644 --- a/autoload/leaderf/Gtags.vim +++ b/autoload/leaderf/Gtags.vim @@ -23,23 +23,21 @@ function! leaderf#Gtags#Maps() nnoremap v :exec g:Lf_py "gtagsExplManager.accept('v')" nnoremap t :exec g:Lf_py "gtagsExplManager.accept('t')" nnoremap p :exec g:Lf_py "gtagsExplManager._previewResult(True)" - nnoremap j j:exec g:Lf_py "gtagsExplManager._previewResult(False)" - nnoremap k k:exec g:Lf_py "gtagsExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "gtagsExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "gtagsExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "gtagsExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "gtagsExplManager._previewResult(False)" + nnoremap j :exec g:Lf_py "gtagsExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "gtagsExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "gtagsExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "gtagsExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "gtagsExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "gtagsExplManager.moveAndPreview('PageDown')" nnoremap q :exec g:Lf_py "gtagsExplManager.quit()" " nnoremap :exec g:Lf_py "gtagsExplManager.quit()" nnoremap i :exec g:Lf_py "gtagsExplManager.input()" nnoremap :exec g:Lf_py "gtagsExplManager.input()" nnoremap :exec g:Lf_py "gtagsExplManager.toggleHelp()" nnoremap d :exec g:Lf_py "gtagsExplManager.deleteCurrentLine()" - if has("nvim") - nnoremap :exec g:Lf_py "gtagsExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "gtagsExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "gtagsExplManager._closePreviewPopup()" - endif + nnoremap :exec g:Lf_py "gtagsExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "gtagsExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "gtagsExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "Gtags") for i in g:Lf_NormalMap["Gtags"] exec 'nnoremap '.i[0].' '.i[1] @@ -66,7 +64,7 @@ function! leaderf#Gtags#getPattern(type) if a:type == 0 return expand('') elseif a:type == 1 - return escape(expand('')) + return '"' . escape(expand(''), '"') . '"' elseif a:type == 2 return leaderf#Gtags#visual() else @@ -107,102 +105,18 @@ EOF endfunction function! leaderf#Gtags#NormalModeFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) - - if key !=# "g" - call win_execute(a:winid, "let g:Lf_Gtags_is_g_pressed = 0") - endif + let key = leaderf#RemapKey(g:Lf_PyEval("id(gtagsExplManager)"), get(g:Lf_KeyMap, a:key, a:key)) - if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "gtagsExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "gtagsExplManager._previewResult(False)" - elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "gtagsExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "gtagsExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "gtagsExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "gtagsExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "gtagsExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "gtagsExplManager._previewResult(False)" - elseif key ==# "g" - if get(g:, "Lf_Gtags_is_g_pressed", 0) == 0 - let g:Lf_Gtags_is_g_pressed = 1 - else - let g:Lf_Gtags_is_g_pressed = 0 - call win_execute(a:winid, "norm! gg") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==# "G" - call win_execute(a:winid, "norm! G") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - if exists("*getmousepos") - let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "gtagsExplManager._previewResult(False)" - elseif has('patch-8.1.2266') - call win_execute(a:winid, "exec v:mouse_lnum") - call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "gtagsExplManager._previewResult(False)" - endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "gtagsExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py "gtagsExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "gtagsExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "q" || key ==? "" - exec g:Lf_py "gtagsExplManager.quit()" - elseif key ==# "i" || key ==? "" - call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') - exec g:Lf_py "gtagsExplManager.input()" - elseif key ==# "o" || key ==? "" || key ==? "<2-LeftMouse>" - exec g:Lf_py "gtagsExplManager.accept()" - elseif key ==# "x" - exec g:Lf_py "gtagsExplManager.accept('h')" - elseif key ==# "v" - exec g:Lf_py "gtagsExplManager.accept('v')" - elseif key ==# "t" - exec g:Lf_py "gtagsExplManager.accept('t')" - elseif key ==# "p" - exec g:Lf_py "gtagsExplManager._previewResult(True)" - elseif key ==? "" - exec g:Lf_py "gtagsExplManager.toggleHelp()" - elseif key ==# "d" + if key ==# "d" exec g:Lf_py "gtagsExplManager.deleteCurrentLine()" - elseif key ==? "" - exec g:Lf_py "gtagsExplManager._toUpInPopup()" - elseif key ==? "" - exec g:Lf_py "gtagsExplManager._toDownInPopup()" + elseif key ==# "s" + exec g:Lf_py "gtagsExplManager.addSelections()" + elseif key ==# "a" + exec g:Lf_py "gtagsExplManager.selectAll()" + elseif key ==# "c" + exec g:Lf_py "gtagsExplManager.clearSelections()" + else + return leaderf#NormalModeFilter(g:Lf_PyEval("id(gtagsExplManager)"), a:winid, a:key) endif return 1 diff --git a/autoload/leaderf/Help.vim b/autoload/leaderf/Help.vim index 808d2749..568f6cd8 100644 --- a/autoload/leaderf/Help.vim +++ b/autoload/leaderf/Help.vim @@ -21,6 +21,13 @@ function! leaderf#Help#Maps() nnoremap x :exec g:Lf_py "helpExplManager.accept('h')" nnoremap v :exec g:Lf_py "helpExplManager.accept('v')" nnoremap t :exec g:Lf_py "helpExplManager.accept('t')" + nnoremap p :exec g:Lf_py "helpExplManager._previewResult(True)" + nnoremap j :exec g:Lf_py "helpExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "helpExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "helpExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "helpExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "helpExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "helpExplManager.moveAndPreview('PageDown')" nnoremap q :exec g:Lf_py "helpExplManager.quit()" " nnoremap :exec g:Lf_py "helpExplManager.quit()" nnoremap i :exec g:Lf_py "helpExplManager.input()" diff --git a/autoload/leaderf/History.vim b/autoload/leaderf/History.vim index 4316127f..a9794af4 100644 --- a/autoload/leaderf/History.vim +++ b/autoload/leaderf/History.vim @@ -29,84 +29,18 @@ function! leaderf#History#Maps() endfunction function! leaderf#History#NormalModeFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) + let key = leaderf#RemapKey(g:Lf_PyEval("id(historyExplManager)"), get(g:Lf_KeyMap, a:key, a:key)) - if key !=# "g" - call win_execute(a:winid, "let g:Lf_History_is_g_pressed = 0") - endif - - if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "historyExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "historyExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "historyExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "historyExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "g" - if get(g:, "Lf_History_is_g_pressed", 0) == 0 - let g:Lf_History_is_g_pressed = 1 - else - let g:Lf_History_is_g_pressed = 0 - call win_execute(a:winid, "norm! gg") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==# "G" - call win_execute(a:winid, "norm! G") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - if exists("*getmousepos") - let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - redraw - elseif has('patch-8.1.2266') - call win_execute(a:winid, "exec v:mouse_lnum") - call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "historyExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py "historyExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "historyExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "q" || key ==? "" - exec g:Lf_py "historyExplManager.quit()" - elseif key ==# "i" || key ==? "" - call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') - exec g:Lf_py "historyExplManager.input()" - elseif key ==# "o" || key ==? "" || key ==? "<2-LeftMouse>" - exec g:Lf_py "historyExplManager.accept()" - elseif key ==? "" - exec g:Lf_py "historyExplManager.toggleHelp()" - elseif key ==? "e" + if key ==# "x" + elseif key ==# "v" + elseif key ==# "t" + elseif key ==# "p" + elseif key ==? "" + elseif key ==? "" + elseif key ==# "e" exec g:Lf_py "historyExplManager.editHistory()" + else + return leaderf#NormalModeFilter(g:Lf_PyEval("id(historyExplManager)"), a:winid, a:key) endif return 1 diff --git a/autoload/leaderf/Jumps.vim b/autoload/leaderf/Jumps.vim index 9f2f6af4..8566bf0d 100644 --- a/autoload/leaderf/Jumps.vim +++ b/autoload/leaderf/Jumps.vim @@ -28,17 +28,15 @@ function! leaderf#Jumps#Maps() nnoremap :exec g:Lf_py "jumpsExplManager.toggleHelp()" nnoremap :exec g:Lf_py "jumpsExplManager.refresh()" nnoremap p :exec g:Lf_py "jumpsExplManager._previewResult(True)" - nnoremap j j:exec g:Lf_py "jumpsExplManager._previewResult(False)" - nnoremap k k:exec g:Lf_py "jumpsExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "jumpsExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "jumpsExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "jumpsExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "jumpsExplManager._previewResult(False)" - if has("nvim") - nnoremap :exec g:Lf_py "jumpsExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "jumpsExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "jumpsExplManager._closePreviewPopup()" - endif + nnoremap j :exec g:Lf_py "jumpsExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "jumpsExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "jumpsExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "jumpsExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "jumpsExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "jumpsExplManager.moveAndPreview('PageDown')" + nnoremap :exec g:Lf_py "jumpsExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "jumpsExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "jumpsExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "Jumps") for i in g:Lf_NormalMap["Jumps"] exec 'nnoremap '.i[0].' '.i[1] diff --git a/autoload/leaderf/Line.vim b/autoload/leaderf/Line.vim index 8497591f..21ac937b 100644 --- a/autoload/leaderf/Line.vim +++ b/autoload/leaderf/Line.vim @@ -27,19 +27,17 @@ function! leaderf#Line#Maps() nnoremap :exec g:Lf_py "lineExplManager.input()" nnoremap :exec g:Lf_py "lineExplManager.toggleHelp()" nnoremap p :exec g:Lf_py "lineExplManager._previewResult(True)" - nnoremap j j:exec g:Lf_py "lineExplManager._previewResult(False)" - nnoremap k k:exec g:Lf_py "lineExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "lineExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "lineExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "lineExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "lineExplManager._previewResult(False)" + nnoremap j :exec g:Lf_py "lineExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "lineExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "lineExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "lineExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "lineExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "lineExplManager.moveAndPreview('PageDown')" nnoremap Q :exec g:Lf_py "lineExplManager.outputToQflist()" nnoremap L :exec g:Lf_py "lineExplManager.outputToLoclist()" - if has("nvim") - nnoremap :exec g:Lf_py "lineExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "lineExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "lineExplManager._closePreviewPopup()" - endif + nnoremap :exec g:Lf_py "lineExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "lineExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "lineExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "Line") for i in g:Lf_NormalMap["Line"] exec 'nnoremap '.i[0].' '.i[1] @@ -48,104 +46,16 @@ function! leaderf#Line#Maps() endfunction function! leaderf#Line#NormalModeFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) - - if key !=# "g" - call win_execute(a:winid, "let g:Lf_Line_is_g_pressed = 0") - endif + let key = leaderf#RemapKey(g:Lf_PyEval("id(lineExplManager)"), get(g:Lf_KeyMap, a:key, a:key)) - if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "lineExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "lineExplManager._previewResult(False)" - elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "lineExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "lineExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "lineExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "lineExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "lineExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "lineExplManager._previewResult(False)" - elseif key ==# "g" - if get(g:, "Lf_Line_is_g_pressed", 0) == 0 - let g:Lf_Line_is_g_pressed = 1 - else - let g:Lf_Line_is_g_pressed = 0 - call win_execute(a:winid, "norm! gg") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==# "G" - call win_execute(a:winid, "norm! G") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - if exists("*getmousepos") - let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "lineExplManager._previewResult(False)" - elseif has('patch-8.1.2266') - call win_execute(a:winid, "exec v:mouse_lnum") - call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "lineExplManager._previewResult(False)" - endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "lineExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py "lineExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "lineExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "q" || key ==? "" - exec g:Lf_py "lineExplManager.quit()" - elseif key ==# "i" || key ==? "" - call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') - exec g:Lf_py "lineExplManager.input()" - elseif key ==# "o" || key ==? "" || key ==? "<2-LeftMouse>" - exec g:Lf_py "lineExplManager.accept()" - elseif key ==# "x" - exec g:Lf_py "lineExplManager.accept('h')" - elseif key ==# "v" - exec g:Lf_py "lineExplManager.accept('v')" - elseif key ==# "t" - exec g:Lf_py "lineExplManager.accept('t')" - elseif key ==# "p" - exec g:Lf_py "lineExplManager._previewResult(True)" - elseif key ==? "" - exec g:Lf_py "lineExplManager.toggleHelp()" - elseif key ==? "" - exec g:Lf_py "lineExplManager._toUpInPopup()" - elseif key ==? "" - exec g:Lf_py "lineExplManager._toDownInPopup()" + if key ==? "" + exec g:Lf_py "lineExplManager.refresh()" elseif key ==# "Q" exec g:Lf_py "lineExplManager.outputToQflist()" elseif key ==# "L" exec g:Lf_py "lineExplManager.outputToLoclist()" + else + return leaderf#NormalModeFilter(g:Lf_PyEval("id(lineExplManager)"), a:winid, a:key) endif return 1 diff --git a/autoload/leaderf/Mru.vim b/autoload/leaderf/Mru.vim index 82356f96..0a9070dc 100644 --- a/autoload/leaderf/Mru.vim +++ b/autoload/leaderf/Mru.vim @@ -31,17 +31,15 @@ function! leaderf#Mru#Maps() nnoremap a :exec g:Lf_py "mruExplManager.selectAll()" nnoremap c :exec g:Lf_py "mruExplManager.clearSelections()" nnoremap p :exec g:Lf_py "mruExplManager._previewResult(True)" - nnoremap j j:exec g:Lf_py "mruExplManager._previewResult(False)" - nnoremap k k:exec g:Lf_py "mruExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "mruExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "mruExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "mruExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "mruExplManager._previewResult(False)" - if has("nvim") - nnoremap :exec g:Lf_py "mruExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "mruExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "mruExplManager._closePreviewPopup()" - endif + nnoremap j :exec g:Lf_py "mruExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "mruExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "mruExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "mruExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "mruExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "mruExplManager.moveAndPreview('PageDown')" + nnoremap :exec g:Lf_py "mruExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "mruExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "mruExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "Mru") for i in g:Lf_NormalMap["Mru"] exec 'nnoremap '.i[0].' '.i[1] @@ -50,93 +48,9 @@ function! leaderf#Mru#Maps() endfunction function! leaderf#Mru#NormalModeFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) - - if key !=# "g" - call win_execute(a:winid, "let g:Lf_Mru_is_g_pressed = 0") - endif + let key = leaderf#RemapKey(g:Lf_PyEval("id(mruExplManager)"), get(g:Lf_KeyMap, a:key, a:key)) - if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "mruExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "mruExplManager._previewResult(False)" - elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "mruExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "mruExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "mruExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "mruExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "mruExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "mruExplManager._previewResult(False)" - elseif key ==# "g" - if get(g:, "Lf_Mru_is_g_pressed", 0) == 0 - let g:Lf_Mru_is_g_pressed = 1 - else - let g:Lf_Mru_is_g_pressed = 0 - call win_execute(a:winid, "norm! gg") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==# "G" - call win_execute(a:winid, "norm! G") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - if exists("*getmousepos") - let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "mruExplManager._previewResult(False)" - elseif has('patch-8.1.2266') - call win_execute(a:winid, "exec v:mouse_lnum") - call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "mruExplManager._previewResult(False)" - endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "mruExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py "mruExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "mruExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "q" || key ==? "" - exec g:Lf_py "mruExplManager.quit()" - elseif key ==# "i" || key ==? "" - call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') - exec g:Lf_py "mruExplManager.input()" - elseif key ==# "o" || key ==? "" || key ==? "<2-LeftMouse>" - exec g:Lf_py "mruExplManager.accept()" - elseif key ==# "x" - exec g:Lf_py "mruExplManager.accept('h')" - elseif key ==# "v" - exec g:Lf_py "mruExplManager.accept('v')" - elseif key ==# "t" - exec g:Lf_py "mruExplManager.accept('t')" - elseif key ==# "d" + if key ==# "d" exec g:Lf_py "mruExplManager.deleteMru()" elseif key ==# "s" exec g:Lf_py "mruExplManager.addSelections()" @@ -144,14 +58,8 @@ function! leaderf#Mru#NormalModeFilter(winid, key) abort exec g:Lf_py "mruExplManager.selectAll()" elseif key ==# "c" exec g:Lf_py "mruExplManager.clearSelections()" - elseif key ==# "p" - exec g:Lf_py "mruExplManager._previewResult(True)" - elseif key ==? "" - exec g:Lf_py "mruExplManager.toggleHelp()" - elseif key ==? "" - exec g:Lf_py "mruExplManager._toUpInPopup()" - elseif key ==? "" - exec g:Lf_py "mruExplManager._toDownInPopup()" + else + return leaderf#NormalModeFilter(g:Lf_PyEval("id(mruExplManager)"), a:winid, a:key) endif return 1 diff --git a/autoload/leaderf/QfLocList.vim b/autoload/leaderf/QfLocList.vim index 5b3916d5..656683d2 100644 --- a/autoload/leaderf/QfLocList.vim +++ b/autoload/leaderf/QfLocList.vim @@ -26,11 +26,9 @@ function! leaderf#QfLocList#Maps() nnoremap i :exec g:Lf_py "qfloclistExplManager.input()" nnoremap :exec g:Lf_py "qfloclistExplManager.input()" nnoremap :exec g:Lf_py "qfloclistExplManager.toggleHelp()" - if has("nvim") - nnoremap :exec g:Lf_py "qfloclistExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "qfloclistExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "qfloclistExplManager._closePreviewPopup()" - endif + nnoremap :exec g:Lf_py "qfloclistExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "qfloclistExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "qfloclistExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "QfLocList") for i in g:Lf_NormalMap["QfLocList"] exec 'nnoremap '.i[0].' '.i[1] @@ -40,95 +38,5 @@ endfunction function! leaderf#QfLocList#NormalModeFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) - - if key !=# "g" - call win_execute(a:winid, "let g:Lf_QfLocList_is_g_pressed = 0") - endif - - if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "qfloclistExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "qfloclistExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "qfloclistExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "qfloclistExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "g" - if get(g:, "Lf_QfLocList_is_g_pressed", 0) == 0 - let g:Lf_QfLocList_is_g_pressed = 1 - else - let g:Lf_QfLocList_is_g_pressed = 0 - call win_execute(a:winid, "norm! gg") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==# "G" - call win_execute(a:winid, "norm! G") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - if exists("*getmousepos") - let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - redraw - elseif has('patch-8.1.2266') - call win_execute(a:winid, "exec v:mouse_lnum") - call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "qfloclistExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py "qfloclistExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "qfloclistExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "q" || key ==? "" - exec g:Lf_py "qfloclistExplManager.quit()" - elseif key ==# "i" || key ==? "" - call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') - exec g:Lf_py "qfloclistExplManager.input()" - elseif key ==# "o" || key ==? "" || key ==? "<2-LeftMouse>" - exec g:Lf_py "qfloclistExplManager.accept()" - elseif key ==# "x" - exec g:Lf_py "qfloclistExplManager.accept('h')" - elseif key ==# "v" - exec g:Lf_py "qfloclistExplManager.accept('v')" - elseif key ==# "t" - exec g:Lf_py "qfloclistExplManager.accept('t')" - elseif key ==# "p" - exec g:Lf_py "qfloclistExplManager._previewResult(True)" - elseif key ==? "" - exec g:Lf_py "qfloclistExplManager.toggleHelp()" - elseif key ==? "" - exec g:Lf_py "qfloclistExplManager._toUpInPopup()" - elseif key ==? "" - exec g:Lf_py "qfloclistExplManager._toDownInPopup()" - endif - - return 1 + return leaderf#NormalModeFilter(g:Lf_PyEval("id(qfloclistExplManager)"), a:winid, a:key) endfunction diff --git a/autoload/leaderf/Rg.vim b/autoload/leaderf/Rg.vim index 6f14125d..436155d0 100644 --- a/autoload/leaderf/Rg.vim +++ b/autoload/leaderf/Rg.vim @@ -22,12 +22,12 @@ function! leaderf#Rg#Maps(heading) nnoremap v :exec g:Lf_py "rgExplManager.accept('v')" nnoremap t :exec g:Lf_py "rgExplManager.accept('t')" nnoremap p :exec g:Lf_py "rgExplManager._previewResult(True)" - nnoremap j j:exec g:Lf_py "rgExplManager._previewResult(False)" - nnoremap k k:exec g:Lf_py "rgExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "rgExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "rgExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "rgExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "rgExplManager._previewResult(False)" + nnoremap j :exec g:Lf_py "rgExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "rgExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "rgExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "rgExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "rgExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "rgExplManager.moveAndPreview('PageDown')" nnoremap q :exec g:Lf_py "rgExplManager.quit()" " nnoremap :exec g:Lf_py "rgExplManager.quit()" if a:heading == 0 @@ -42,11 +42,9 @@ function! leaderf#Rg#Maps(heading) nnoremap w :call leaderf#Rg#ApplyChangesAndSave(0) nnoremap W :call leaderf#Rg#ApplyChangesAndSave(1) nnoremap U :call leaderf#Rg#UndoLastChange() - if has("nvim") - nnoremap :exec g:Lf_py "rgExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "rgExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "rgExplManager._closePreviewPopup()" - endif + nnoremap :exec g:Lf_py "rgExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "rgExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "rgExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "Rg") for i in g:Lf_NormalMap["Rg"] exec 'nnoremap '.i[0].' '.i[1] @@ -60,7 +58,7 @@ function! leaderf#Rg#visual() let x_save = getreg("x", 1) let type = getregtype("x") norm! gv"xy - return '"' . escape(@x, '"') . '"' + return '"' . escape(@x, '\"') . '"' finally call setreg("x", x_save, type) endtry @@ -73,7 +71,7 @@ function! leaderf#Rg#getPattern(type) if a:type == 0 return expand('') elseif a:type == 1 - return escape(expand('')) + return '"' . escape(expand(''), '"') . '"' elseif a:type == 2 return leaderf#Rg#visual() else @@ -182,82 +180,9 @@ endfunction function! leaderf#Rg#NormalModeFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) - - if key !=# "g" - call win_execute(a:winid, "let g:Lf_Rg_is_g_pressed = 0") - endif + let key = leaderf#RemapKey(g:Lf_PyEval("id(rgExplManager)"), get(g:Lf_KeyMap, a:key, a:key)) - if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "rgExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "rgExplManager._previewResult(False)" - elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "rgExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "rgExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "rgExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "rgExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "rgExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "rgExplManager._previewResult(False)" - elseif key ==# "g" - if get(g:, "Lf_Rg_is_g_pressed", 0) == 0 - let g:Lf_Rg_is_g_pressed = 1 - else - let g:Lf_Rg_is_g_pressed = 0 - call win_execute(a:winid, "norm! gg") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==# "G" - call win_execute(a:winid, "norm! G") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - if exists("*getmousepos") - let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "rgExplManager._previewResult(False)" - elseif has('patch-8.1.2266') - call win_execute(a:winid, "exec v:mouse_lnum") - call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "rgExplManager._previewResult(False)" - endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "rgExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py "rgExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "rgExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "q" || key ==? "" - exec g:Lf_py "rgExplManager.quit()" - elseif key ==# "i" || key ==? "" + if key ==# "i" || key ==? "" if g:Lf_py == "py " let has_heading = pyeval("'--heading' in rgExplManager._arguments") else @@ -267,28 +192,14 @@ function! leaderf#Rg#NormalModeFilter(winid, key) abort call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') exec g:Lf_py "rgExplManager.input()" endif - elseif key ==# "o" || key ==? "" || key ==? "<2-LeftMouse>" - exec g:Lf_py "rgExplManager.accept()" - elseif key ==# "x" - exec g:Lf_py "rgExplManager.accept('h')" - elseif key ==# "v" - exec g:Lf_py "rgExplManager.accept('v')" - elseif key ==# "t" - exec g:Lf_py "rgExplManager.accept('t')" - elseif key ==# "p" - exec g:Lf_py "rgExplManager._previewResult(True)" - elseif key ==? "" - exec g:Lf_py "rgExplManager.toggleHelp()" elseif key ==# "d" exec g:Lf_py "rgExplManager.deleteCurrentLine()" elseif key ==# "Q" exec g:Lf_py "rgExplManager.outputToQflist()" elseif key ==# "L" exec g:Lf_py "rgExplManager.outputToLoclist()" - elseif key ==? "" - exec g:Lf_py "rgExplManager._toUpInPopup()" - elseif key ==? "" - exec g:Lf_py "rgExplManager._toDownInPopup()" + else + return leaderf#NormalModeFilter(g:Lf_PyEval("id(rgExplManager)"), a:winid, a:key) endif return 1 diff --git a/autoload/leaderf/Tag.vim b/autoload/leaderf/Tag.vim index 7156a991..8538e2d3 100644 --- a/autoload/leaderf/Tag.vim +++ b/autoload/leaderf/Tag.vim @@ -28,17 +28,15 @@ function! leaderf#Tag#Maps() nnoremap :exec g:Lf_py "tagExplManager.toggleHelp()" nnoremap :exec g:Lf_py "tagExplManager.refresh()" nnoremap p :exec g:Lf_py "tagExplManager._previewResult(True)" - nnoremap j j:exec g:Lf_py "tagExplManager._previewResult(False)" - nnoremap k k:exec g:Lf_py "tagExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "tagExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "tagExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "tagExplManager._previewResult(False)" - nnoremap :exec g:Lf_py "tagExplManager._previewResult(False)" - if has("nvim") - nnoremap :exec g:Lf_py "tagExplManager._toUpInPopup()" - nnoremap :exec g:Lf_py "tagExplManager._toDownInPopup()" - nnoremap :exec g:Lf_py "tagExplManager._closePreviewPopup()" - endif + nnoremap j :exec g:Lf_py "tagExplManager.moveAndPreview('j')" + nnoremap k :exec g:Lf_py "tagExplManager.moveAndPreview('k')" + nnoremap :exec g:Lf_py "tagExplManager.moveAndPreview('Up')" + nnoremap :exec g:Lf_py "tagExplManager.moveAndPreview('Down')" + nnoremap :exec g:Lf_py "tagExplManager.moveAndPreview('PageUp')" + nnoremap :exec g:Lf_py "tagExplManager.moveAndPreview('PageDown')" + nnoremap :exec g:Lf_py "tagExplManager._toUpInPopup()" + nnoremap :exec g:Lf_py "tagExplManager._toDownInPopup()" + nnoremap :exec g:Lf_py "tagExplManager.closePreviewPopupOrQuit()" if has_key(g:Lf_NormalMap, "Tag") for i in g:Lf_NormalMap["Tag"] exec 'nnoremap '.i[0].' '.i[1] diff --git a/autoload/leaderf/Window.vim b/autoload/leaderf/Window.vim index b6fab9fb..9716ee34 100644 --- a/autoload/leaderf/Window.vim +++ b/autoload/leaderf/Window.vim @@ -32,90 +32,18 @@ function! leaderf#Window#Maps() endfunction function! leaderf#Window#NormalModeFilter(winid, key) abort - let key = get(g:Lf_KeyDict, get(g:Lf_KeyMap, a:key, a:key), a:key) - - if key !=# "g" - call win_execute(a:winid, "let g:Lf_Window_is_g_pressed = 0") - endif - - if key ==# "j" || key ==? "" - call win_execute(a:winid, "norm! j") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "windowExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "windowExplManager._previewResult(False)" - elseif key ==# "k" || key ==? "" - call win_execute(a:winid, "norm! k") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - "redraw - exec g:Lf_py "windowExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "windowExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "windowExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "windowExplManager._previewResult(False)" - elseif key ==? "" || key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - exec g:Lf_py "windowExplManager._getInstance().refreshPopupStatusline()" - exec g:Lf_py "windowExplManager._previewResult(False)" - elseif key ==# "g" - if get(g:, "Lf_Window_is_g_pressed", 0) == 0 - let g:Lf_Window_is_g_pressed = 1 - else - let g:Lf_Window_is_g_pressed = 0 - call win_execute(a:winid, "norm! gg") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - redraw - endif - elseif key ==# "G" - call win_execute(a:winid, "norm! G") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - call win_execute(a:winid, "norm! \") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - redraw - elseif key ==? "" - if exists("*getmousepos") - let pos = getmousepos() - call win_execute(pos.winid, "call cursor([pos.line, pos.column])") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "windowExplManager._previewResult(False)" - elseif has('patch-8.1.2266') - call win_execute(a:winid, "exec v:mouse_lnum") - call win_execute(a:winid, "exec 'norm!'.v:mouse_col.'|'") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "windowExplManager._previewResult(False)" - endif - elseif key ==? "" - call win_execute(a:winid, "norm! 3k") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "windowExplManager._getInstance().refreshPopupStatusline()" - elseif key ==? "" - call win_execute(a:winid, "norm! 3j") - exec g:Lf_py "windowExplManager._cli._buildPopupPrompt()" - redraw - exec g:Lf_py "windowExplManager._getInstance().refreshPopupStatusline()" - elseif key ==# "q" || key ==? "" - exec g:Lf_py "windowExplManager.quit()" - elseif key ==# "i" || key ==? "" - call leaderf#ResetPopupOptions(a:winid, 'filter', 'leaderf#PopupFilter') - exec g:Lf_py "windowExplManager.input()" - elseif key ==# "o" || key ==? "" || key ==? "<2-LeftMouse>" - exec g:Lf_py "windowExplManager.accept()" - elseif key ==? "" - exec g:Lf_py "windowExplManager.toggleHelp()" - elseif key ==? "d" + let key = leaderf#RemapKey(g:Lf_PyEval("id(windowExplManager)"), get(g:Lf_KeyMap, a:key, a:key)) + + if key ==# "x" + elseif key ==# "v" + elseif key ==# "t" + elseif key ==# "p" + elseif key ==? "" + elseif key ==? "" + elseif key ==# "d" exec g:Lf_py "windowExplManager.deleteWindow()" + else + return leaderf#NormalModeFilter(g:Lf_PyEval("id(windowExplManager)"), a:winid, a:key) endif return 1 diff --git a/autoload/leaderf/colorscheme.vim b/autoload/leaderf/colorscheme.vim index f1056f92..88f0d898 100644 --- a/autoload/leaderf/colorscheme.vim +++ b/autoload/leaderf/colorscheme.vim @@ -14,8 +14,11 @@ endfunction let s:modeMap = { \ 'NameOnly': 'Lf_hl_stlNameOnlyMode', \ 'FullPath': 'Lf_hl_stlFullPathMode', + \ 'Contents': 'Lf_hl_stlNameOnlyMode', + \ 'WholeLine': 'Lf_hl_stlFullPathMode', \ 'Fuzzy': 'Lf_hl_stlFuzzyMode', - \ 'Regex': 'Lf_hl_stlRegexMode' + \ 'Regex': 'Lf_hl_stlRegexMode', + \ 'Live': 'Lf_hl_stlFuzzyMode' \ } let s:leftSep = { @@ -108,8 +111,8 @@ function! leaderf#colorscheme#highlight(category, bufnr) let right_guibg = synIDattr(sid_right, "bg", "gui") let right_ctermbg = synIDattr(sid_right, "bg", "cterm") let hiCmd = printf("hi Lf_hl_%s_%s", a:category, sep) - let hiCmd .= printf(" guifg=%s guibg=%s", left_guibg == '' ? 'NONE': left_guibg, right_guibg == '' ? 'NONE': right_guibg) - let hiCmd .= printf(" ctermfg=%s ctermbg=%s", left_ctermbg == '' ? 'NONE': left_ctermbg, right_ctermbg == '' ? 'NONE': right_ctermbg) + let hiCmd .= printf(" gui=nocombine guifg=%s guibg=%s", left_guibg == '' ? 'NONE': left_guibg, right_guibg == '' ? 'NONE': right_guibg) + let hiCmd .= printf(" cterm=nocombine ctermfg=%s ctermbg=%s", left_ctermbg == '' ? 'NONE': left_ctermbg, right_ctermbg == '' ? 'NONE': right_ctermbg) if get(g:Lf_StlSeparator, "font", "") != "" let hiCmd .= printf(" font='%s'", g:Lf_StlSeparator["font"]) endif @@ -124,8 +127,8 @@ function! leaderf#colorscheme#highlight(category, bufnr) let right_guibg = synIDattr(sid_right, "bg", "gui") let right_ctermbg = synIDattr(sid_right, "bg", "cterm") let hiCmd = printf("hi Lf_hl_%s_%s", a:category, sep) - let hiCmd .= printf(" guifg=%s guibg=%s", right_guibg == '' ? 'NONE': right_guibg, left_guibg == '' ? 'NONE': left_guibg) - let hiCmd .= printf(" ctermfg=%s ctermbg=%s", right_ctermbg == '' ? 'NONE': right_ctermbg, left_ctermbg == '' ? 'NONE': left_ctermbg) + let hiCmd .= printf(" gui=nocombine guifg=%s guibg=%s", right_guibg == '' ? 'NONE': right_guibg, left_guibg == '' ? 'NONE': left_guibg) + let hiCmd .= printf(" cterm=nocombine ctermfg=%s ctermbg=%s", right_ctermbg == '' ? 'NONE': right_ctermbg, left_ctermbg == '' ? 'NONE': left_ctermbg) if get(g:Lf_StlSeparator, "font", "") != "" let hiCmd .= printf(" font='%s'", g:Lf_StlSeparator["font"]) endif diff --git a/autoload/leaderf/colorscheme/default.vim b/autoload/leaderf/colorscheme/default.vim index c96a78fe..e83da67a 100644 --- a/autoload/leaderf/colorscheme/default.vim +++ b/autoload/leaderf/colorscheme/default.vim @@ -9,101 +9,101 @@ let s:palette = { \ 'stlName': { - \ 'gui': 'bold', + \ 'gui': 'bold,nocombine', \ 'font': 'NONE', \ 'guifg': '#2F5C00', \ 'guibg': '#BAFFA3', - \ 'cterm': 'bold', + \ 'cterm': 'bold,nocombine', \ 'ctermfg': '22', \ 'ctermbg': '157' \ }, \ 'stlCategory': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#000000', \ 'guibg': '#F28379', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '210' \ }, \ 'stlNameOnlyMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#000000', \ 'guibg': '#E8ED51', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '227' \ }, \ 'stlFullPathMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#000000', \ 'guibg': '#AAAAFF', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '147' \ }, \ 'stlFuzzyMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#000000', \ 'guibg': '#E8ED51', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '227' \ }, \ 'stlRegexMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#000000', \ 'guibg': '#7FECAD', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '121' \ }, \ 'stlCwd': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#EBFFEF', \ 'guibg': '#606168', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '195', \ 'ctermbg': '241' \ }, \ 'stlBlank': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': 'NONE', \ 'guibg': '#3B3E4C', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': 'NONE', \ 'ctermbg': '237' \ }, \ 'stlSpin': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#E6E666', \ 'guibg': '#3B3E4C', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '185', \ 'ctermbg': '237' \ }, \ 'stlLineInfo': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#000000', \ 'guibg': '#EBFFEF', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '195' \ }, \ 'stlTotal': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#000000', \ 'guibg': '#BCDC5C', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '149' \ } diff --git a/autoload/leaderf/colorscheme/gruvbox_material.vim b/autoload/leaderf/colorscheme/gruvbox_material.vim index c02146da..7cb443ad 100644 --- a/autoload/leaderf/colorscheme/gruvbox_material.vim +++ b/autoload/leaderf/colorscheme/gruvbox_material.vim @@ -11,101 +11,101 @@ if &background ==# 'light' let s:palette = { \ 'stlName': { - \ 'gui': 'bold', + \ 'gui': 'bold,nocombine', \ 'font': 'NONE', \ 'guifg': '#ebdbb2', \ 'guibg': '#7c6f64', - \ 'cterm': 'bold', + \ 'cterm': 'bold,nocombine', \ 'ctermfg': '223', \ 'ctermbg': '243' \ }, \ 'stlCategory': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#4f3829', \ 'guibg': '#bdae93', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '241', \ 'ctermbg': '248' \ }, \ 'stlNameOnlyMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#4f3829', \ 'guibg': '#d5c4a1', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '241', \ 'ctermbg': '250' \ }, \ 'stlFullPathMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#4f3829', \ 'guibg': '#d5c4a1', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '241', \ 'ctermbg': '250' \ }, \ 'stlFuzzyMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#4f3829', \ 'guibg': '#d5c4a1', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '241', \ 'ctermbg': '250' \ }, \ 'stlRegexMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#4f3829', \ 'guibg': '#d5c4a1', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '241', \ 'ctermbg': '250' \ }, \ 'stlCwd': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#4f3829', \ 'guibg': '#ebdbb2', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '241', \ 'ctermbg': '223' \ }, \ 'stlBlank': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#4f3829', \ 'guibg': '#ebdbb2', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '241', \ 'ctermbg': '223' \ }, \ 'stlSpin': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#984c93', \ 'guibg': '#ebdbb2', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '96', \ 'ctermbg': '223' \ }, \ 'stlLineInfo': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#4f3829', \ 'guibg': '#d5c4a1', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '241', \ 'ctermbg': '250' \ }, \ 'stlTotal': { - \ 'gui': 'bold', + \ 'gui': 'bold,nocombine', \ 'font': 'NONE', \ 'guifg': '#ebdbb2', \ 'guibg': '#7c6f64', - \ 'cterm': 'bold', + \ 'cterm': 'bold,nocombine', \ 'ctermfg': '223', \ 'ctermbg': '243' \ } @@ -113,101 +113,101 @@ if &background ==# 'light' else let s:palette = { \ 'stlName': { - \ 'gui': 'bold', + \ 'gui': 'bold,nocombine', \ 'font': 'NONE', \ 'guifg': '#282828', \ 'guibg': '#a89984', - \ 'cterm': 'bold', + \ 'cterm': 'bold,nocombine', \ 'ctermfg': '235', \ 'ctermbg': '246' \ }, \ 'stlCategory': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#ddc7a1', \ 'guibg': '#665c54', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '223', \ 'ctermbg': '241' \ }, \ 'stlNameOnlyMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#ddc7a1', \ 'guibg': '#504945', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '223', \ 'ctermbg': '239' \ }, \ 'stlFullPathMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#ddc7a1', \ 'guibg': '#504945', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '223', \ 'ctermbg': '239' \ }, \ 'stlFuzzyMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#ddc7a1', \ 'guibg': '#504945', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '223', \ 'ctermbg': '239' \ }, \ 'stlRegexMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#ddc7a1', \ 'guibg': '#504945', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '223', \ 'ctermbg': '239' \ }, \ 'stlCwd': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#ddc7a1', \ 'guibg': '#3c3836', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '223', \ 'ctermbg': '237' \ }, \ 'stlBlank': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#ddc7a1', \ 'guibg': '#3c3836', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '223', \ 'ctermbg': '237' \ }, \ 'stlSpin': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#E6E666', \ 'guibg': '#3c3836', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '185', \ 'ctermbg': '227' \ }, \ 'stlLineInfo': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#ddc7a1', \ 'guibg': '#665c54', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '223', \ 'ctermbg': '241' \ }, \ 'stlTotal': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#282828', \ 'guibg': '#a89984', - \ 'cterm': 'bold', + \ 'cterm': 'bold,nocombine', \ 'ctermfg': '235', \ 'ctermbg': '246' \ } diff --git a/autoload/leaderf/colorscheme/one.vim b/autoload/leaderf/colorscheme/one.vim index 40987352..52c0dd2f 100644 --- a/autoload/leaderf/colorscheme/one.vim +++ b/autoload/leaderf/colorscheme/one.vim @@ -10,101 +10,101 @@ let s:palette = { \ 'stlName': { - \ 'gui': 'bold', + \ 'gui': 'bold,nocombine', \ 'font': 'NONE', \ 'guifg': '#3E4452', \ 'guibg': '#98C379', - \ 'cterm': 'bold', + \ 'cterm': 'bold,nocombine', \ 'ctermfg': '16', \ 'ctermbg': '76' \ }, \ 'stlCategory': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#3E4452', \ 'guibg': '#E06C75', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '168' \ }, \ 'stlNameOnlyMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#3E4452', \ 'guibg': '#61AFEF', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '75' \ }, \ 'stlFullPathMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#3E4452', \ 'guibg': '#61AFEF', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '147' \ }, \ 'stlFuzzyMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#3E4452', \ 'guibg': '#E5C07B', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '180' \ }, \ 'stlRegexMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#3E4452', \ 'guibg': '#98C379', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '76' \ }, \ 'stlCwd': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#ABB2BF', \ 'guibg': '#475265', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '145', \ 'ctermbg': '236' \ }, \ 'stlBlank': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#ABB2BF', \ 'guibg': '#3E4452', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '145', \ 'ctermbg': '235' \ }, \ 'stlSpin': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#E6E666', \ 'guibg': '#3E4452', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '185', \ 'ctermbg': '235' \ }, \ 'stlLineInfo': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#3E4452', \ 'guibg': '#98C379', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '76' \ }, \ 'stlTotal': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#3E4452', \ 'guibg': '#98C379', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '76' \ } diff --git a/autoload/leaderf/colorscheme/onedark.vim b/autoload/leaderf/colorscheme/onedark.vim new file mode 100644 index 00000000..21371485 --- /dev/null +++ b/autoload/leaderf/colorscheme/onedark.vim @@ -0,0 +1,25 @@ +if &background ==? 'dark' + let s:palette = { + \ 'match': { 'guifg': '#E06C75', 'ctermfg': '204' }, + \ 'match0': { 'guifg': '#E06C75', 'ctermfg': '204' }, + \ 'match1': { 'guifg': '#D19A66', 'ctermfg': '173' }, + \ 'match2': { 'guifg': '#61AFEF', 'ctermfg': '39' }, + \ 'match3': { 'guifg': '#98C379', 'ctermfg': '114' }, + \ 'match4': { 'guifg': '#56B6C2', 'ctermfg': '38' }, + \ 'matchRefine': { 'guifg': '#D19A66', 'ctermfg': '173' }, + \ 'cursorline': { 'guifg': '#ABB2BF', 'ctermfg': '145' }, + \ 'stlName': { 'guifg': '#2C323C', 'ctermfg': '236', 'guibg': '#98C379', 'ctermbg': '114', 'gui': 'bold,nocombine', 'cterm': 'bold,nocombine' }, + \ 'stlCategory': { 'guifg': '#2C323C', 'ctermfg': '236', 'guibg': '#ABB2BF', 'ctermbg': '145', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlNameOnlyMode': { 'guifg': '#2C323C', 'ctermfg': '236', 'guibg': '#C678DD', 'ctermbg': '170', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlFullPathMode': { 'guifg': '#2C323C', 'ctermfg': '236', 'guibg': '#E5C07B', 'ctermbg': '180', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlFuzzyMode': { 'guifg': '#2C323C', 'ctermfg': '236', 'guibg': '#56B6C2', 'ctermbg': '38', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlRegexMode': { 'guifg': '#2C323C', 'ctermfg': '236', 'guibg': '#E06C75', 'ctermbg': '204', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlCwd': { 'guifg': '#ABB2BF', 'ctermfg': '145', 'guibg': '#3E4452', 'ctermbg': '237', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlBlank': { 'guifg': '#ABB2BF', 'ctermfg': '145', 'guibg': '#282C34', 'ctermbg': '235', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlLineInfo': { 'guifg': '#ABB2BF', 'ctermfg': '145', 'guibg': '#3E4452', 'ctermbg': '237', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlTotal': { 'guifg': '#2C323C', 'ctermfg': '236', 'guibg': '#98C379', 'ctermbg': '114', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlSpin': { 'guifg': '#ABB2BF', 'ctermfg': '145', 'guibg': '#282C34', 'ctermbg': '235', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ } +endif + +let g:leaderf#colorscheme#onedark#palette = leaderf#colorscheme#mergePalette(s:palette) diff --git a/autoload/leaderf/colorscheme/popup.vim b/autoload/leaderf/colorscheme/popup.vim index 7a7a8c1e..507f1776 100644 --- a/autoload/leaderf/colorscheme/popup.vim +++ b/autoload/leaderf/colorscheme/popup.vim @@ -10,8 +10,11 @@ let s:matchModeMap = { \ 'NameOnly': 'Lf_hl_popup_nameOnlyMode', \ 'FullPath': 'Lf_hl_popup_fullPathMode', + \ 'Contents': 'Lf_hl_popup_nameOnlyMode', + \ 'WholeLine': 'Lf_hl_popup_fullPathMode', \ 'Fuzzy': 'Lf_hl_popup_fuzzyMode', - \ 'Regex': 'Lf_hl_popup_regexMode' + \ 'Regex': 'Lf_hl_popup_regexMode', + \ 'Live': 'Lf_hl_popup_fuzzyMode' \ } let s:leftSep = { @@ -52,6 +55,20 @@ function! s:HighlightGroup(category, name) abort endif endfunction +function! s:SynIDattr(synID, what, ...) abort + if a:0 == 0 + let result = synIDattr(a:synID, a:what) + else + let result = synIDattr(a:synID, a:what, a:1) + endif + + if result == -1 + return '' + else + return result + endif +endfunction + function! s:HighlightSeperator(category) abort exec printf("highlight link Lf_hl_popup_%s_mode Lf_hl_popup_inputMode", a:category) exec printf("highlight link Lf_hl_popup_%s_matchMode %s", a:category, s:matchModeMap[g:Lf_DefaultMode]) @@ -62,10 +79,10 @@ function! s:HighlightSeperator(category) abort for [sep, dict] in items(s:leftSep) let sid_left = synIDtrans(hlID(s:HighlightGroup(a:category, dict.left))) let sid_right = synIDtrans(hlID(s:HighlightGroup(a:category, dict.right))) - let left_guibg = synIDattr(sid_left, "bg", "gui") - let left_ctermbg = synIDattr(sid_left, "bg", "cterm") - let right_guibg = synIDattr(sid_right, "bg", "gui") - let right_ctermbg = synIDattr(sid_right, "bg", "cterm") + let left_guibg = s:SynIDattr(sid_left, "bg", "gui") + let left_ctermbg = s:SynIDattr(sid_left, "bg", "cterm") + let right_guibg = s:SynIDattr(sid_right, "bg", "gui") + let right_ctermbg = s:SynIDattr(sid_right, "bg", "cterm") let hiCmd = printf("hi Lf_hl_popup_%s_%s", a:category, sep) let hiCmd .= printf(" guifg=%s guibg=%s", left_guibg == '' ? 'NONE': left_guibg, right_guibg == '' ? 'NONE': right_guibg) let hiCmd .= printf(" ctermfg=%s ctermbg=%s", left_ctermbg == '' ? 'NONE': left_ctermbg, right_ctermbg == '' ? 'NONE': right_ctermbg) @@ -80,10 +97,10 @@ function! s:HighlightSeperator(category) abort for [sep, dict] in items(s:rightSep) let sid_left = synIDtrans(hlID(s:HighlightGroup(a:category, dict.left))) let sid_right = synIDtrans(hlID(s:HighlightGroup(a:category, dict.right))) - let left_guibg = synIDattr(sid_left, "bg", "gui") - let left_ctermbg = synIDattr(sid_left, "bg", "cterm") - let right_guibg = synIDattr(sid_right, "bg", "gui") - let right_ctermbg = synIDattr(sid_right, "bg", "cterm") + let left_guibg = s:SynIDattr(sid_left, "bg", "gui") + let left_ctermbg = s:SynIDattr(sid_left, "bg", "cterm") + let right_guibg = s:SynIDattr(sid_right, "bg", "gui") + let right_ctermbg = s:SynIDattr(sid_right, "bg", "cterm") let hiCmd = printf("hi Lf_hl_popup_%s_%s", a:category, sep) let hiCmd .= printf(" guifg=%s guibg=%s", right_guibg == '' ? 'NONE': right_guibg, left_guibg == '' ? 'NONE': left_guibg) let hiCmd .= printf(" ctermfg=%s ctermbg=%s", right_ctermbg == '' ? 'NONE': right_ctermbg, left_ctermbg == '' ? 'NONE': left_ctermbg) @@ -96,31 +113,77 @@ function! s:HighlightSeperator(category) abort endfor endfunction +function! s:getSynAttr(sid) abort + let attr = "" + if s:SynIDattr(a:sid, "bold") + let attr = "bold" + endif + if s:SynIDattr(a:sid, "italic") + if attr == "" + let attr = "italic" + else + let attr = attr . ",italic" + endif + endif + if s:SynIDattr(a:sid, "standout") + if attr == "" + let attr = "standout" + else + let attr = attr . ",standout" + endif + endif + if s:SynIDattr(a:sid, "underline") + if attr == "" + let attr = "underline" + else + let attr = attr . ",underline" + endif + endif + if s:SynIDattr(a:sid, "undercurl") + if attr == "" + let attr = "undercurl" + else + let attr = attr . ",undercurl" + endif + endif + if s:SynIDattr(a:sid, "strike") + if attr == "" + let attr = "strike" + else + let attr = attr . ",strike" + endif + endif + if s:SynIDattr(a:sid, "nocombine") + if attr == "" + let attr = "nocombine" + else + let attr = attr . ",nocombine" + endif + endif + return attr +endfunction + " https://github.com/vim/vim/issues/5227 " same as `hi link` but filter out the `reverse` attribute function! leaderf#colorscheme#popup#link_no_reverse(from, to) abort let sid = synIDtrans(hlID(a:to)) - if synIDattr(sid, "reverse") || synIDattr(sid, "inverse") - let guibg = synIDattr(sid, "fg", "gui") - let guifg = synIDattr(sid, "bg", "gui") - let ctermbg = synIDattr(sid, "fg", "cterm") - let ctermfg = synIDattr(sid, "bg", "cterm") + if s:SynIDattr(sid, "reverse") || s:SynIDattr(sid, "inverse") + let guibg = s:SynIDattr(sid, "fg", "gui") + let guifg = s:SynIDattr(sid, "bg", "gui") + let ctermbg = s:SynIDattr(sid, "fg", "cterm") + let ctermfg = s:SynIDattr(sid, "bg", "cterm") else - let guibg = synIDattr(sid, "bg", "gui") - let guifg = synIDattr(sid, "fg", "gui") - let ctermbg = synIDattr(sid, "bg", "cterm") - let ctermfg = synIDattr(sid, "fg", "cterm") - endif - let bold = synIDattr(sid, "bold") ? "bold" : "" - let italic = synIDattr(sid, "italic") ? "italic" : "" - if bold == "" && italic == "" + let guibg = s:SynIDattr(sid, "bg", "gui") + let guifg = s:SynIDattr(sid, "fg", "gui") + let ctermbg = s:SynIDattr(sid, "bg", "cterm") + let ctermfg = s:SynIDattr(sid, "fg", "cterm") + endif + + let attr = s:getSynAttr(sid) + if attr == "" let attr = "gui=NONE cterm=NONE" - elseif bold == "bold" && italic == "italic" - let attr = "gui=bold,italic cterm=bold,italic" - elseif bold == "bold" - let attr = "gui=bold cterm=bold" else - let attr = "gui=italic cterm=italic" + let attr = "gui=" . attr . " cterm=" . attr endif let hiCmd = printf("hi def %s %s", a:from, attr) let hiCmd .= printf(" guifg=%s guibg=%s", guifg == '' ? 'NONE': guifg, guibg == '' ? 'NONE': guibg) @@ -128,6 +191,65 @@ function! leaderf#colorscheme#popup#link_no_reverse(from, to) abort exec hiCmd endfunction +" link to bg's background color and fg's foreground color +function! leaderf#colorscheme#popup#link_two(from, bg, fg, no_attr) abort + let bg_sid = synIDtrans(hlID(a:bg)) + if s:SynIDattr(bg_sid, "reverse") || s:SynIDattr(bg_sid, "inverse") + let guibg = s:SynIDattr(bg_sid, "fg", "gui") + let ctermbg = s:SynIDattr(bg_sid, "fg", "cterm") + else + let guibg = s:SynIDattr(bg_sid, "bg", "gui") + let ctermbg = s:SynIDattr(bg_sid, "bg", "cterm") + endif + + let fg_sid = synIDtrans(hlID(a:fg)) + if s:SynIDattr(fg_sid, "reverse") || s:SynIDattr(fg_sid, "inverse") + let guifg = s:SynIDattr(fg_sid, "bg", "gui") + if guifg == guibg + let guifg = s:SynIDattr(fg_sid, "fg", "gui") + endif + let ctermfg = s:SynIDattr(fg_sid, "bg", "cterm") + if ctermfg == ctermbg + let ctermfg = s:SynIDattr(fg_sid, "fg", "cterm") + endif + else + let guifg = s:SynIDattr(fg_sid, "fg", "gui") + if guifg == guibg + let guifg = s:SynIDattr(fg_sid, "bg", "gui") + endif + let ctermfg = s:SynIDattr(fg_sid, "fg", "cterm") + if ctermfg == ctermbg + let ctermfg = s:SynIDattr(fg_sid, "bg", "cterm") + endif + endif + + if a:no_attr + let attr = "gui=NONE cterm=NONE" + else + let attr = s:getSynAttr(sid) + if attr == "" + let attr = "gui=NONE cterm=NONE" + else + let attr = "gui=" . attr . "cterm=" . attr + endif + endif + let hiCmd = printf("hi def %s %s", a:from, attr) + let hiCmd .= printf(" guifg=%s guibg=%s", guifg == '' ? 'NONE': guifg, guibg == '' ? 'NONE': guibg) + let hiCmd .= printf(" ctermfg=%s ctermbg=%s", ctermfg == '' ? 'NONE': ctermfg, ctermbg == '' ? 'NONE': ctermbg) + exec hiCmd +endfunction + +" nvim has a weird bug, if `hi Cursor cterm=reverse gui=reverse` +" and `hi def link Lf_hl_popup_cursor Cursor`, the bug occurs. +function! leaderf#colorscheme#popup#link_cursor(from) abort + let sid = synIDtrans(hlID("Cursor")) + if s:SynIDattr(sid, "bg") == '' || s:SynIDattr(sid, "fg") == '' + exec printf("hi def %s gui=reverse guifg=NONE guibg=NONE cterm=reverse ctermfg=NONE ctermbg=NONE", a:from) + else + exec printf("hi def link %s Cursor", a:from) + endif +endfunction + " mode can be: " 1. INPUT mode " 2. NORMAL mode @@ -138,8 +260,8 @@ function! leaderf#colorscheme#popup#hiMode(category, mode) abort exec printf("hi link Lf_hl_popup_%s_mode Lf_hl_popup_inputMode", a:category) endif let sid = synIDtrans(hlID(printf("Lf_hl_popup_%s_mode", a:category))) - let guibg = synIDattr(sid, "bg", "gui") - let ctermbg = synIDattr(sid, "bg", "cterm") + let guibg = s:SynIDattr(sid, "bg", "gui") + let ctermbg = s:SynIDattr(sid, "bg", "cterm") exec printf("hi Lf_hl_popup_%s_sep0 guifg=%s", a:category, guibg == '' ? 'NONE': guibg) exec printf("hi Lf_hl_popup_%s_sep0 ctermfg=%s", a:category, ctermbg == '' ? 'NONE': ctermbg) endfunction @@ -151,8 +273,8 @@ endfunction " 4. Regex mode function! leaderf#colorscheme#popup#hiMatchMode(category, mode) abort let sid = synIDtrans(hlID(s:matchModeMap[a:mode])) - let guibg = synIDattr(sid, "bg", "gui") - let ctermbg = synIDattr(sid, "bg", "cterm") + let guibg = s:SynIDattr(sid, "bg", "gui") + let ctermbg = s:SynIDattr(sid, "bg", "cterm") exec printf("hi link Lf_hl_popup_%s_matchMode %s", a:category, s:matchModeMap[a:mode]) exec printf("hi Lf_hl_popup_%s_sep1 guibg=%s", a:category, guibg == '' ? 'NONE': guibg) exec printf("hi Lf_hl_popup_%s_sep1 ctermbg=%s", a:category, ctermbg == '' ? 'NONE': ctermbg) @@ -160,6 +282,38 @@ function! leaderf#colorscheme#popup#hiMatchMode(category, mode) abort exec printf("hi Lf_hl_popup_%s_sep2 ctermfg=%s", a:category, ctermbg == '' ? 'NONE': ctermbg) endfunction +" define Lf_hl_LineNr +function! s:hiDefineLineNr() abort + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_LineNr", "LineNr") + let sid = synIDtrans(hlID("Lf_hl_LineNr")) + if s:SynIDattr(sid, "nocombine") == "" + let attr = s:getSynAttr(sid) + if attr == "" + let attr = "nocombine" + else + let attr = attr . ",nocombine" + endif + + let guibg = s:SynIDattr(sid, "bg", "gui") + let ctermbg = s:SynIDattr(sid, "bg", "cterm") + if guibg == "" + let guibg = s:SynIDattr(synIDtrans(hlID("Normal")), "bg", "gui") + if guibg == "" + let guibg = "NONE" + endif + endif + if ctermbg == "" + let ctermbg = s:SynIDattr(synIDtrans(hlID("Normal")), "bg", "cterm") + if ctermbg == "" + let ctermbg = "NONE" + endif + endif + + let hiCmd = printf("hi Lf_hl_LineNr cterm=%s ctermbg=%s gui=%s guibg=%s", attr, ctermbg, attr, guibg) + exec hiCmd + endif +endfunction + function! s:AddPropType() abort silent! highlight def link Lf_hl_cursor Cursor silent! call prop_type_add("Lf_hl_popup_cursor", {'highlight': "Lf_hl_cursor", 'priority': 20}) @@ -178,6 +332,13 @@ function! s:AddPropType() abort silent! call prop_type_add("Lf_hl_popup_blank", {'highlight': "Lf_hl_popup_blank", 'priority': 20}) silent! call prop_type_add("Lf_hl_popup_lineInfo", {'highlight': "Lf_hl_popup_lineInfo", 'priority': 20}) silent! call prop_type_add("Lf_hl_popup_total", {'highlight': "Lf_hl_popup_total", 'priority': 20}) + silent! call prop_type_add("Lf_hl_gitDiffAdd", {'highlight': "Lf_hl_gitDiffAdd", 'priority': 20}) + silent! call prop_type_add("Lf_hl_gitDiffDelete", {'highlight': "Lf_hl_gitDiffDelete", 'priority': 20}) + silent! call prop_type_add("Lf_hl_gitDiffChange", {'highlight': "Lf_hl_gitDiffChange", 'priority': 21}) + silent! call prop_type_add("Lf_hl_gitDiffText", {'highlight': "Lf_hl_gitDiffText", 'priority': 22}) + silent! call prop_type_add("Lf_hl_LineNr", {'highlight': "Lf_hl_LineNr", 'priority': 20}) + silent! call prop_type_add("Lf_hl_gitTransparent", {'highlight': "Lf_hl_gitTransparent", 'priority': -2000}) + silent! call prop_type_add("Lf_hl_gitInlineBlame", {'highlight': "Comment", 'priority': 20}) endfunction " @@ -223,10 +384,15 @@ endfunction function! leaderf#colorscheme#popup#load(category, name) exec 'runtime autoload/leaderf/colorscheme/popup/'.a:name.'.vim' + " in case a:name does not exist + if a:name != "default" + exec 'runtime autoload/leaderf/colorscheme/popup/default.vim' + endif + + call s:hiDefineLineNr() if !has("nvim") call s:AddPropType() endif call s:LoadFromPalette() call s:HighlightSeperator(a:category) - call g:LfDefineDefaultColors() endfunction diff --git a/autoload/leaderf/colorscheme/popup/default.vim b/autoload/leaderf/colorscheme/popup/default.vim index 33639ccd..e70cf349 100644 --- a/autoload/leaderf/colorscheme/popup/default.vim +++ b/autoload/leaderf/colorscheme/popup/default.vim @@ -18,12 +18,17 @@ if &background ==? 'dark' highlight def Lf_hl_popup_inputText guifg=#87ceeb guibg=#4d4d4d gui=NONE ctermfg=117 ctermbg=239 cterm=NONE " Lf_hl_popup_window is the wincolor of content window - highlight def Lf_hl_popup_window guifg=#eeeeee guibg=#404040 gui=NONE ctermfg=255 ctermbg=237 cterm=NONE + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_window", "Normal") " Lf_hl_popup_blank is the wincolor of statusline window - highlight def Lf_hl_popup_blank guifg=NONE guibg=#4b4e50 gui=NONE ctermbg=239 cterm=NONE + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_blank", "StatusLineNC") + + " nvim has a weird bug, if `hi Cursor cterm=reverse gui=reverse` + " and `hi def link Lf_hl_popup_cursor Cursor`, the bug occurs. + call leaderf#colorscheme#popup#link_cursor("Lf_hl_popup_cursor") + + call leaderf#colorscheme#popup#link_two("Lf_hl_popupBorder", "Normal", "VertSplit", 1) - highlight def link Lf_hl_popup_cursor Cursor highlight def Lf_hl_popup_prompt guifg=#ffcd4a guibg=NONE gui=NONE ctermfg=221 ctermbg=NONE cterm=NONE highlight def Lf_hl_popup_spin guifg=#e6e666 guibg=NONE gui=NONE ctermfg=185 ctermbg=NONE cterm=NONE highlight def Lf_hl_popup_normalMode guifg=#333300 guibg=#c1ce96 gui=bold ctermfg=58 ctermbg=187 cterm=bold @@ -42,10 +47,10 @@ if &background ==? 'dark' highlight def Lf_hl_cursorline guifg=Yellow guibg=NONE gui=NONE ctermfg=226 ctermbg=NONE cterm=NONE " the color of matching character - highlight def Lf_hl_match guifg=SpringGreen guibg=NONE gui=bold ctermfg=85 ctermbg=NONE cterm=bold + highlight def Lf_hl_match guifg=#afff5f guibg=NONE gui=bold ctermfg=155 ctermbg=NONE cterm=bold " the color of matching character in `And mode` - highlight def Lf_hl_match0 guifg=SpringGreen guibg=NONE gui=bold ctermfg=85 ctermbg=NONE cterm=bold + highlight def Lf_hl_match0 guifg=#afff5f guibg=NONE gui=bold ctermfg=155 ctermbg=NONE cterm=bold highlight def Lf_hl_match1 guifg=#fe8019 guibg=NONE gui=bold ctermfg=208 ctermbg=NONE cterm=bold highlight def Lf_hl_match2 guifg=#3ff5d1 guibg=NONE gui=bold ctermfg=50 ctermbg=NONE cterm=bold highlight def Lf_hl_match3 guifg=#ff7272 guibg=NONE gui=bold ctermfg=203 ctermbg=NONE cterm=bold @@ -127,17 +132,67 @@ if &background ==? 'dark' highlight def link Lf_hl_loclistFileName Directory highlight def link Lf_hl_loclistLineNumber Constant highlight def link Lf_hl_loclistColumnNumber Constant + + highlight def link Lf_hl_jumpsTitle Title + highlight def link Lf_hl_jumpsNumber Number + highlight def link Lf_hl_jumpsLineCol String + highlight def link Lf_hl_jumpsIndicator Type + + highlight def Lf_hl_gitDiffModification guifg=#87afff guibg=NONE gui=NONE ctermfg=111 ctermbg=NONE cterm=NONE + highlight def Lf_hl_gitDiffAddition guifg=#a8e332 guibg=NONE gui=NONE ctermfg=148 ctermbg=NONE cterm=NONE + highlight def Lf_hl_gitDiffDeletion guifg=#ff477e guibg=NONE gui=NONE ctermfg=204 ctermbg=NONE cterm=NONE + highlight def link Lf_hl_gitHash Identifier + highlight def link Lf_hl_gitRefNames Special + highlight def link Lf_hl_gitFolder Directory + highlight def link Lf_hl_gitTitle Label + highlight def link Lf_hl_gitFilesNum Number + highlight def link Lf_hl_gitFolderIcon Special + highlight def link Lf_hl_gitAddIcon Lf_hl_gitDiffAddition + highlight def link Lf_hl_gitCopyIcon Number + highlight def link Lf_hl_gitDelIcon Lf_hl_gitDiffDeletion + highlight def link Lf_hl_gitModifyIcon Lf_hl_gitDiffModification + highlight def link Lf_hl_gitRenameIcon Constant + highlight def link Lf_hl_gitNumStatAdd Lf_hl_gitDiffAddition + highlight def link Lf_hl_gitNumStatDel Lf_hl_gitDiffDeletion + highlight def link Lf_hl_gitNumStatBinary Constant + highlight def link Lf_hl_gitHelp Comment + call leaderf#colorscheme#popup#link_two("Lf_hl_gitStlChangedNum", "StatusLine", "Number", 1) + highlight def link Lf_hl_gitStlFileChanged StatusLine + call leaderf#colorscheme#popup#link_two("Lf_hl_gitStlAdd", "StatusLine", "Lf_hl_gitDiffAddition", 1) + call leaderf#colorscheme#popup#link_two("Lf_hl_gitStlDel", "StatusLine", "Lf_hl_gitDiffDeletion", 1) + highlight def link Lf_hl_gitStatPath Directory + highlight def link Lf_hl_gitStatNumber Number + highlight def link Lf_hl_gitStatPlus Lf_hl_gitNumStatAdd + highlight def link Lf_hl_gitStatMinus Lf_hl_gitNumStatDel + highlight def link Lf_hl_gitGraph1 Statement + highlight def link Lf_hl_gitGraph2 String + highlight def link Lf_hl_gitGraph3 Special + highlight def link Lf_hl_gitGraph4 Lf_hl_gitGraph1 + highlight def link Lf_hl_gitGraphSlash Constant + highlight def link Lf_hl_gitBlameDate Number + highlight def link Lf_hl_gitSelectedOption Tag + highlight def link Lf_hl_gitNonSelectedOption Comment + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_gitDiffAdd", "DiffAdd") + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_gitDiffDelete", "DiffDelete") + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_gitDiffChange", "DiffChange") + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_gitDiffText", "DiffText") + highlight def link Lf_hl_gitInlineBlame Comment else " Lf_hl_popup_inputText is the wincolor of input window highlight def Lf_hl_popup_inputText guifg=#525252 guibg=#f4f3d7 gui=NONE ctermfg=239 ctermbg=230 cterm=NONE " Lf_hl_popup_window is the wincolor of content window - highlight def Lf_hl_popup_window guifg=#4d4d4d guibg=#fafbff gui=NONE ctermfg=239 ctermbg=231 cterm=NONE + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_window", "Normal") " Lf_hl_popup_blank is the wincolor of statusline window - highlight def Lf_hl_popup_blank guifg=NONE guibg=#eeecc1 gui=NONE ctermbg=230 cterm=NONE + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_blank", "StatusLineNC") + + " nvim has a weird bug, if `hi Cursor cterm=reverse gui=reverse` + " and `hi def link Lf_hl_popup_cursor Cursor`, the bug occurs. + call leaderf#colorscheme#popup#link_cursor("Lf_hl_popup_cursor") + + call leaderf#colorscheme#popup#link_two("Lf_hl_popupBorder", "Normal", "VertSplit", 1) - highlight def link Lf_hl_popup_cursor Cursor highlight def Lf_hl_popup_prompt guifg=#c77400 guibg=NONE gui=NONE ctermfg=172 cterm=NONE highlight def Lf_hl_popup_spin guifg=#f12d2d guibg=NONE gui=NONE ctermfg=196 cterm=NONE highlight def Lf_hl_popup_normalMode guifg=#808000 guibg=#ccc88e gui=bold ctermfg=100 ctermbg=186 cterm=bold @@ -241,4 +296,49 @@ else highlight def link Lf_hl_loclistFileName Directory highlight def link Lf_hl_loclistLineNumber Constant highlight def link Lf_hl_loclistColumnNumber Constant + + highlight def link Lf_hl_jumpsTitle Title + highlight def link Lf_hl_jumpsNumber Number + highlight def link Lf_hl_jumpsLineCol String + highlight def link Lf_hl_jumpsIndicator Type + + highlight def Lf_hl_gitDiffModification guifg=#5f87af guibg=NONE gui=NONE ctermfg=67 ctermbg=NONE cterm=NONE + highlight def Lf_hl_gitDiffAddition guifg=#008700 guibg=NONE gui=NONE ctermfg=28 ctermbg=NONE cterm=NONE + highlight def Lf_hl_gitDiffDeletion guifg=#df0000 guibg=NONE gui=NONE ctermfg=160 ctermbg=NONE cterm=NONE + highlight def link Lf_hl_gitHash Identifier + highlight def link Lf_hl_gitRefNames Special + highlight def link Lf_hl_gitFolder Directory + highlight def link Lf_hl_gitTitle Label + highlight def link Lf_hl_gitFilesNum Number + highlight def link Lf_hl_gitFolderIcon Special + highlight def link Lf_hl_gitAddIcon Lf_hl_gitDiffAddition + highlight def link Lf_hl_gitCopyIcon Number + highlight def link Lf_hl_gitDelIcon Lf_hl_gitDiffDeletion + highlight def link Lf_hl_gitModifyIcon Lf_hl_gitDiffModification + highlight def link Lf_hl_gitRenameIcon Constant + highlight def link Lf_hl_gitNumStatAdd Lf_hl_gitDiffAddition + highlight def link Lf_hl_gitNumStatDel Lf_hl_gitDiffDeletion + highlight def link Lf_hl_gitNumStatBinary Constant + highlight def link Lf_hl_gitHelp Comment + call leaderf#colorscheme#popup#link_two("Lf_hl_gitStlChangedNum", "StatusLine", "Number", 1) + highlight def link Lf_hl_gitStlFileChanged StatusLine + call leaderf#colorscheme#popup#link_two("Lf_hl_gitStlAdd", "StatusLine", "Lf_hl_gitDiffAddition", 1) + call leaderf#colorscheme#popup#link_two("Lf_hl_gitStlDel", "StatusLine", "Lf_hl_gitDiffDeletion", 1) + highlight def link Lf_hl_gitStatPath Directory + highlight def link Lf_hl_gitStatNumber Number + highlight def link Lf_hl_gitStatPlus Lf_hl_gitNumStatAdd + highlight def link Lf_hl_gitStatMinus Lf_hl_gitNumStatDel + highlight def link Lf_hl_gitGraph1 Statement + highlight def link Lf_hl_gitGraph2 String + highlight def link Lf_hl_gitGraph3 Special + highlight def link Lf_hl_gitGraph4 Lf_hl_gitGraph1 + highlight def link Lf_hl_gitGraphSlash Constant + highlight def link Lf_hl_gitBlameDate Number + highlight def link Lf_hl_gitSelectedOption Tag + highlight def link Lf_hl_gitNonSelectedOption Comment + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_gitDiffAdd", "DiffAdd") + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_gitDiffDelete", "DiffDelete") + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_gitDiffChange", "DiffChange") + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_gitDiffText", "DiffText") + highlight def link Lf_hl_gitInlineBlame Comment endif diff --git a/autoload/leaderf/colorscheme/popup/gruvbox_default.vim b/autoload/leaderf/colorscheme/popup/gruvbox_default.vim index b15a6537..f20d23ac 100644 --- a/autoload/leaderf/colorscheme/popup/gruvbox_default.vim +++ b/autoload/leaderf/colorscheme/popup/gruvbox_default.vim @@ -18,12 +18,17 @@ if &background ==? 'dark' highlight def Lf_hl_popup_inputText guifg=#dfebb2 guibg=#413d39 gui=NONE ctermfg=187 ctermbg=237 cterm=NONE " Lf_hl_popup_window is the wincolor of content window - highlight def Lf_hl_popup_window guifg=#ebdbb2 guibg=#323232 gui=NONE ctermfg=187 ctermbg=236 cterm=NONE + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_window", "Normal") " Lf_hl_popup_blank is the wincolor of statusline window - highlight def Lf_hl_popup_blank guifg=NONE guibg=#413d39 gui=NONE ctermbg=237 cterm=NONE + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_blank", "StatusLineNC") + + " nvim has a weird bug, if `hi Cursor cterm=reverse gui=reverse` + " and `hi def link Lf_hl_popup_cursor Cursor`, the bug occurs. + call leaderf#colorscheme#popup#link_cursor("Lf_hl_popup_cursor") + + call leaderf#colorscheme#popup#link_two("Lf_hl_popupBorder", "Normal", "VertSplit", 1) - highlight def link Lf_hl_popup_cursor Cursor highlight def Lf_hl_popup_prompt guifg=#fabd2f guibg=NONE gui=NONE ctermfg=214 ctermbg=NONE cterm=NONE highlight def Lf_hl_popup_spin guifg=#e6e666 guibg=NONE gui=NONE ctermfg=185 ctermbg=NONE cterm=NONE highlight def Lf_hl_popup_normalMode guifg=#282828 guibg=#a89984 gui=bold ctermfg=235 ctermbg=137 cterm=bold @@ -126,17 +131,27 @@ if &background ==? 'dark' highlight def link Lf_hl_loclistFileName Directory highlight def link Lf_hl_loclistLineNumber Constant highlight def link Lf_hl_loclistColumnNumber Constant + + highlight def link Lf_hl_jumpsTitle Title + highlight def link Lf_hl_jumpsNumber Number + highlight def link Lf_hl_jumpsLineCol String + highlight def link Lf_hl_jumpsIndicator Type else " Lf_hl_popup_inputText is the wincolor of input window highlight def Lf_hl_popup_inputText guifg=#504945 guibg=#faefb2 gui=NONE ctermfg=239 ctermbg=253 cterm=NONE " Lf_hl_popup_window is the wincolor of content window - highlight def Lf_hl_popup_window guifg=#665c54 guibg=#fcf4cf gui=NONE ctermfg=59 ctermbg=230 cterm=NONE + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_window", "Normal") " Lf_hl_popup_blank is the wincolor of statusline window - highlight def Lf_hl_popup_blank guifg=NONE guibg=#faefb2 gui=NONE ctermbg=253 cterm=NONE + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_blank", "StatusLineNC") + + " nvim has a weird bug, if `hi Cursor cterm=reverse gui=reverse` + " and `hi def link Lf_hl_popup_cursor Cursor`, the bug occurs. + call leaderf#colorscheme#popup#link_cursor("Lf_hl_popup_cursor") + + call leaderf#colorscheme#popup#link_two("Lf_hl_popupBorder", "Normal", "VertSplit", 1) - highlight def link Lf_hl_popup_cursor Cursor highlight def Lf_hl_popup_prompt guifg=#c77400 guibg=NONE gui=NONE ctermfg=172 cterm=NONE highlight def Lf_hl_popup_spin guifg=#f12d2d guibg=NONE gui=NONE ctermfg=196 cterm=NONE highlight def Lf_hl_popup_normalMode guifg=#fbf1c7 guibg=#a89984 gui=bold ctermfg=230 ctermbg=137 cterm=bold @@ -240,4 +255,9 @@ else highlight def link Lf_hl_loclistFileName Directory highlight def link Lf_hl_loclistLineNumber Constant highlight def link Lf_hl_loclistColumnNumber Constant + + highlight def link Lf_hl_jumpsTitle Title + highlight def link Lf_hl_jumpsNumber Number + highlight def link Lf_hl_jumpsLineCol String + highlight def link Lf_hl_jumpsIndicator Type endif diff --git a/autoload/leaderf/colorscheme/popup/gruvbox_material.vim b/autoload/leaderf/colorscheme/popup/gruvbox_material.vim index 948f8e4d..f617d5d7 100644 --- a/autoload/leaderf/colorscheme/popup/gruvbox_material.vim +++ b/autoload/leaderf/colorscheme/popup/gruvbox_material.vim @@ -10,15 +10,17 @@ if &background ==# 'dark' if get(g:, 'gruvbox_material_background', 'medium') ==# 'hard' highlight Lf_hl_popup_inputText guifg=#ddc7a1 guibg=#3c3836 gui=NONE ctermfg=223 ctermbg=237 cterm=NONE - highlight Lf_hl_popup_window guifg=#ddc7a1 guibg=#3c3836 gui=NONE ctermfg=223 ctermbg=237 cterm=NONE + "highlight Lf_hl_popup_window guifg=#ddc7a1 guibg=#3c3836 gui=NONE ctermfg=223 ctermbg=237 cterm=NONE elseif get(g:, 'gruvbox_material_background', 'medium') ==# 'medium' highlight Lf_hl_popup_inputText guifg=#ddc7a1 guibg=#504945 gui=NONE ctermfg=223 ctermbg=239 cterm=NONE - highlight Lf_hl_popup_window guifg=#ddc7a1 guibg=#504945 gui=NONE ctermfg=223 ctermbg=239 cterm=NONE + "highlight Lf_hl_popup_window guifg=#ddc7a1 guibg=#504945 gui=NONE ctermfg=223 ctermbg=239 cterm=NONE elseif get(g:, 'gruvbox_material_background', 'medium') ==# 'soft' highlight Lf_hl_popup_inputText guifg=#ddc7a1 guibg=#504945 gui=NONE ctermfg=223 ctermbg=239 cterm=NONE - highlight Lf_hl_popup_window guifg=#ddc7a1 guibg=#504945 gui=NONE ctermfg=223 ctermbg=239 cterm=NONE + "highlight Lf_hl_popup_window guifg=#ddc7a1 guibg=#504945 gui=NONE ctermfg=223 ctermbg=239 cterm=NONE endif + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_window", "Normal") + highlight Lf_hl_popup_prompt guifg=#89b482 ctermfg=108 highlight Lf_hl_popup_spin guifg=#d8a657 ctermfg=214 highlight Lf_hl_popup_normalMode guifg=#282828 guibg=#a89984 gui=bold ctermfg=235 ctermbg=246 cterm=bold @@ -34,7 +36,12 @@ if &background ==# 'dark' highlight Lf_hl_popup_lineInfo guifg=#ddc7a1 guibg=#665c54 gui=NONE ctermfg=223 ctermbg=241 cterm=NONE highlight Lf_hl_popup_total guifg=#282828 guibg=#a89984 gui=NONE ctermfg=235 ctermbg=246 cterm=NONE - highlight def link Lf_hl_popup_cursor Cursor + " nvim has a weird bug, if `hi Cursor cterm=reverse gui=reverse` + " and `hi def link Lf_hl_popup_cursor Cursor`, the bug occurs. + call leaderf#colorscheme#popup#link_cursor("Lf_hl_popup_cursor") + + call leaderf#colorscheme#popup#link_two("Lf_hl_popupBorder", "Normal", "VertSplit", 1) + " the color of the cursorline highlight def Lf_hl_cursorline guifg=#d4be98 guibg=NONE gui=NONE ctermfg=223 ctermbg=NONE cterm=NONE @@ -124,18 +131,25 @@ if &background ==# 'dark' highlight def link Lf_hl_loclistFileName Directory highlight def link Lf_hl_loclistLineNumber Constant highlight def link Lf_hl_loclistColumnNumber Constant + + highlight def link Lf_hl_jumpsTitle Title + highlight def link Lf_hl_jumpsNumber Number + highlight def link Lf_hl_jumpsLineCol String + highlight def link Lf_hl_jumpsIndicator Type else if get(g:, 'gruvbox_material_background', 'medium') ==# 'hard' highlight Lf_hl_popup_inputText guifg=#4f3829 guibg=#f2e5bc gui=NONE ctermfg=237 ctermbg=228 cterm=NONE - highlight Lf_hl_popup_window guifg=#4f3829 guibg=#f2e5bc gui=NONE ctermfg=237 ctermbg=228 cterm=NONE + "highlight Lf_hl_popup_window guifg=#4f3829 guibg=#f2e5bc gui=NONE ctermfg=237 ctermbg=228 cterm=NONE elseif get(g:, 'gruvbox_material_background', 'medium') ==# 'medium' highlight Lf_hl_popup_inputText guifg=#4f3829 guibg=#ebdbb2 gui=NONE ctermfg=237 ctermbg=223 cterm=NONE - highlight Lf_hl_popup_window guifg=#4f3829 guibg=#ebdbb2 gui=NONE ctermfg=237 ctermbg=223 cterm=NONE + "highlight Lf_hl_popup_window guifg=#4f3829 guibg=#ebdbb2 gui=NONE ctermfg=237 ctermbg=223 cterm=NONE elseif get(g:, 'gruvbox_material_background', 'medium') ==# 'soft' highlight Lf_hl_popup_inputText guifg=#4f3829 guibg=#d5c4a1 gui=NONE ctermfg=237 ctermbg=250 cterm=NONE - highlight Lf_hl_popup_window guifg=#4f3829 guibg=#d5c4a1 gui=NONE ctermfg=237 ctermbg=250 cterm=NONE + "highlight Lf_hl_popup_window guifg=#4f3829 guibg=#d5c4a1 gui=NONE ctermfg=237 ctermbg=250 cterm=NONE endif + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_window", "Normal") + highlight Lf_hl_popup_prompt guifg=#4c7a5d ctermfg=165 highlight Lf_hl_popup_spin guifg=#b47109 ctermfg=136 highlight Lf_hl_popup_normalMode guifg=#ebdbb2 guibg=#7c6f64 gui=bold ctermfg=235 ctermbg=243 cterm=bold @@ -151,7 +165,12 @@ else highlight Lf_hl_popup_lineInfo guifg=#4f3829 guibg=#bdae93 gui=NONE ctermfg=237 ctermbg=248 cterm=NONE highlight Lf_hl_popup_total guifg=#ebdbb2 guibg=#7c6f64 gui=NONE ctermfg=235 ctermbg=243 cterm=NONE - highlight def link Lf_hl_popup_cursor Cursor + " nvim has a weird bug, if `hi Cursor cterm=reverse gui=reverse` + " and `hi def link Lf_hl_popup_cursor Cursor`, the bug occurs. + call leaderf#colorscheme#popup#link_cursor("Lf_hl_popup_cursor") + + call leaderf#colorscheme#popup#link_two("Lf_hl_popupBorder", "Normal", "VertSplit", 1) + " the color of the cursorline highlight def Lf_hl_cursorline guifg=#654735 guibg=NONE gui=NONE ctermfg=237 ctermbg=NONE cterm=NONE @@ -241,5 +260,10 @@ else highlight def link Lf_hl_loclistFileName Directory highlight def link Lf_hl_loclistLineNumber Constant highlight def link Lf_hl_loclistColumnNumber Constant + + highlight def link Lf_hl_jumpsTitle Title + highlight def link Lf_hl_jumpsNumber Number + highlight def link Lf_hl_jumpsLineCol String + highlight def link Lf_hl_jumpsIndicator Type endif diff --git a/autoload/leaderf/colorscheme/popup/onedark.vim b/autoload/leaderf/colorscheme/popup/onedark.vim new file mode 100644 index 00000000..56381f66 --- /dev/null +++ b/autoload/leaderf/colorscheme/popup/onedark.vim @@ -0,0 +1,103 @@ +if &background ==? 'dark' + highlight def Lf_hl_popup_inputText guifg=#ABB2BF ctermfg=145 guibg=#3B4048 ctermbg=238 + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_window", "Normal") + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_blank", "StatusLineNC") + highlight def Lf_hl_popup_cursor guifg=#657b83 ctermfg=66 guibg=#98C379 ctermbg=114 + call leaderf#colorscheme#popup#link_two("Lf_hl_popupBorder", "Normal", "VertSplit", 1) + highlight def Lf_hl_popup_prompt guifg=#D19A66 ctermfg=173 guibg=#3B4048 ctermbg=238 gui=bold cterm=bold + highlight def Lf_hl_popup_spin guifg=#ABB2BF ctermfg=145 guibg=#3B4048 ctermbg=238 + highlight def Lf_hl_popup_normalMode guifg=#2C323C ctermfg=236 guibg=#98C379 ctermbg=114 gui=bold cterm=bold + highlight def Lf_hl_popup_inputMode guifg=#2C323C ctermfg=236 guibg=#61AFEF ctermbg=39 gui=bold cterm=bold + highlight def Lf_hl_popup_category guifg=#2C323C ctermfg=236 guibg=#ABB2BF ctermbg=145 + highlight def Lf_hl_popup_nameOnlyMode guifg=#2C323C ctermfg=236 guibg=#C678DD ctermbg=170 + highlight def Lf_hl_popup_fullPathMode guifg=#2C323C ctermfg=236 guibg=#E5C07B ctermbg=180 + highlight def Lf_hl_popup_fuzzyMode guifg=#2C323C ctermfg=236 guibg=#56B6C2 ctermbg=38 + highlight def Lf_hl_popup_regexMode guifg=#2C323C ctermfg=236 guibg=#E06C75 ctermbg=204 + highlight def Lf_hl_popup_cwd guifg=#ABB2BF ctermfg=145 guibg=#3E4452 ctermbg=237 + highlight def Lf_hl_popup_lineInfo guifg=#ABB2BF ctermfg=145 guibg=#3E4452 ctermbg=237 + highlight def Lf_hl_popup_total guifg=#2C323C ctermfg=236 guibg=#ABB2BF ctermbg=145 + + highlight def Lf_hl_cursorline guifg=#ABB2BF ctermfg=145 guibg=NONE ctermbg=NONE + + highlight def Lf_hl_match guifg=#E06C75 ctermfg=204 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match0 guifg=#E06C75 ctermfg=204 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match1 guifg=#D19A66 ctermfg=173 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match2 guifg=#61AFEF ctermfg=39 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match3 guifg=#98C379 ctermfg=114 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match4 guifg=#56B6C2 ctermfg=38 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_matchRefine guifg=#D19A66 ctermfg=173 + + highlight def Lf_hl_selection guifg=#2C323C ctermfg=236 guibg=#E5C07B ctermbg=180 gui=NONE cterm=NONE + + " the color of `Leaderf buffer` + highlight def link Lf_hl_bufNumber Constant + highlight def link Lf_hl_bufIndicators Statement + highlight def link Lf_hl_bufModified String + highlight def link Lf_hl_bufNomodifiable Comment + highlight def link Lf_hl_bufDirname Directory + + " the color of `Leaderf tag` + highlight def link Lf_hl_tagFile Directory + highlight def link Lf_hl_tagType Type + highlight def link Lf_hl_tagKeyword Keyword + + " the color of `Leaderf bufTag` + highlight def link Lf_hl_buftagKind Title + highlight def link Lf_hl_buftagScopeType Keyword + highlight def link Lf_hl_buftagScope Type + highlight def link Lf_hl_buftagDirname Directory + highlight def link Lf_hl_buftagLineNum Constant + highlight def link Lf_hl_buftagCode Comment + + " the color of `Leaderf function` + highlight def link Lf_hl_funcKind Title + highlight def link Lf_hl_funcReturnType Type + highlight def link Lf_hl_funcScope Keyword + highlight def link Lf_hl_funcName Function + highlight def link Lf_hl_funcDirname Directory + highlight def link Lf_hl_funcLineNum Constant + + " the color of `Leaderf line` + highlight def link Lf_hl_lineLocation Comment + + " the color of `Leaderf self` + highlight def link Lf_hl_selfIndex Constant + highlight def link Lf_hl_selfDescription Comment + + " the color of `Leaderf help` + highlight def link Lf_hl_helpTagfile Comment + + " the color of `Leaderf rg` + highlight def link Lf_hl_rgFileName Directory + highlight def link Lf_hl_rgLineNumber Constant + " the color of line number if '-A' or '-B' or '-C' is in the options list + " of `Leaderf rg` + highlight def link Lf_hl_rgLineNumber2 Folded + " the color of column number if '--column' in g:Lf_RgConfig + highlight def link Lf_hl_rgColumnNumber Constant + highlight def Lf_hl_rgHighlight guifg=#000000 guibg=#cccc66 gui=NONE ctermfg=16 ctermbg=185 cterm=NONE + + " the color of `Leaderf gtags` + highlight def link Lf_hl_gtagsFileName Directory + highlight def link Lf_hl_gtagsLineNumber Constant + highlight def Lf_hl_gtagsHighlight guifg=#000000 guibg=#cccc66 gui=NONE ctermfg=16 ctermbg=185 cterm=NONE + + highlight def link Lf_hl_previewTitle Statusline + + highlight def link Lf_hl_winNumber Constant + highlight def link Lf_hl_winIndicators Statement + highlight def link Lf_hl_winModified String + highlight def link Lf_hl_winNomodifiable Comment + highlight def link Lf_hl_winDirname Directory + highlight def link Lf_hl_quickfixFileName Directory + highlight def link Lf_hl_quickfixLineNumber Constant + highlight def link Lf_hl_quickfixColumnNumber Constant + highlight def link Lf_hl_loclistFileName Directory + highlight def link Lf_hl_loclistLineNumber Constant + highlight def link Lf_hl_loclistColumnNumber Constant + + highlight def link Lf_hl_jumpsTitle Title + highlight def link Lf_hl_jumpsNumber Number + highlight def link Lf_hl_jumpsLineCol String + highlight def link Lf_hl_jumpsIndicator Type +endif diff --git a/autoload/leaderf/colorscheme/popup/solarized.vim b/autoload/leaderf/colorscheme/popup/solarized.vim new file mode 100644 index 00000000..e860b91f --- /dev/null +++ b/autoload/leaderf/colorscheme/popup/solarized.vim @@ -0,0 +1,215 @@ +if &background ==? 'dark' + highlight def Lf_hl_popup_inputText guifg=#839496 ctermfg=102 guibg=#002b36 ctermbg=17 + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_window", "Normal") + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_blank", "StatusLineNC") + highlight def Lf_hl_popup_cursor guifg=#657b83 ctermfg=66 guibg=#93a1a1 ctermbg=109 + call leaderf#colorscheme#popup#link_two("Lf_hl_popupBorder", "Normal", "VertSplit", 1) + highlight def Lf_hl_popup_prompt guifg=#b58900 ctermfg=136 guibg=#002b36 ctermbg=17 gui=bold cterm=bold + highlight def Lf_hl_popup_spin guifg=#fdf6e3 ctermfg=230 guibg=#002b36 ctermbg=17 + highlight def Lf_hl_popup_normalMode guifg=#fdf6e3 ctermfg=230 guibg=#93a1a1 ctermbg=109 gui=bold cterm=bold + highlight def Lf_hl_popup_inputMode guifg=#fdf6e3 ctermfg=230 guibg=#b58900 ctermbg=136 gui=bold cterm=bold + highlight def Lf_hl_popup_category guifg=#eee8d5 ctermfg=224 guibg=#657b83 ctermbg=66 + highlight def Lf_hl_popup_nameOnlyMode guifg=#eee8d5 ctermfg=224 guibg=#268bd2 ctermbg=32 + highlight def Lf_hl_popup_fullPathMode guifg=#eee8d5 ctermfg=224 guibg=#586e75 ctermbg=60 + highlight def Lf_hl_popup_fuzzyMode guifg=#eee8d5 ctermfg=224 guibg=#586e75 ctermbg=60 + highlight def Lf_hl_popup_regexMode guifg=#eee8d5 ctermfg=224 guibg=#dc322f ctermbg=166 + highlight def Lf_hl_popup_cwd guifg=#93a1a1 ctermfg=109 guibg=#073642 ctermbg=23 + highlight def Lf_hl_popup_lineInfo guifg=#eee8d5 ctermfg=224 guibg=#657b83 ctermbg=66 + highlight def Lf_hl_popup_total guifg=#fdf6e3 ctermfg=230 guibg=#93a1a1 ctermbg=109 + + highlight def Lf_hl_cursorline guifg=#fdf6e3 ctermfg=230 + + highlight def Lf_hl_match guifg=#b58900 ctermfg=136 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match0 guifg=#d33682 ctermfg=168 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match1 guifg=#6c71c4 ctermfg=62 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match2 guifg=#268bd2 ctermfg=32 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match3 guifg=#2aa198 ctermfg=36 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match4 guifg=#859900 ctermfg=100 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_matchRefine guifg=#cb4b16 ctermfg=166 + + " the color of help in normal mode when is pressed + highlight def link Lf_hl_help Comment + highlight def link Lf_hl_helpCmd Identifier + + " the color when select multiple lines + highlight def Lf_hl_selection guifg=#4d4d4d guibg=#a5eb84 gui=NONE ctermfg=239 ctermbg=156 cterm=NONE + + " the color of `Leaderf buffer` + highlight def link Lf_hl_bufNumber Constant + highlight def link Lf_hl_bufIndicators Statement + highlight def link Lf_hl_bufModified String + highlight def link Lf_hl_bufNomodifiable Comment + highlight def link Lf_hl_bufDirname Directory + + " the color of `Leaderf tag` + highlight def link Lf_hl_tagFile Directory + highlight def link Lf_hl_tagType Type + highlight def link Lf_hl_tagKeyword Keyword + + " the color of `Leaderf bufTag` + highlight def link Lf_hl_buftagKind Title + highlight def link Lf_hl_buftagScopeType Keyword + highlight def link Lf_hl_buftagScope Type + highlight def link Lf_hl_buftagDirname Directory + highlight def link Lf_hl_buftagLineNum Constant + highlight def link Lf_hl_buftagCode Comment + + " the color of `Leaderf function` + highlight def link Lf_hl_funcKind Title + highlight def link Lf_hl_funcReturnType Type + highlight def link Lf_hl_funcScope Keyword + highlight def link Lf_hl_funcName Function + highlight def link Lf_hl_funcDirname Directory + highlight def link Lf_hl_funcLineNum Constant + + " the color of `Leaderf line` + highlight def link Lf_hl_lineLocation Comment + + " the color of `Leaderf self` + highlight def link Lf_hl_selfIndex Constant + highlight def link Lf_hl_selfDescription Comment + + " the color of `Leaderf help` + highlight def link Lf_hl_helpTagfile Comment + + " the color of `Leaderf rg` + highlight def link Lf_hl_rgFileName Directory + highlight def link Lf_hl_rgLineNumber Constant + " the color of line number if '-A' or '-B' or '-C' is in the options list + " of `Leaderf rg` + highlight def link Lf_hl_rgLineNumber2 Folded + " the color of column number if '--column' in g:Lf_RgConfig + highlight def link Lf_hl_rgColumnNumber Constant + highlight def Lf_hl_rgHighlight guifg=#4d4d4d guibg=#cccc66 gui=NONE ctermfg=239 ctermbg=185 cterm=NONE + + " the color of `Leaderf gtags` + highlight def link Lf_hl_gtagsFileName Directory + highlight def link Lf_hl_gtagsLineNumber Constant + highlight def Lf_hl_gtagsHighlight guifg=#4d4d4d guibg=#cccc66 gui=NONE ctermfg=239 ctermbg=185 cterm=NONE + + highlight def link Lf_hl_previewTitle Statusline + + highlight def link Lf_hl_winNumber Constant + highlight def link Lf_hl_winIndicators Statement + highlight def link Lf_hl_winModified String + highlight def link Lf_hl_winNomodifiable Comment + highlight def link Lf_hl_winDirname Directory + highlight def link Lf_hl_quickfixFileName Directory + highlight def link Lf_hl_quickfixLineNumber Constant + highlight def link Lf_hl_quickfixColumnNumber Constant + highlight def link Lf_hl_loclistFileName Directory + highlight def link Lf_hl_loclistLineNumber Constant + highlight def link Lf_hl_loclistColumnNumber Constant + + highlight def link Lf_hl_jumpsTitle Title + highlight def link Lf_hl_jumpsNumber Number + highlight def link Lf_hl_jumpsLineCol String + highlight def link Lf_hl_jumpsIndicator Type +else + highlight def Lf_hl_popup_inputText guifg=#657b83 ctermfg=66 guibg=#fdf6e3 ctermbg=230 + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_window", "Normal") + call leaderf#colorscheme#popup#link_no_reverse("Lf_hl_popup_blank", "StatusLineNC") + highlight def Lf_hl_popup_cursor guifg=#b58900 ctermfg=136 guibg=#586e75 ctermbg=60 + call leaderf#colorscheme#popup#link_two("Lf_hl_popupBorder", "Normal", "VertSplit", 1) + highlight def Lf_hl_popup_prompt guifg=#073642 ctermfg=23 guibg=#fdf6e3 ctermbg=230 gui=bold cterm=bold + highlight def Lf_hl_popup_spin guifg=#002b36 ctermfg=17 guibg=#fdf6e3 ctermbg=230 + highlight def Lf_hl_popup_normalMode guifg=#fdf6e3 ctermfg=230 guibg=#586e75 ctermbg=60 gui=bold cterm=bold + highlight def Lf_hl_popup_inputMode guifg=#fdf6e3 ctermfg=230 guibg=#b58900 ctermbg=136 gui=bold cterm=bold + highlight def Lf_hl_popup_category guifg=#eee8d5 ctermfg=224 guibg=#839496 ctermbg=102 + highlight def Lf_hl_popup_nameOnlyMode guifg=#eee8d5 ctermfg=224 guibg=#268bd2 ctermbg=32 + highlight def Lf_hl_popup_fullPathMode guifg=#eee8d5 ctermfg=224 guibg=#93a1a1 ctermbg=109 + highlight def Lf_hl_popup_fuzzyMode guifg=#eee8d5 ctermfg=224 guibg=#93a1a1 ctermbg=109 + highlight def Lf_hl_popup_regexMode guifg=#eee8d5 ctermfg=224 guibg=#dc322f ctermbg=166 + highlight def Lf_hl_popup_cwd guifg=#586e75 ctermfg=60 guibg=#eee8d5 ctermbg=224 + highlight def Lf_hl_popup_lineInfo guifg=#eee8d5 ctermfg=224 guibg=#839496 ctermbg=102 + highlight def Lf_hl_popup_total guifg=#fdf6e3 ctermfg=230 guibg=#586e75 ctermbg=60 + + highlight def Lf_hl_cursorline guifg=#002b36 ctermfg=17 + + highlight def Lf_hl_match guifg=#b58900 ctermfg=136 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match0 guifg=#d33682 ctermfg=168 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match1 guifg=#6c71c4 ctermfg=62 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match2 guifg=#268bd2 ctermfg=32 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match3 guifg=#2aa198 ctermfg=36 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_match4 guifg=#859900 ctermfg=100 guibg=NONE ctermbg=NONE gui=bold cterm=bold + highlight def Lf_hl_matchRefine guifg=#cb4b16 ctermfg=166 guibg=NONE ctermbg=NONE gui=bold cterm=bold + + " the color of help in normal mode when is pressed + highlight def link Lf_hl_help Comment + highlight def link Lf_hl_helpCmd Identifier + + " the color when select multiple lines + highlight def Lf_hl_selection guifg=#4d4d4d guibg=#a5eb84 gui=NONE ctermfg=239 ctermbg=156 cterm=NONE + + " the color of `Leaderf buffer` + highlight def link Lf_hl_bufNumber Constant + highlight def link Lf_hl_bufIndicators Statement + highlight def link Lf_hl_bufModified String + highlight def link Lf_hl_bufNomodifiable Comment + highlight def link Lf_hl_bufDirname Directory + + " the color of `Leaderf tag` + highlight def link Lf_hl_tagFile Directory + highlight def link Lf_hl_tagType Type + highlight def link Lf_hl_tagKeyword Keyword + + " the color of `Leaderf bufTag` + highlight def link Lf_hl_buftagKind Title + highlight def link Lf_hl_buftagScopeType Keyword + highlight def link Lf_hl_buftagScope Type + highlight def link Lf_hl_buftagDirname Directory + highlight def link Lf_hl_buftagLineNum Constant + highlight def link Lf_hl_buftagCode Comment + + " the color of `Leaderf function` + highlight def link Lf_hl_funcKind Title + highlight def link Lf_hl_funcReturnType Type + highlight def link Lf_hl_funcScope Keyword + highlight def link Lf_hl_funcName Function + highlight def link Lf_hl_funcDirname Directory + highlight def link Lf_hl_funcLineNum Constant + + " the color of `Leaderf line` + highlight def link Lf_hl_lineLocation Comment + + " the color of `Leaderf self` + highlight def link Lf_hl_selfIndex Constant + highlight def link Lf_hl_selfDescription Comment + + " the color of `Leaderf help` + highlight def link Lf_hl_helpTagfile Comment + + " the color of `Leaderf rg` + highlight def link Lf_hl_rgFileName Directory + highlight def link Lf_hl_rgLineNumber Constant + " the color of line number if '-A' or '-B' or '-C' is in the options list + " of `Leaderf rg` + highlight def link Lf_hl_rgLineNumber2 Folded + " the color of column number if '--column' in g:Lf_RgConfig + highlight def link Lf_hl_rgColumnNumber Constant + highlight def Lf_hl_rgHighlight guifg=#4d4d4d guibg=#cccc66 gui=NONE ctermfg=239 ctermbg=185 cterm=NONE + + " the color of `Leaderf gtags` + highlight def link Lf_hl_gtagsFileName Directory + highlight def link Lf_hl_gtagsLineNumber Constant + highlight def Lf_hl_gtagsHighlight guifg=#4d4d4d guibg=#cccc66 gui=NONE ctermfg=239 ctermbg=185 cterm=NONE + + highlight def link Lf_hl_previewTitle Statusline + + highlight def link Lf_hl_winNumber Constant + highlight def link Lf_hl_winIndicators Statement + highlight def link Lf_hl_winModified String + highlight def link Lf_hl_winNomodifiable Comment + highlight def link Lf_hl_winDirname Directory + highlight def link Lf_hl_quickfixFileName Directory + highlight def link Lf_hl_quickfixLineNumber Constant + highlight def link Lf_hl_quickfixColumnNumber Constant + highlight def link Lf_hl_loclistFileName Directory + highlight def link Lf_hl_loclistLineNumber Constant + highlight def link Lf_hl_loclistColumnNumber Constant + + highlight def link Lf_hl_jumpsTitle Title + highlight def link Lf_hl_jumpsNumber Number + highlight def link Lf_hl_jumpsLineCol String + highlight def link Lf_hl_jumpsIndicator Type +endif diff --git a/autoload/leaderf/colorscheme/powerline.vim b/autoload/leaderf/colorscheme/powerline.vim index e41d6771..fdb3c8d1 100644 --- a/autoload/leaderf/colorscheme/powerline.vim +++ b/autoload/leaderf/colorscheme/powerline.vim @@ -9,101 +9,101 @@ let s:palette = { \ 'stlName': { - \ 'gui': 'bold', + \ 'gui': 'bold,nocombine', \ 'font': 'NONE', \ 'guifg': '#005F00', \ 'guibg': '#AFDF00', - \ 'cterm': 'bold', + \ 'cterm': 'bold,nocombine', \ 'ctermfg': '22', \ 'ctermbg': '148' \ }, \ 'stlCategory': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#870000', \ 'guibg': '#FF8700', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '88', \ 'ctermbg': '208' \ }, \ 'stlNameOnlyMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#005D5D', \ 'guibg': '#FFFFFF', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '23', \ 'ctermbg': '231' \ }, \ 'stlFullPathMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#FFFFFF', \ 'guibg': '#FF2929', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '231', \ 'ctermbg': '196' \ }, \ 'stlFuzzyMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#004747', \ 'guibg': '#FFFFFF', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '23', \ 'ctermbg': '231' \ }, \ 'stlRegexMode': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#000000', \ 'guibg': '#7FECAD', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '16', \ 'ctermbg': '121' \ }, \ 'stlCwd': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#FFFFFF', \ 'guibg': '#585858', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '231', \ 'ctermbg': '240' \ }, \ 'stlBlank': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': 'NONE', \ 'guibg': '#303136', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': 'NONE', \ 'ctermbg': '236' \ }, \ 'stlSpin': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#E6E666', \ 'guibg': '#303136', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '185', \ 'ctermbg': '236' \ }, \ 'stlLineInfo': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#C9C9C9', \ 'guibg': '#585858', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '251', \ 'ctermbg': '240' \ }, \ 'stlTotal': { - \ 'gui': 'NONE', + \ 'gui': 'nocombine', \ 'font': 'NONE', \ 'guifg': '#545454', \ 'guibg': '#D0D0D0', - \ 'cterm': 'NONE', + \ 'cterm': 'nocombine', \ 'ctermfg': '240', \ 'ctermbg': '252' \ } diff --git a/autoload/leaderf/colorscheme/solarized.vim b/autoload/leaderf/colorscheme/solarized.vim new file mode 100644 index 00000000..aeba28ef --- /dev/null +++ b/autoload/leaderf/colorscheme/solarized.vim @@ -0,0 +1,45 @@ +if &background ==? 'dark' + let s:palette = { + \ 'match': { 'guifg': '#b58900', 'ctermfg': '136' }, + \ 'match0': { 'guifg': '#d33682', 'ctermfg': '168' }, + \ 'match1': { 'guifg': '#6c71c4', 'ctermfg': '62' }, + \ 'match2': { 'guifg': '#268bd2', 'ctermfg': '32' }, + \ 'match3': { 'guifg': '#2aa198', 'ctermfg': '36' }, + \ 'match4': { 'guifg': '#859900', 'ctermfg': '100' }, + \ 'matchRefine': { 'guifg': '#cb4b16', 'ctermfg': '166' }, + \ 'cursorline': { 'guifg': '#fdf6e3', 'ctermfg': '230' }, + \ 'stlName': { 'guifg': '#fdf6e3', 'ctermfg': '230', 'guibg': '#b58900', 'ctermbg': '136', 'gui': 'bold,nocombine', 'cterm': 'bold,nocombine' }, + \ 'stlCategory': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#657b83', 'ctermbg': '66', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlNameOnlyMode': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#268bd2', 'ctermbg': '32', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlFullPathMode': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#586e75', 'ctermbg': '60', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlFuzzyMode': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#586e75', 'ctermbg': '60', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlRegexMode': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#dc322f', 'ctermbg': '166', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlCwd': { 'guifg': '#93a1a1', 'ctermfg': '109', 'guibg': '#073642', 'ctermbg': '23', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlBlank': { 'guifg': '#93a1a1', 'ctermfg': '109', 'guibg': '#073642', 'ctermbg': '23', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlLineInfo': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#657b83', 'ctermbg': '66', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlTotal': { 'guifg': '#fdf6e3', 'ctermfg': '230', 'guibg': '#93a1a1', 'ctermbg': '109', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ } +else + let s:palette = { + \ 'match': { 'guifg': '#b58900', 'ctermfg': '136' }, + \ 'match0': { 'guifg': '#d33682', 'ctermfg': '168' }, + \ 'match1': { 'guifg': '#6c71c4', 'ctermfg': '62' }, + \ 'match2': { 'guifg': '#268bd2', 'ctermfg': '32' }, + \ 'match3': { 'guifg': '#2aa198', 'ctermfg': '36' }, + \ 'match4': { 'guifg': '#859900', 'ctermfg': '100' }, + \ 'matchRefine': { 'guifg': '#cb4b16', 'ctermfg': '166' }, + \ 'cursorline': { 'guifg': '#002b36', 'ctermfg': '17' }, + \ 'stlName': { 'guifg': '#fdf6e3', 'ctermfg': '230', 'guibg': '#b58900', 'ctermbg': '136', 'gui': 'bold,nocombine', 'cterm': 'bold,nocombine' }, + \ 'stlCategory': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#839496', 'ctermbg': '102', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlNameOnlyMode': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#268bd2', 'ctermbg': '32', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlFullPathMode': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#93a1a1', 'ctermbg': '109', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlFuzzyMode': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#93a1a1', 'ctermbg': '109', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlRegexMode': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#dc322f', 'ctermbg': '166', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlCwd': { 'guifg': '#586e75', 'ctermfg': '60', 'guibg': '#eee8d5', 'ctermbg': '224', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlBlank': { 'guifg': '#586e75', 'ctermfg': '60', 'guibg': '#eee8d5', 'ctermbg': '224', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlLineInfo': { 'guifg': '#eee8d5', 'ctermfg': '224', 'guibg': '#839496', 'ctermbg': '102', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ 'stlTotal': { 'guifg': '#fdf6e3', 'ctermfg': '230', 'guibg': '#586e75', 'ctermbg': '60', 'gui': 'nocombine', 'cterm': 'nocombine' }, + \ } +endif + +let g:leaderf#colorscheme#solarized#palette = leaderf#colorscheme#mergePalette(s:palette) diff --git a/autoload/leaderf/fuzzyMatch_C/fuzzyEngine.c b/autoload/leaderf/fuzzyMatch_C/fuzzyEngine.c index 24e391e3..d21b1685 100644 --- a/autoload/leaderf/fuzzyMatch_C/fuzzyEngine.c +++ b/autoload/leaderf/fuzzyMatch_C/fuzzyEngine.c @@ -689,7 +689,7 @@ static PyObject* createWeights(void* weights) * fuzzyMatch(engine, source, pattern, is_name_only=False, sort_results=True) * * `is_name_only` is optional, it defaults to `False`, which indicates using the full path matching algorithm. - * `sort_results` is optional, it defineds to `True`, which indicates whether to sort the results. + * `sort_results` is optional, it defaults to `True`, which indicates whether to sort the results. * * return a tuple, (a list of corresponding weight, a sorted list of items from `source` that match `pattern`). */ @@ -1863,6 +1863,7 @@ enum Category_File, Category_Gtags, Category_Line, + Category_GitDiff, }; typedef struct RgParameter @@ -1983,6 +1984,7 @@ static void rg_getDigest(char** str, uint32_t* length, RgParameter* param) { if ( *p == ':' ) { + minus = 0; ++colon; if ( (colon == 2 && !param->has_column) || colon == 3 ) { @@ -1993,6 +1995,7 @@ static void rg_getDigest(char** str, uint32_t* length, RgParameter* param) } else if ( *p == '-' ) { + colon = 0; ++minus; if ( minus == 2 ) { @@ -2001,6 +2004,11 @@ static void rg_getDigest(char** str, uint32_t* length, RgParameter* param) return; } } + else if ( !isdigit(*p) && colon + minus > 0 ) + { + colon = 0; + minus = 0; + } } } } @@ -2019,6 +2027,10 @@ static void rg_getDigest(char** str, uint32_t* length, RgParameter* param) return; } } + else if ( !isdigit(*p) && colon > 0 ) + { + colon = 0; + } } } } @@ -2119,11 +2131,23 @@ static void line_getDigest(char** str, uint32_t* length, Parameter* param) } } +static void gitdiff_getDigest(char** str, uint32_t* length, Parameter* param) +{ + if ( param->mode == 0 ) { + uint32_t len = 5; + *str += len; + *length -= len; + } + else { + file_getDigest(str, length, param); + } +} + /** * fuzzyMatchPart(engine, source, pattern, category, param, is_name_only=False, sort_results=True) * * `is_name_only` is optional, it defaults to `False`, which indicates using the full path matching algorithm. - * `sort_results` is optional, it defineds to `True`, which indicates whether to sort the results. + * `sort_results` is optional, it defaults to `True`, which indicates whether to sort the results. * * return a tuple, (a list of corresponding weight, a sorted list of items from `source` that match `pattern`). */ @@ -2303,6 +2327,9 @@ static PyObject* fuzzyEngine_fuzzyMatchPart(PyObject* self, PyObject* args, PyOb case Category_Line: line_getDigest(&s->str, &s->len, (Parameter*)PyCapsule_GetPointer(py_param, NULL)); break; + case Category_GitDiff: + gitdiff_getDigest(&s->str, &s->len, (Parameter*)PyCapsule_GetPointer(py_param, NULL)); + break; } } @@ -2556,6 +2583,12 @@ PyMODINIT_FUNC PyInit_fuzzyEngine(void) return NULL; } + if ( PyModule_AddObject(module, "Category_GitDiff", Py_BuildValue("I", Category_GitDiff)) ) + { + Py_DECREF(module); + return NULL; + } + return module; } @@ -2599,6 +2632,11 @@ PyMODINIT_FUNC initfuzzyEngine(void) return; } + if ( PyModule_AddObject(module, "Category_GitDiff", Py_BuildValue("I", Category_GitDiff)) ) + { + Py_DECREF(module); + return; + } } #endif diff --git a/autoload/leaderf/fuzzyMatch_C/fuzzyEngine.cpp b/autoload/leaderf/fuzzyMatch_C/fuzzyEngine.cpp new file mode 100644 index 00000000..d21b1685 --- /dev/null +++ b/autoload/leaderf/fuzzyMatch_C/fuzzyEngine.cpp @@ -0,0 +1,2643 @@ +/** + * Copyright (C) 2018 Yggdroot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PY_SSIZE_T_CLEAN + #define PY_SSIZE_T_CLEAN +#endif + +#include +#include +#include +#include + +#if defined(_MSC_VER) +#include +#else +#include +#endif + +#include "fuzzyEngine.h" +#include "fuzzyMatch.h" + +typedef float weight_t; + +typedef struct FeString +{ + char* str; + uint32_t len; +}FeString; + +typedef struct TaskItem +{ + uint32_t function; + uint32_t offset; + uint32_t length; +}TaskItem; + +typedef struct FeResult +{ + union + { + weight_t weight; + uint32_t path_weight; + }; + uint32_t index; +}FeResult; + +typedef struct MergeTaskItem +{ + uint32_t function; + uint32_t offset_1; + uint32_t length_1; + /* offset_2 = offset_1 + length_1, so no need to define it */ + uint32_t length_2; + /* a buffer that helps merge two list, its length is `length_2` */ + FeResult* buffer; +}MergeTaskItem; + +typedef struct PySetTaskItem +{ + uint32_t function; + uint32_t offset; + uint32_t length; + union + { + weight_t* weights; + uint32_t* path_weights; + }; + PyObject* text_list; + PyObject* py_source; +}PySetTaskItem; + +typedef struct FeCircularQueue +{ + void** buffer; + uint32_t capacity; + uint32_t head; + uint32_t tail; +#if defined(_MSC_VER) + uint32_t done_num; + uint32_t task_count; + CRITICAL_SECTION cs; + HANDLE task_sem; + HANDLE all_done_event; +#else + uint32_t unfinished_tasks; + pthread_mutex_t mutex; + pthread_cond_t not_empty_cond; + pthread_cond_t task_cond; +#endif +}FeCircularQueue; + +struct FuzzyEngine +{ + uint32_t cpu_count; +#if defined(_MSC_VER) + HANDLE* threads; +#else + pthread_t* threads; +#endif + union + { + struct + { + PatternContext* pPattern_ctxt; + uint8_t is_name_only; + }; + struct + { + const char* filename; + const char* suffix; + const char* dirname; + }; + }; + FeString* source; + union + { + FeResult* results; + HighlightGroup** highlights; + }; + FeCircularQueue task_queue; +}; + +#if defined(_MSC_VER) + +#define QUEUE_INIT(queue, queue_capacity, ret_val) \ + do { \ + (queue).buffer = (void**)calloc((queue_capacity), sizeof(void*)); \ + if ( !(queue).buffer ) \ + { \ + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); \ + ret_val = -1; \ + break; \ + } \ + (queue).capacity = (queue_capacity); \ + (queue).head = (queue).tail = 0; \ + (queue).done_num = 0; \ + (queue).task_count = 0; \ + InitializeCriticalSection(&(queue).cs); \ + (queue).task_sem = CreateSemaphore(NULL, 0, 2048, NULL); \ + if ( !(queue).task_sem ) \ + { \ + fprintf(stderr, "CreateSemaphore error: %d\n", GetLastError()); \ + ret_val = -1; \ + break; \ + } \ + /* auto-reset event */ \ + (queue).all_done_event = CreateEvent(NULL, FALSE, FALSE, NULL); \ + if ( !(queue).all_done_event ) \ + { \ + fprintf(stderr, "CreateEvent error: %d\n", GetLastError()); \ + ret_val = -1; \ + break; \ + } \ + } while(0) + +#define QUEUE_SET_TASK_COUNT(queue, count) \ + do { \ + EnterCriticalSection(&(queue).cs); \ + (queue).task_count = (count); \ + LeaveCriticalSection(&(queue).cs); \ + } while(0) + +#define QUEUE_DESTROY(queue) \ + do { \ + free((queue).buffer); \ + DeleteCriticalSection(&(queue).cs); \ + CloseHandle((queue).task_sem); \ + CloseHandle((queue).all_done_event); \ + } while(0) + +#define QUEUE_PUT(queue, pTask) \ + do { \ + EnterCriticalSection(&(queue).cs); \ + (queue).buffer[(queue).tail] = (void*)(pTask); \ + (queue).tail = ((queue).tail + 1) % (queue).capacity; \ + LeaveCriticalSection(&(queue).cs); \ + if ( !ReleaseSemaphore((queue).task_sem, 1, NULL) ) \ + { \ + fprintf(stderr, "ReleaseSemaphore error: %d\n", GetLastError()); \ + break; \ + } \ + } while(0) + +#define QUEUE_GET(queue, type, pTask) \ + do { \ + if ( WaitForSingleObject((queue).task_sem, INFINITE) == WAIT_FAILED ) \ + { \ + fprintf(stderr, "WaitForSingleObject error: %d\n", GetLastError()); \ + break; \ + } \ + EnterCriticalSection(&(queue).cs); \ + pTask = (type)(queue).buffer[(queue).head]; \ + (queue).head = ((queue).head + 1) % (queue).capacity; \ + LeaveCriticalSection(&(queue).cs); \ + } while(0) + +#define QUEUE_JOIN(queue) \ + do { \ + if ( WaitForSingleObject((queue).all_done_event, INFINITE) == WAIT_FAILED ) \ + { \ + fprintf(stderr, "WaitForSingleObject error: %d\n", GetLastError()); \ + break; \ + } \ + EnterCriticalSection(&(queue).cs); \ + (queue).done_num = 0; \ + (queue).task_count = 0; \ + LeaveCriticalSection(&(queue).cs); \ + } while(0) + +#define QUEUE_TASK_DONE(queue) \ + do { \ + EnterCriticalSection(&(queue).cs); \ + ++(queue).done_num; \ + if ( (queue).done_num >= (queue).task_count ) \ + SetEvent((queue).all_done_event); \ + LeaveCriticalSection(&(queue).cs); \ + } while(0) + +#else + +#define QUEUE_INIT(queue, queue_capacity, ret_val) \ + do { \ + (queue).buffer = (void**)calloc((queue_capacity), sizeof(void*)); \ + if ( !(queue).buffer ) \ + { \ + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); \ + ret_val = -1; \ + break; \ + } \ + (queue).capacity = (queue_capacity); \ + (queue).head = (queue).tail = 0; \ + (queue).unfinished_tasks = 0; \ + int ret; \ + ret = pthread_mutex_init(&(queue).mutex, NULL); \ + if ( ret != 0 ) \ + { \ + fprintf(stderr, "pthread_mutex_init error!\n"); \ + ret_val = -1; \ + break; \ + } \ + ret = pthread_cond_init(&(queue).not_empty_cond, NULL); \ + if ( ret != 0 ) \ + { \ + fprintf(stderr, "pthread_cond_init error!\n"); \ + ret_val = -1; \ + break; \ + } \ + ret = pthread_cond_init(&(queue).task_cond, NULL); \ + if ( ret != 0 ) \ + { \ + fprintf(stderr, "pthread_cond_init error!\n"); \ + ret_val = -1; \ + break; \ + } \ + } while(0) + +#define QUEUE_DESTROY(queue) \ + do { \ + free((queue).buffer); \ + pthread_mutex_destroy(&(queue).mutex); \ + pthread_cond_destroy(&(queue).not_empty_cond); \ + pthread_cond_destroy(&(queue).task_cond); \ + } while(0) + +#define QUEUE_PUT(queue, pTask) \ + do { \ + pthread_mutex_lock(&(queue).mutex); \ + (queue).buffer[(queue).tail] = (void*)(pTask); \ + (queue).tail = ((queue).tail + 1) % (queue).capacity; \ + ++(queue).unfinished_tasks; \ + pthread_cond_signal(&(queue).not_empty_cond); \ + pthread_mutex_unlock(&(queue).mutex); \ + } while(0) + +#define QUEUE_GET(queue, type, pTask) \ + do { \ + pthread_mutex_lock(&(queue).mutex); \ + while ( (queue).head == (queue).tail ) \ + { \ + pthread_cond_wait(&(queue).not_empty_cond, &(queue).mutex); \ + } \ + pTask = (type)(queue).buffer[(queue).head]; \ + (queue).head = ((queue).head + 1) % (queue).capacity; \ + pthread_mutex_unlock(&(queue).mutex); \ + } while(0) + +#define QUEUE_JOIN(queue) \ + do { \ + pthread_mutex_lock(&(queue).mutex); \ + while ( (queue).unfinished_tasks > 0 ) \ + { \ + pthread_cond_wait(&(queue).task_cond, &(queue).mutex); \ + } \ + (queue).unfinished_tasks = 0; \ + pthread_mutex_unlock(&(queue).mutex); \ + } while(0) + +#define QUEUE_TASK_DONE(queue) \ + do { \ + pthread_mutex_lock(&(queue).mutex); \ + --(queue).unfinished_tasks; \ + if ( (queue).unfinished_tasks <= 0 ) \ + pthread_cond_signal(&(queue).task_cond); \ + pthread_mutex_unlock(&(queue).mutex); \ + } while(0) + +#endif + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define MAX_TASK_COUNT(cpu_count) ((cpu_count) << 3) + +enum +{ + GET_WEIGHT = 0, + GET_HIGHLIGHTS, + GET_PATH_WEIGHT, + Q_SORT, + Q_SORT_2, + MERGE, + MERGE_2, + PY_SET_ITEM, + PY_SET_ITEM_2 +}; + +/* sort in descending order */ +static int compare(const void* a, const void* b) +{ + weight_t wa = ((const FeResult*)a)->weight; + weight_t wb = ((const FeResult*)b)->weight; + return (wa < wb) - (wa > wb); +} + +/* sort in descending order */ +static int compare2(const void* a, const void* b) +{ + uint32_t wa = ((const FeResult*)a)->path_weight; + uint32_t wb = ((const FeResult*)b)->path_weight; + + return (int)wb - (int)wa; +} + +#if defined(_MSC_VER) +static DWORD WINAPI _worker(LPVOID pParam) +#else +static void* _worker(void* pParam) +#endif +{ + FuzzyEngine* pEngine = (FuzzyEngine*)pParam; + + while ( 1 ) + { + TaskItem* pTask = NULL; + QUEUE_GET(pEngine->task_queue, TaskItem*, pTask); + + if ( pTask ) + { + switch ( pTask->function ) + { + case GET_WEIGHT: + { + FeString* tasks = pEngine->source + pTask->offset; + FeResult* results = pEngine->results + pTask->offset; + uint32_t length = pTask->length; + uint32_t i = 0; + for ( ; i < length; ++i ) + { + results[i].weight = getWeight(tasks[i].str, tasks[i].len, + pEngine->pPattern_ctxt, pEngine->is_name_only); + results[i].index = pTask->offset + i; + } + } + break; + case GET_HIGHLIGHTS: + { + FeString* tasks = pEngine->source + pTask->offset; + HighlightGroup** results = pEngine->highlights + pTask->offset; + uint32_t length = pTask->length; + uint32_t i = 0; + for ( ; i < length; ++i ) + { + results[i] = getHighlights(tasks[i].str, tasks[i].len, + pEngine->pPattern_ctxt, pEngine->is_name_only); + } + } + break; + case GET_PATH_WEIGHT: + { + FeString* tasks = pEngine->source + pTask->offset; + FeResult* results = pEngine->results + pTask->offset; + uint32_t length = pTask->length; + uint32_t i = 0; + for ( ; i < length; ++i ) + { + results[i].path_weight = getPathWeight(pEngine->filename, pEngine->suffix, pEngine->dirname, tasks[i].str, tasks[i].len); + results[i].index = pTask->offset + i; + } + } + break; + case Q_SORT: + { + FeResult* tasks = pEngine->results + pTask->offset; + qsort(tasks, pTask->length, sizeof(FeResult), compare); + } + break; + case Q_SORT_2: + { + FeResult* tasks = pEngine->results + pTask->offset; + qsort(tasks, pTask->length, sizeof(FeResult), compare2); + } + break; + case MERGE: + { + MergeTaskItem* pMergeTask = (MergeTaskItem*)pTask; + FeResult* list_1 = pEngine->results + pMergeTask->offset_1; + FeResult* list_2 = list_1 + pMergeTask->length_1; + FeResult* buffer = pMergeTask->buffer; + memcpy(buffer, list_2, pMergeTask->length_2 * sizeof(FeResult)); + int32_t i = pMergeTask->length_1 - 1; + int32_t j = pMergeTask->length_2 - 1; + int32_t k = pMergeTask->length_1 + j; + while ( i >= 0 && j >= 0 ) + { + if ( list_1[i].weight < buffer[j].weight ) + { + list_1[k--] = list_1[i--]; + } + else + { + list_1[k--] = buffer[j--]; + } + } + while ( j >= 0 ) + { + list_1[k--] = buffer[j--]; + } + } + break; + case MERGE_2: + { + MergeTaskItem* pMergeTask = (MergeTaskItem*)pTask; + FeResult* list_1 = pEngine->results + pMergeTask->offset_1; + FeResult* list_2 = list_1 + pMergeTask->length_1; + FeResult* buffer = pMergeTask->buffer; + memcpy(buffer, list_2, pMergeTask->length_2 * sizeof(FeResult)); + int32_t i = pMergeTask->length_1 - 1; + int32_t j = pMergeTask->length_2 - 1; + int32_t k = pMergeTask->length_1 + j; + while ( i >= 0 && j >= 0 ) + { + if ( list_1[i].path_weight < buffer[j].path_weight ) + { + list_1[k--] = list_1[i--]; + } + else + { + list_1[k--] = buffer[j--]; + } + } + while ( j >= 0 ) + { + list_1[k--] = buffer[j--]; + } + } + break; + case PY_SET_ITEM: + { + PySetTaskItem* pPySetTask = (PySetTaskItem*)pTask; + weight_t* weights = pPySetTask->weights + pPySetTask->offset; + FeResult* results = pEngine->results + pPySetTask->offset; + PyObject* text_list = pPySetTask->text_list; + PyObject* py_source = pPySetTask->py_source; + uint32_t i = 0; + uint32_t length = pPySetTask->length; + + for ( i = 0; i < length; ++i ) + { + weights[i] = results[i].weight; + PyObject* item = PyList_GET_ITEM(py_source, results[i].index); + Py_INCREF(item); + /* PyList_SET_ITEM() steals a reference to item. */ + PyList_SET_ITEM(text_list, pPySetTask->offset + i, item); + } + } + break; + case PY_SET_ITEM_2: + { + PySetTaskItem* pPySetTask = (PySetTaskItem*)pTask; + uint32_t* path_weights = pPySetTask->path_weights + pPySetTask->offset; + FeResult* results = pEngine->results + pPySetTask->offset; + PyObject* text_list = pPySetTask->text_list; + PyObject* py_source = pPySetTask->py_source; + uint32_t i = 0; + uint32_t length = pPySetTask->length; + + for ( i = 0; i < length; ++i ) + { + path_weights[i] = results[i].path_weight; + PyObject* item = PyList_GET_ITEM(py_source, results[i].index); + Py_INCREF(item); + /* PyList_SET_ITEM() steals a reference to item. */ + PyList_SET_ITEM(text_list, pPySetTask->offset + i, item); + } + } + break; + } + + QUEUE_TASK_DONE(pEngine->task_queue); + } + else + { + break; + } + } + +#if defined(_MSC_VER) + return 0; +#else + return NULL; +#endif +} + +FuzzyEngine* createFuzzyEngine(uint32_t cpu_count) +{ + FuzzyEngine* pEngine = (FuzzyEngine*)malloc(sizeof(FuzzyEngine)); + if ( !pEngine ) + { + return NULL; + } + + pEngine->cpu_count = cpu_count; + pEngine->threads = NULL; + pEngine->pPattern_ctxt = NULL; + pEngine->source = NULL; + + int32_t ret = 0; + QUEUE_INIT(pEngine->task_queue, MAX_TASK_COUNT(cpu_count) + cpu_count + 1, ret); + if ( ret != 0 ) + { + free(pEngine); + return NULL; + } + + return pEngine; +} + +void closeFuzzyEngine(FuzzyEngine* pEngine) +{ + if ( !pEngine ) + return; + + /** + * pEngine->threads is NULL if fuzzyMatch() is not called, + * or fuzzyMatch() returns before malloc for pEngine->threads. + */ + if ( pEngine->threads ) + { + uint32_t i = 0; + for ( ; i < pEngine->cpu_count; ++i ) + { + QUEUE_PUT(pEngine->task_queue, NULL); + } + +#if defined(_MSC_VER) + WaitForMultipleObjects(pEngine->cpu_count, pEngine->threads, TRUE, INFINITE); + for ( i = 0; i < pEngine->cpu_count; ++i ) + { + CloseHandle(pEngine->threads[i]); + } +#else + for ( i = 0; i < pEngine->cpu_count; ++i ) + { + pthread_join(pEngine->threads[i], NULL); + } +#endif + free(pEngine->threads); + } + + QUEUE_DESTROY(pEngine->task_queue); + free(pEngine); +} + +static int32_t pyObject_ToStringAndSize(PyObject* obj, char** buffer, uint32_t* size) +{ + Py_ssize_t length = 0; +#if PY_MAJOR_VERSION >= 3 + *buffer = (char*)PyUnicode_AsUTF8AndSize(obj, &length); + *size = (uint32_t)length; + if ( buffer ) + return 0; + else + return -1; +#else + int ret = PyString_AsStringAndSize(obj, buffer, &length); + *size = (uint32_t)length; + if ( ret >= 0 ) + return 0; + else + return -1; +#endif +} + +static void delFuzzyEngine(PyObject* obj) +{ + closeFuzzyEngine((FuzzyEngine*)PyCapsule_GetPointer(obj, NULL)); +} + +/** + * createFuzzyEngine(cpu_count, auto_free=False) + * + * `auto_free` is optional that specifies whether auto free the fuzzyEngine object. + * It defaults to `False`, which means do not auto free the fuzzyEngine object, + * so that you should call closeFuzzyEngine() manually. + * return a fuzzyEngine object + */ +static PyObject* fuzzyEngine_createFuzzyEngine(PyObject* self, PyObject* args, PyObject* kwargs) +{ + uint32_t cpu_count; + uint8_t auto_free = 0; + static char* kwlist[] = {"cpu_count", "auto_free", NULL}; + + if ( !PyArg_ParseTupleAndKeywords(args, kwargs, "I|b:createFuzzyEngine", kwlist, &cpu_count, &auto_free) ) + return NULL; + + FuzzyEngine* pEngine = createFuzzyEngine(cpu_count); + + return PyCapsule_New(pEngine, NULL, auto_free ? delFuzzyEngine : NULL); +} + +/** + * closeFuzzyEngine(engine) + */ +static PyObject* fuzzyEngine_closeFuzzyEngine(PyObject* self, PyObject* args) +{ + PyObject* engine = NULL; + if ( !PyArg_ParseTuple(args, "O:closeFuzzyEngine", &engine) ) + return NULL; + + closeFuzzyEngine((FuzzyEngine*)PyCapsule_GetPointer(engine, NULL)); + + Py_RETURN_NONE; +} + +static void delPatternContext(PyObject* obj) +{ + free(PyCapsule_GetPointer(obj, NULL)); +} + +/** + * initPattern(pattern) + */ +static PyObject* fuzzyEngine_initPattern(PyObject* self, PyObject* args) +{ + const char* pattern; + Py_ssize_t pattern_len; + + if ( !PyArg_ParseTuple(args, "s#:initPattern", &pattern, &pattern_len) ) + return NULL; + + PatternContext* pCtxt = initPattern(pattern, (uint16_t)pattern_len); + + return PyCapsule_New(pCtxt, NULL, delPatternContext); +} + +static void delWeights(PyObject* obj) +{ + free(PyCapsule_GetPointer(obj, NULL)); +} + +static PyObject* createWeights(void* weights) +{ + return PyCapsule_New(weights, NULL, delWeights); +} + +/** + * fuzzyMatch(engine, source, pattern, is_name_only=False, sort_results=True) + * + * `is_name_only` is optional, it defaults to `False`, which indicates using the full path matching algorithm. + * `sort_results` is optional, it defaults to `True`, which indicates whether to sort the results. + * + * return a tuple, (a list of corresponding weight, a sorted list of items from `source` that match `pattern`). + */ +static PyObject* fuzzyEngine_fuzzyMatch(PyObject* self, PyObject* args, PyObject* kwargs) +{ + PyObject* py_engine = NULL; + PyObject* py_source = NULL; + PyObject* py_patternCtxt = NULL; + uint8_t is_name_only = 0; + uint8_t sort_results = 1; + static char* kwlist[] = {"engine", "source", "pattern", "is_name_only", "sort_results", NULL}; + + if ( !PyArg_ParseTupleAndKeywords(args, kwargs, "OOO|bb:fuzzyMatch", kwlist, &py_engine, + &py_source, &py_patternCtxt, &is_name_only, &sort_results) ) + return NULL; + + FuzzyEngine* pEngine = (FuzzyEngine*)PyCapsule_GetPointer(py_engine, NULL); + if ( !pEngine ) + return NULL; + + if ( !PyList_Check(py_source) ) + { + PyErr_SetString(PyExc_TypeError, "parameter `source` must be a list."); + return NULL; + } + + uint32_t source_size = (uint32_t)PyList_Size(py_source); + if ( source_size == 0 ) + { + return Py_BuildValue("([],[])"); + } + + pEngine->pPattern_ctxt = (PatternContext*)PyCapsule_GetPointer(py_patternCtxt, NULL); + if ( !pEngine->pPattern_ctxt ) + return NULL; + + pEngine->is_name_only = is_name_only; + + uint32_t max_task_count = MAX_TASK_COUNT(pEngine->cpu_count); + uint32_t chunk_size = (source_size + max_task_count - 1) / max_task_count; + uint32_t task_count = (source_size + chunk_size - 1) / chunk_size; + if ( chunk_size == 1 || pEngine->cpu_count == 1 ) + { + chunk_size = source_size; + task_count = 1; + } + + pEngine->source = (FeString*)malloc(source_size * sizeof(FeString)); + if ( !pEngine->source ) + { + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + TaskItem* tasks = (TaskItem*)malloc(task_count * sizeof(TaskItem)); + if ( !tasks ) + { + free(pEngine->source); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + pEngine->results = (FeResult*)malloc(source_size * sizeof(FeResult)); + if ( !pEngine->results ) + { + free(pEngine->source); + free(tasks); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + FeResult* results = pEngine->results; + + if ( !pEngine->threads ) + { +#if defined(_MSC_VER) + pEngine->threads = (HANDLE*)malloc(pEngine->cpu_count * sizeof(HANDLE)); +#else + pEngine->threads = (pthread_t*)malloc(pEngine->cpu_count * sizeof(pthread_t)); +#endif + if ( !pEngine->threads ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + uint32_t i = 0; + for ( ; i < pEngine->cpu_count; ++i) + { +#if defined(_MSC_VER) + pEngine->threads[i] = CreateThread(NULL, 0, _worker, pEngine, 0, NULL); + if ( !pEngine->threads[i] ) +#else + int ret = pthread_create(&pEngine->threads[i], NULL, _worker, pEngine); + if ( ret != 0 ) +#endif + { + free(pEngine->source); + free(tasks); + free(results); + free(pEngine->threads); + fprintf(stderr, "pthread_create error!\n"); + return NULL; + } + } + } + +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + + uint32_t i = 0; + for ( ; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, source_size - offset); + + tasks[i].function = GET_WEIGHT; + tasks[i].offset = offset; + tasks[i].length = length; + + uint32_t j = 0; + for ( ; j < length; ++j ) + { + FeString *s = pEngine->source + offset + j; + PyObject* item = PyList_GET_ITEM(py_source, offset + j); + if ( pyObject_ToStringAndSize(item, &s->str, &s->len) < 0 ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "pyObject_ToStringAndSize error!\n"); + return NULL; + } + } + + QUEUE_PUT(pEngine->task_queue, tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + uint32_t results_count = 0; + for ( i = 0; i < source_size; ++i ) + { + if ( results[i].weight > MIN_WEIGHT ) + { + if ( i > results_count ) + { + results[results_count] = results[i]; + } + ++results_count; + } + } + + if ( results_count == 0 ) + { + free(pEngine->source); + free(tasks); + free(results); + return Py_BuildValue("([],[])"); + } + + if ( sort_results ) + { + if ( task_count == 1 || results_count < 60000 ) + { + qsort(results, results_count, sizeof(FeResult), compare); + } + else + { + chunk_size = (results_count + task_count - 1) / task_count; + if ( chunk_size < 2000 ) + { + chunk_size = (results_count + (task_count >> 1) - 1) / (task_count >> 1); + } + task_count = (results_count + chunk_size - 1) / chunk_size; + FeResult* buffer = (FeResult*)malloc(chunk_size * (task_count >> 1) * sizeof(FeResult)); + if ( !buffer ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + for ( i = 0; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, results_count - offset); + + tasks[i].function = Q_SORT; + tasks[i].offset = offset; + tasks[i].length = length; + QUEUE_PUT(pEngine->task_queue, tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + MergeTaskItem* merge_tasks = NULL; + merge_tasks = (MergeTaskItem*)malloc(task_count * sizeof(MergeTaskItem)); + if ( !merge_tasks ) + { + free(pEngine->source); + free(tasks); + free(results); + free(buffer); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + while ( chunk_size < results_count ) + { + uint32_t q = results_count / (chunk_size << 1); + uint32_t r = results_count % (chunk_size << 1); +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, q + r/chunk_size); +#endif + for ( i = 0; i < q; ++i ) + { + merge_tasks[i].function = MERGE; + merge_tasks[i].offset_1 = i * (chunk_size << 1); + merge_tasks[i].length_1 = chunk_size; + merge_tasks[i].length_2 = chunk_size; + merge_tasks[i].buffer = buffer + (merge_tasks[i].offset_1 >> 1); /* buffer + i * chunk_size */ + QUEUE_PUT(pEngine->task_queue, merge_tasks + i); + } + + if ( r > chunk_size ) + { + merge_tasks[i].function = MERGE; + merge_tasks[i].offset_1 = i * (chunk_size << 1); + merge_tasks[i].length_1 = chunk_size; + merge_tasks[i].length_2 = r - chunk_size; + merge_tasks[i].buffer = buffer + (merge_tasks[i].offset_1 >> 1); /* buffer + i * chunk_size */ + QUEUE_PUT(pEngine->task_queue, merge_tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + chunk_size <<= 1; + } + + free(buffer); + free(merge_tasks); + } + } + + weight_t* weights = (weight_t*)malloc(results_count * sizeof(weight_t)); + if ( !weights ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + PyObject* text_list = PyList_New(results_count); + if ( task_count == 1 || results_count < 40000 ) + { + for ( i = 0; i < results_count; ++i ) + { + weights[i] = results[i].weight; + /* PyList_SET_ITEM() steals a reference to item. */ + /* PySequence_ITEM() return value: New reference. */ + PyList_SET_ITEM(text_list, i, PySequence_ITEM(py_source, results[i].index)); + } + } + else + { + chunk_size = (results_count + task_count - 1) / task_count; + if ( chunk_size < 8000 ) + { + chunk_size = (results_count + (task_count >> 1) - 1) / (task_count >> 1); + } + task_count = (results_count + chunk_size - 1) / chunk_size; + + PySetTaskItem* py_set_tasks = NULL; + py_set_tasks = (PySetTaskItem*)malloc(task_count * sizeof(PySetTaskItem)); + if ( !py_set_tasks ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + for ( i = 0; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, results_count - offset); + + py_set_tasks[i].function = PY_SET_ITEM; + py_set_tasks[i].offset = offset; + py_set_tasks[i].length = length; + py_set_tasks[i].weights = weights; + py_set_tasks[i].text_list = text_list; + py_set_tasks[i].py_source = py_source; + QUEUE_PUT(pEngine->task_queue, py_set_tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + free(py_set_tasks); + } + + free(pEngine->source); + free(tasks); + free(results); + + return Py_BuildValue("(NN)", createWeights(weights), text_list); +} + +/** + * fuzzyMatchEx(engine, source, pattern, is_name_only=False, sort_results=True, is_and_mode=False) + * + * same as fuzzyMatch(), the only difference is the return value. + * return a tuple, (a list of corresponding weight, a sorted list of index to items from `source` that match `pattern`). + */ +static PyObject* fuzzyEngine_fuzzyMatchEx(PyObject* self, PyObject* args, PyObject* kwargs) +{ + PyObject* py_engine = NULL; + PyObject* py_source = NULL; + PyObject* py_patternCtxt = NULL; + uint8_t is_name_only = 0; + uint8_t sort_results = 1; + uint8_t is_and_mode = 0; + static char* kwlist[] = {"engine", "source", "pattern", "is_name_only", "sort_results", "is_and_mode", NULL}; + + if ( !PyArg_ParseTupleAndKeywords(args, kwargs, "OOO|bbb:fuzzyMatch", kwlist, &py_engine, + &py_source, &py_patternCtxt, &is_name_only, &sort_results, &is_and_mode) ) + return NULL; + + FuzzyEngine* pEngine = (FuzzyEngine*)PyCapsule_GetPointer(py_engine, NULL); + if ( !pEngine ) + return NULL; + + if ( !PyList_Check(py_source) ) + { + PyErr_SetString(PyExc_TypeError, "parameter `source` must be a list."); + return NULL; + } + + uint32_t source_size = (uint32_t)PyList_Size(py_source); + if ( source_size == 0 ) + { + return Py_BuildValue("([],[])"); + } + + pEngine->pPattern_ctxt = (PatternContext*)PyCapsule_GetPointer(py_patternCtxt, NULL); + if ( !pEngine->pPattern_ctxt ) + return NULL; + + pEngine->is_name_only = is_name_only; + + uint32_t max_task_count = MAX_TASK_COUNT(pEngine->cpu_count); + uint32_t chunk_size = (source_size + max_task_count - 1) / max_task_count; + uint32_t task_count = (source_size + chunk_size - 1) / chunk_size; + if ( chunk_size == 1 || pEngine->cpu_count == 1 ) + { + chunk_size = source_size; + task_count = 1; + } + + pEngine->source = (FeString*)malloc(source_size * sizeof(FeString)); + if ( !pEngine->source ) + { + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + TaskItem* tasks = (TaskItem*)malloc(task_count * sizeof(TaskItem)); + if ( !tasks ) + { + free(pEngine->source); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + pEngine->results = (FeResult*)malloc(source_size * sizeof(FeResult)); + if ( !pEngine->results ) + { + free(pEngine->source); + free(tasks); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + FeResult* results = pEngine->results; + + if ( !pEngine->threads ) + { +#if defined(_MSC_VER) + pEngine->threads = (HANDLE*)malloc(pEngine->cpu_count * sizeof(HANDLE)); +#else + pEngine->threads = (pthread_t*)malloc(pEngine->cpu_count * sizeof(pthread_t)); +#endif + if ( !pEngine->threads ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + uint32_t i = 0; + for ( ; i < pEngine->cpu_count; ++i) + { +#if defined(_MSC_VER) + pEngine->threads[i] = CreateThread(NULL, 0, _worker, pEngine, 0, NULL); + if ( !pEngine->threads[i] ) +#else + int ret = pthread_create(&pEngine->threads[i], NULL, _worker, pEngine); + if ( ret != 0 ) +#endif + { + free(pEngine->source); + free(tasks); + free(results); + free(pEngine->threads); + fprintf(stderr, "pthread_create error!\n"); + return NULL; + } + } + } + +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + + uint32_t i = 0; + for ( ; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, source_size - offset); + + tasks[i].function = GET_WEIGHT; + tasks[i].offset = offset; + tasks[i].length = length; + + uint32_t j = 0; + for ( ; j < length; ++j ) + { + FeString *s = pEngine->source + offset + j; + PyObject* item = PyList_GET_ITEM(py_source, offset + j); + if ( pyObject_ToStringAndSize(item, &s->str, &s->len) < 0 ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "pyObject_ToStringAndSize error!\n"); + return NULL; + } + } + + QUEUE_PUT(pEngine->task_queue, tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + uint32_t results_count = 0; + for ( i = 0; i < source_size; ++i ) + { + if ( results[i].weight > MIN_WEIGHT ) + { + if ( i > results_count ) + { + results[results_count] = results[i]; + } + ++results_count; + } + } + + if ( results_count == 0 ) + { + free(pEngine->source); + free(tasks); + free(results); + return Py_BuildValue("([],[])"); + } + + if ( sort_results ) + { + if ( task_count == 1 || results_count < 60000 ) + { + qsort(results, results_count, sizeof(FeResult), compare); + } + else + { + chunk_size = (results_count + task_count - 1) / task_count; + if ( chunk_size < 2000 ) + { + chunk_size = (results_count + (task_count >> 1) - 1) / (task_count >> 1); + } + task_count = (results_count + chunk_size - 1) / chunk_size; + FeResult* buffer = (FeResult*)malloc(chunk_size * (task_count >> 1) * sizeof(FeResult)); + if ( !buffer ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + for ( i = 0; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, results_count - offset); + + tasks[i].function = Q_SORT; + tasks[i].offset = offset; + tasks[i].length = length; + QUEUE_PUT(pEngine->task_queue, tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + MergeTaskItem* merge_tasks = NULL; + merge_tasks = (MergeTaskItem*)malloc(task_count * sizeof(MergeTaskItem)); + if ( !merge_tasks ) + { + free(pEngine->source); + free(tasks); + free(results); + free(buffer); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + while ( chunk_size < results_count ) + { + uint32_t q = results_count / (chunk_size << 1); + uint32_t r = results_count % (chunk_size << 1); +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, q + r/chunk_size); +#endif + for ( i = 0; i < q; ++i ) + { + merge_tasks[i].function = MERGE; + merge_tasks[i].offset_1 = i * (chunk_size << 1); + merge_tasks[i].length_1 = chunk_size; + merge_tasks[i].length_2 = chunk_size; + merge_tasks[i].buffer = buffer + (merge_tasks[i].offset_1 >> 1); /* buffer + i * chunk_size */ + QUEUE_PUT(pEngine->task_queue, merge_tasks + i); + } + + if ( r > chunk_size ) + { + merge_tasks[i].function = MERGE; + merge_tasks[i].offset_1 = i * (chunk_size << 1); + merge_tasks[i].length_1 = chunk_size; + merge_tasks[i].length_2 = r - chunk_size; + merge_tasks[i].buffer = buffer + (merge_tasks[i].offset_1 >> 1); /* buffer + i * chunk_size */ + QUEUE_PUT(pEngine->task_queue, merge_tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + chunk_size <<= 1; + } + + free(buffer); + free(merge_tasks); + } + } + + if ( is_and_mode ) + { + PyObject* weight_list = PyList_New(results_count); + PyObject* index_list = PyList_New(results_count); + for ( i = 0; i < results_count; ++i ) + { + /* PyList_SET_ITEM() steals a reference to item. */ + PyList_SET_ITEM(weight_list, i, Py_BuildValue("f", results[i].weight)); + PyList_SET_ITEM(index_list, i, Py_BuildValue("I", results[i].index)); + } + + free(pEngine->source); + free(tasks); + free(results); + + return Py_BuildValue("(NN)", weight_list, index_list); + } + else + { + weight_t* weights = (weight_t*)malloc(results_count * sizeof(weight_t)); + if ( !weights ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + PyObject* index_list = PyList_New(results_count); + for ( i = 0; i < results_count; ++i ) + { + weights[i] = results[i].weight; + /* PyList_SET_ITEM() steals a reference to item. */ + /* PySequence_ITEM() return value: New reference. */ + PyList_SET_ITEM(index_list, i, Py_BuildValue("I", results[i].index)); + } + + free(pEngine->source); + free(tasks); + free(results); + + return Py_BuildValue("(NN)", createWeights(weights), index_list); + } +} + +/** + * merge(tuple_a, tuple_b) + * tuple_a, tuple_b are the return value of fuzzyEngine_fuzzyMatch + */ +static PyObject* fuzzyEngine_merge(PyObject* self, PyObject* args) +{ + PyObject* weight_list_a = NULL; + PyObject* text_list_a = NULL; + PyObject* weight_list_b = NULL; + PyObject* text_list_b = NULL; + if ( !PyArg_ParseTuple(args, "(OO)(OO):merge", &weight_list_a, &text_list_a, &weight_list_b, &text_list_b) ) + return NULL; + + uint32_t size_a = (uint32_t)PyList_Size(text_list_a); + if ( size_a == 0 ) + { + return Py_BuildValue("(OO)", weight_list_b, text_list_b); + } + uint32_t size_b = (uint32_t)PyList_Size(text_list_b); + if ( size_b == 0 ) + { + return Py_BuildValue("(OO)", weight_list_a, text_list_a); + } + + weight_t* weights = (weight_t*)malloc((size_a + size_b) * sizeof(weight_t)); + if ( !weights ) + { + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + PyObject* text_list = PyList_New(size_a + size_b); + + uint32_t i = 0; + uint32_t j = 0; + + weight_t* weights_a = (weight_t*)PyCapsule_GetPointer(weight_list_a, NULL); + weight_t w_a = weights_a[i]; + weight_t* weights_b = (weight_t*)PyCapsule_GetPointer(weight_list_b, NULL); + weight_t w_b = weights_b[j]; + while ( i < size_a && j < size_b ) + { + if ( w_a > w_b ) + { + weights[i + j] = weights_a[i]; + PyList_SET_ITEM(text_list, i + j, PySequence_ITEM(text_list_a, i)); + ++i; + if ( i < size_a ) + { + w_a = weights_a[i]; + } + } + else + { + weights[i + j] = weights_b[j]; + PyList_SET_ITEM(text_list, i + j, PySequence_ITEM(text_list_b, j)); + ++j; + if ( j < size_b ) + { + w_b = weights_b[j]; + } + } + } + while ( i < size_a ) + { + weights[i + j] = weights_a[i]; + PyList_SET_ITEM(text_list, i + j, PySequence_ITEM(text_list_a, i)); + ++i; + } + while ( j < size_b ) + { + weights[i + j] = weights_b[j]; + PyList_SET_ITEM(text_list, i + j, PySequence_ITEM(text_list_b, j)); + ++j; + } + return Py_BuildValue("(NN)", createWeights(weights), text_list); +} +/** + * getHighlights(engine, source, pattern, is_name_only=False) + * + * `is_name_only` is optional, it defaults to `False`, which indicates using the full path matching algorithm. + * + * return a list of list of pair [col, length], where `col` is the column number(start from 1, the value must + * correspond to the byte index of `text`) and `length` is the length of the highlight in bytes. + * e.g., [ + * [ [2,3], [6,2], [10,4], ... ], + * [ [3,2], [5,2], [9,3], ... ], + * ... + * ] + * NOTE: this function must be called after fuzzyMatch() is called, because this function assume that all the + * texts in `source` match `pattern` and all the threads in FuzzyEngine have already been started. + */ +static PyObject* fuzzyEngine_getHighlights(PyObject* self, PyObject* args, PyObject* kwargs) +{ + PyObject* py_source = NULL; + PyObject* py_patternCtxt = NULL; + PyObject* py_engine = NULL; + uint8_t is_name_only = 0; + static char* kwlist[] = {"engine", "source", "pattern", "is_name_only", NULL}; + + if ( !PyArg_ParseTupleAndKeywords(args, kwargs, "OOO|b:fuzzyMatch", kwlist, &py_engine, + &py_source, &py_patternCtxt, &is_name_only) ) + return NULL; + + FuzzyEngine* pEngine = (FuzzyEngine*)PyCapsule_GetPointer(py_engine, NULL); + if ( !pEngine ) + return NULL; + + if ( !PyList_Check(py_source) ) + { + PyErr_SetString(PyExc_TypeError, "parameter `source` must be a list."); + return NULL; + } + + pEngine->pPattern_ctxt = (PatternContext*)PyCapsule_GetPointer(py_patternCtxt, NULL); + if ( !pEngine->pPattern_ctxt ) + return NULL; + + pEngine->is_name_only = is_name_only; + + uint32_t source_size = (uint32_t)PyList_Size(py_source); + + uint32_t max_task_count = MAX_TASK_COUNT(pEngine->cpu_count); + uint32_t chunk_size = (source_size + max_task_count - 1) / max_task_count; + uint32_t task_count = (source_size + chunk_size - 1) / chunk_size; + + pEngine->source = (FeString*)malloc(source_size * sizeof(FeString)); + if ( !pEngine->source ) + { + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + TaskItem* tasks = (TaskItem*)malloc(task_count * sizeof(TaskItem)); + if ( !tasks ) + { + free(pEngine->source); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + pEngine->highlights = (HighlightGroup**)malloc(source_size * sizeof(HighlightGroup*)); + if ( !pEngine->highlights ) + { + free(pEngine->source); + free(tasks); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + + uint32_t i = 0; + for ( ; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, source_size - offset); + + tasks[i].function = GET_HIGHLIGHTS; + tasks[i].offset = offset; + tasks[i].length = length; + + uint32_t j = 0; + for ( ; j < length; ++j ) + { + FeString *s = pEngine->source + offset + j; + PyObject* item = PyList_GET_ITEM(py_source, offset + j); + if ( pyObject_ToStringAndSize(item, &s->str, &s->len) < 0 ) + { + free(pEngine->source); + free(tasks); + free(pEngine->highlights); + fprintf(stderr, "pyObject_ToStringAndSize error!\n"); + return NULL; + } + } + + QUEUE_PUT(pEngine->task_queue, tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + PyObject* res = PyList_New(source_size); + for ( i = 0; i < source_size; ++i ) + { + HighlightGroup* pGroup = pEngine->highlights[i]; + if ( !pGroup ) + { + free(pEngine->source); + free(tasks); + free(pEngine->highlights); + Py_XDECREF(res); + return NULL; + } + + PyObject* list = PyList_New(pGroup->end_index); + uint16_t j; + for ( j = 0; j < pGroup->end_index; ++j ) + { + PyList_SetItem(list, j, Py_BuildValue("[H,H]", pGroup->positions[j].col, pGroup->positions[j].len)); + } + PyList_SetItem(res, i, list); + free(pGroup); + } + + free(pEngine->source); + free(tasks); + free(pEngine->highlights); + + return res; +} + +/** + * guessMatch(engine, source, filename, suffix, dirname, icon, sort_results=True) + * + * e.g., /usr/src/example.tar.gz + * `filename` is "example.tar" + * `suffix` is ".gz" + * `dirname` is "/usr/src" + * + * return a tuple, (a list of corresponding weight, a sorted list of items from `source` that match `pattern`). + */ +static PyObject* fuzzyEngine_guessMatch(PyObject* self, PyObject* args, PyObject* kwargs) +{ + PyObject* py_engine = NULL; + PyObject* py_source = NULL; + const char* filename = NULL; + const char* suffix = NULL; + const char* dirname = NULL; + PyObject* py_icon = NULL; + uint8_t sort_results = 1; + static char* kwlist[] = {"engine", "source", "filename", "suffix", "dirname", "icon", "sort_results", NULL}; + + if ( !PyArg_ParseTupleAndKeywords(args, kwargs, "OOsssO|b:guessMatch", kwlist, &py_engine, + &py_source, &filename, &suffix, &dirname, &py_icon, &sort_results) ) + return NULL; + + FuzzyEngine* pEngine = (FuzzyEngine*)PyCapsule_GetPointer(py_engine, NULL); + if ( !pEngine ) + return NULL; + + if ( !PyList_Check(py_source) ) + { + PyErr_SetString(PyExc_TypeError, "parameter `source` must be a list."); + return NULL; + } + + uint32_t source_size = (uint32_t)PyList_Size(py_source); + if ( source_size == 0 ) + { + return Py_BuildValue("([],[])"); + } + + pEngine->filename = filename; + pEngine->suffix = suffix; + pEngine->dirname = dirname; + + uint32_t max_task_count = MAX_TASK_COUNT(pEngine->cpu_count); + uint32_t chunk_size = (source_size + max_task_count - 1) / max_task_count; + uint32_t task_count = (source_size + chunk_size - 1) / chunk_size; + if ( chunk_size == 1 || pEngine->cpu_count == 1 ) + { + chunk_size = source_size; + task_count = 1; + } + + pEngine->source = (FeString*)malloc(source_size * sizeof(FeString)); + if ( !pEngine->source ) + { + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + TaskItem* tasks = (TaskItem*)malloc(task_count * sizeof(TaskItem)); + if ( !tasks ) + { + free(pEngine->source); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + pEngine->results = (FeResult*)malloc(source_size * sizeof(FeResult)); + if ( !pEngine->results ) + { + free(pEngine->source); + free(tasks); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + FeResult* results = pEngine->results; + + if ( !pEngine->threads ) + { +#if defined(_MSC_VER) + pEngine->threads = (HANDLE*)malloc(pEngine->cpu_count * sizeof(HANDLE)); +#else + pEngine->threads = (pthread_t*)malloc(pEngine->cpu_count * sizeof(pthread_t)); +#endif + if ( !pEngine->threads ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + uint32_t i = 0; + for ( ; i < pEngine->cpu_count; ++i) + { +#if defined(_MSC_VER) + pEngine->threads[i] = CreateThread(NULL, 0, _worker, pEngine, 0, NULL); + if ( !pEngine->threads[i] ) +#else + int ret = pthread_create(&pEngine->threads[i], NULL, _worker, pEngine); + if ( ret != 0 ) +#endif + { + free(pEngine->source); + free(tasks); + free(results); + free(pEngine->threads); + fprintf(stderr, "pthread_create error!\n"); + return NULL; + } + } + } + +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + + char *icon_str = NULL; + uint32_t icon_len = 0; + if ( pyObject_ToStringAndSize(py_icon, &icon_str, &icon_len) < 0 ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "pyObject_ToStringAndSize error!\n"); + return NULL; + } + + uint32_t i = 0; + for ( ; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, source_size - offset); + + tasks[i].function = GET_PATH_WEIGHT; + tasks[i].offset = offset; + tasks[i].length = length; + + uint32_t j = 0; + for ( ; j < length; ++j ) + { + FeString *s = pEngine->source + offset + j; + PyObject* item = PyList_GET_ITEM(py_source, offset + j); + if ( pyObject_ToStringAndSize(item, &s->str, &s->len) < 0 ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "pyObject_ToStringAndSize error!\n"); + return NULL; + } + + if ( icon_len > 0 ) + { + s->str += icon_len; + s->len -= icon_len; + } + } + + QUEUE_PUT(pEngine->task_queue, tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + if ( sort_results ) + { + if ( task_count == 1 || source_size < 60000 ) + { + qsort(results, source_size, sizeof(FeResult), compare2); + } + else + { + chunk_size = (source_size + task_count - 1) / task_count; + if ( chunk_size < 2000 ) + { + chunk_size = (source_size + (task_count >> 1) - 1) / (task_count >> 1); + } + task_count = (source_size + chunk_size - 1) / chunk_size; + FeResult* buffer = (FeResult*)malloc(chunk_size * (task_count >> 1) * sizeof(FeResult)); + if ( !buffer ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + for ( i = 0; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, source_size - offset); + + tasks[i].function = Q_SORT_2; + tasks[i].offset = offset; + tasks[i].length = length; + QUEUE_PUT(pEngine->task_queue, tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + MergeTaskItem* merge_tasks = NULL; + merge_tasks = (MergeTaskItem*)malloc(task_count * sizeof(MergeTaskItem)); + if ( !merge_tasks ) + { + free(pEngine->source); + free(tasks); + free(results); + free(buffer); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + while ( chunk_size < source_size ) + { + uint32_t q = source_size / (chunk_size << 1); + uint32_t r = source_size % (chunk_size << 1); +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, q + r/chunk_size); +#endif + for ( i = 0; i < q; ++i ) + { + merge_tasks[i].function = MERGE_2; + merge_tasks[i].offset_1 = i * (chunk_size << 1); + merge_tasks[i].length_1 = chunk_size; + merge_tasks[i].length_2 = chunk_size; + merge_tasks[i].buffer = buffer + (merge_tasks[i].offset_1 >> 1); /* buffer + i * chunk_size */ + QUEUE_PUT(pEngine->task_queue, merge_tasks + i); + } + + if ( r > chunk_size ) + { + merge_tasks[i].function = MERGE_2; + merge_tasks[i].offset_1 = i * (chunk_size << 1); + merge_tasks[i].length_1 = chunk_size; + merge_tasks[i].length_2 = r - chunk_size; + merge_tasks[i].buffer = buffer + (merge_tasks[i].offset_1 >> 1); /* buffer + i * chunk_size */ + QUEUE_PUT(pEngine->task_queue, merge_tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + chunk_size <<= 1; + } + + free(buffer); + free(merge_tasks); + } + } + + uint32_t* path_weights = (uint32_t*)malloc(source_size * sizeof(uint32_t)); + if ( !path_weights ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + PyObject* text_list = PyList_New(source_size); + if ( task_count == 1 || source_size < 40000 ) + { + for ( i = 0; i < source_size; ++i ) + { + path_weights[i] = results[i].path_weight; + /* PyList_SET_ITEM() steals a reference to item. */ + /* PySequence_ITEM() return value: New reference. */ + PyList_SET_ITEM(text_list, i, PySequence_ITEM(py_source, results[i].index)); + } + } + else + { + chunk_size = (source_size + task_count - 1) / task_count; + if ( chunk_size < 8000 ) + { + chunk_size = (source_size + (task_count >> 1) - 1) / (task_count >> 1); + } + task_count = (source_size + chunk_size - 1) / chunk_size; + + PySetTaskItem* py_set_tasks = NULL; + py_set_tasks = (PySetTaskItem*)malloc(task_count * sizeof(PySetTaskItem)); + if ( !py_set_tasks ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + for ( i = 0; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, source_size - offset); + + py_set_tasks[i].function = PY_SET_ITEM_2; + py_set_tasks[i].offset = offset; + py_set_tasks[i].length = length; + py_set_tasks[i].path_weights = path_weights; + py_set_tasks[i].text_list = text_list; + py_set_tasks[i].py_source = py_source; + QUEUE_PUT(pEngine->task_queue, py_set_tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + free(py_set_tasks); + } + + free(pEngine->source); + free(tasks); + free(results); + + return Py_BuildValue("(NN)", createWeights(path_weights), text_list); +} + +enum +{ + Category_Rg = 0, + Category_Tag, + Category_File, + Category_Gtags, + Category_Line, + Category_GitDiff, +}; + +typedef struct RgParameter +{ + uint32_t display_multi; + const char* separator; + uint32_t separator_len; + uint32_t has_column; +}RgParameter; + +typedef struct Parameter +{ + uint32_t mode; +}Parameter; + +typedef struct GtagsParameter +{ + uint32_t mode; + uint32_t format; + uint32_t match_path; +}GtagsParameter; + +static void delParamObj(PyObject* obj) +{ + free(PyCapsule_GetPointer(obj, NULL)); +} + +/** + * createRgParameter(display_multi, separator, has_column) + */ +static PyObject* fuzzyEngine_createRgParameter(PyObject* self, PyObject* args) +{ + uint32_t display_multi = 0; + const char* separator; + Py_ssize_t separator_len; + uint32_t has_column = 0; + + if ( !PyArg_ParseTuple(args, "Is#I:createRgParameter", &display_multi, &separator, &separator_len, &has_column) ) + return NULL; + + RgParameter* param = (RgParameter*)malloc(sizeof(RgParameter)); + if ( !param ) + { + return NULL; + } + param->display_multi = display_multi; + param->separator = separator; + param->separator_len = (uint32_t)separator_len; + param->has_column = has_column; + + return PyCapsule_New(param, NULL, delParamObj); +} + +/** + * createParameter(mode) + */ +static PyObject* fuzzyEngine_createParameter(PyObject* self, PyObject* args) +{ + uint32_t mode = 0; + + if ( !PyArg_ParseTuple(args, "I:createParameter", &mode) ) + return NULL; + + Parameter* param = (Parameter*)malloc(sizeof(Parameter)); + if ( !param ) + { + return NULL; + } + + param->mode = mode; + + return PyCapsule_New(param, NULL, delParamObj); +} + +/** + * createGtagsParameter(mode, format, match_path) + */ +static PyObject* fuzzyEngine_createGtagsParameter(PyObject* self, PyObject* args) +{ + uint32_t mode; + uint32_t format; + uint32_t match_path; + + if ( !PyArg_ParseTuple(args, "III:createGtagsParameter", &mode, &format, &match_path) ) + return NULL; + + GtagsParameter* param = (GtagsParameter*)malloc(sizeof(GtagsParameter)); + if ( !param ) + { + return NULL; + } + + param->mode = mode; + param->format = format; + param->match_path = match_path; + + return PyCapsule_New(param, NULL, delParamObj); +} + +static void rg_getDigest(char** str, uint32_t* length, RgParameter* param) +{ + char* s = *str; + uint32_t len = *length; + char* p = NULL; + + if ( param->display_multi ) + { + if ( len == param->separator_len && strncmp(s, param->separator, len) == 0 ) + { + *length = 0; + return; + } + else + { + uint8_t colon = 0; + uint8_t minus = 0; + for ( p = s; p < s + len; ++p ) + { + if ( *p == ':' ) + { + minus = 0; + ++colon; + if ( (colon == 2 && !param->has_column) || colon == 3 ) + { + *str = p + 1; + *length -= (uint32_t)(*str - s); + return; + } + } + else if ( *p == '-' ) + { + colon = 0; + ++minus; + if ( minus == 2 ) + { + *str = p + 1; + *length -= (uint32_t)(*str - s); + return; + } + } + else if ( !isdigit(*p) && colon + minus > 0 ) + { + colon = 0; + minus = 0; + } + } + } + } + else + { + uint8_t colon = 0; + for ( p = s; p < s + len; ++p ) + { + if ( *p == ':' ) + { + ++colon; + if ( (colon == 2 && !param->has_column) || colon == 3 ) + { + *str = p + 1; + *length -= (uint32_t)(*str - s); + return; + } + } + else if ( !isdigit(*p) && colon > 0 ) + { + colon = 0; + } + } + } +} + +static void tag_getDigest(char** str, uint32_t* length, Parameter* param) +{ + char* s = *str; + uint32_t len = *length; + char* p = s; + for ( ; p < s + len; ++p ) + { + if ( *p == '\t' ) + { + *length = (uint32_t)(p - s); + return; + } + } + /* if there is no '\t', the text is invalid */ + *length = 0; +} + +static void file_getDigest(char** str, uint32_t* length, Parameter* param) +{ + char* s = *str; + char *p = s + *length - 1; + for ( ; p >= s; --p ) + { + if ( *p == '/' || *p == '\\' ) + { + *str = p + 1; + *length -= (uint32_t)(*str - s); + return; + } + } +} + +static void gtags_getDigest(char** str, uint32_t* length, GtagsParameter* param) +{ + char* s = *str; + uint32_t len = *length; + char* p = NULL; + + if ( param->match_path ) + return; + + if ( param->format == 0 ) /* ctags-mod */ + { + uint8_t tab = 0; + for ( p = s; p < s + len; ++p ) + { + if ( *p == '\t' ) + { + ++tab; + if ( tab == 2 ) + { + *str = p + 1; + *length -= (uint32_t)(*str - s); + return; + } + } + } + } + else if ( param->format == 1 ) /* ctags */ + { + for ( p = s; p < s + len; ++p ) + { + if ( *p == '\t' ) + { + *length = (uint32_t)(p - s); + return; + } + } + } + else if ( param->format == 2 ) /* ctags-x */ + { + for ( p = s; p < s + len; ++p ) + { + if ( *p == ' ' ) + { + *length = (uint32_t)(p - s); + return; + } + } + } +} + +static void line_getDigest(char** str, uint32_t* length, Parameter* param) +{ + char* s = *str; + char *p = s + *length - 1; + for ( ; p >= s; --p ) + { + if ( *p == '\t' ) + { + *length = (uint32_t)(p - s); + return; + } + } +} + +static void gitdiff_getDigest(char** str, uint32_t* length, Parameter* param) +{ + if ( param->mode == 0 ) { + uint32_t len = 5; + *str += len; + *length -= len; + } + else { + file_getDigest(str, length, param); + } +} + +/** + * fuzzyMatchPart(engine, source, pattern, category, param, is_name_only=False, sort_results=True) + * + * `is_name_only` is optional, it defaults to `False`, which indicates using the full path matching algorithm. + * `sort_results` is optional, it defaults to `True`, which indicates whether to sort the results. + * + * return a tuple, (a list of corresponding weight, a sorted list of items from `source` that match `pattern`). + */ +static PyObject* fuzzyEngine_fuzzyMatchPart(PyObject* self, PyObject* args, PyObject* kwargs) +{ + PyObject* py_engine = NULL; + PyObject* py_source = NULL; + PyObject* py_patternCtxt = NULL; + PyObject* py_param = NULL; + uint32_t category; + uint8_t is_name_only = 0; + uint8_t sort_results = 1; + static char* kwlist[] = {"engine", "source", "pattern", "category", "param", "is_name_only", "sort_results", NULL}; + + if ( !PyArg_ParseTupleAndKeywords(args, kwargs, "OOOIO|bb:fuzzyMatch", kwlist, &py_engine, &py_source, + &py_patternCtxt, &category, &py_param, &is_name_only, &sort_results) ) + return NULL; + + FuzzyEngine* pEngine = (FuzzyEngine*)PyCapsule_GetPointer(py_engine, NULL); + if ( !pEngine ) + return NULL; + + if ( !PyList_Check(py_source) ) + { + PyErr_SetString(PyExc_TypeError, "parameter `source` must be a list."); + return NULL; + } + + uint32_t source_size = (uint32_t)PyList_Size(py_source); + if ( source_size == 0 ) + { + return Py_BuildValue("([],[])"); + } + + pEngine->pPattern_ctxt = (PatternContext*)PyCapsule_GetPointer(py_patternCtxt, NULL); + if ( !pEngine->pPattern_ctxt ) + return NULL; + + pEngine->is_name_only = is_name_only; + + uint32_t max_task_count = MAX_TASK_COUNT(pEngine->cpu_count); + uint32_t chunk_size = (source_size + max_task_count - 1) / max_task_count; + uint32_t task_count = (source_size + chunk_size - 1) / chunk_size; + if ( chunk_size == 1 || pEngine->cpu_count == 1 ) + { + chunk_size = source_size; + task_count = 1; + } + + pEngine->source = (FeString*)malloc(source_size * sizeof(FeString)); + if ( !pEngine->source ) + { + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + TaskItem* tasks = (TaskItem*)malloc(task_count * sizeof(TaskItem)); + if ( !tasks ) + { + free(pEngine->source); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + pEngine->results = (FeResult*)malloc(source_size * sizeof(FeResult)); + if ( !pEngine->results ) + { + free(pEngine->source); + free(tasks); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + FeResult* results = pEngine->results; + + if ( !pEngine->threads ) + { +#if defined(_MSC_VER) + pEngine->threads = (HANDLE*)malloc(pEngine->cpu_count * sizeof(HANDLE)); +#else + pEngine->threads = (pthread_t*)malloc(pEngine->cpu_count * sizeof(pthread_t)); +#endif + if ( !pEngine->threads ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + uint32_t i = 0; + for ( ; i < pEngine->cpu_count; ++i) + { +#if defined(_MSC_VER) + pEngine->threads[i] = CreateThread(NULL, 0, _worker, pEngine, 0, NULL); + if ( !pEngine->threads[i] ) +#else + int ret = pthread_create(&pEngine->threads[i], NULL, _worker, pEngine); + if ( ret != 0 ) +#endif + { + free(pEngine->source); + free(tasks); + free(results); + free(pEngine->threads); + fprintf(stderr, "pthread_create error!\n"); + return NULL; + } + } + } + +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + + uint32_t i = 0; + for ( ; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, source_size - offset); + + tasks[i].function = GET_WEIGHT; + tasks[i].offset = offset; + tasks[i].length = length; + + uint32_t j = 0; + for ( ; j < length; ++j ) + { + FeString *s = pEngine->source + offset + j; + PyObject* item = PyList_GET_ITEM(py_source, offset + j); + if ( pyObject_ToStringAndSize(item, &s->str, &s->len) < 0 ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "pyObject_ToStringAndSize error!\n"); + return NULL; + } + + switch ( category ) + { + case Category_Rg: + { + RgParameter* param = (RgParameter*)PyCapsule_GetPointer(py_param, NULL); + if ( !param ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "PyCapsule_GetPointer error!\n"); + return NULL; + } + rg_getDigest(&s->str, &s->len, param); + break; + } + case Category_Tag: + tag_getDigest(&s->str, &s->len, (Parameter*)PyCapsule_GetPointer(py_param, NULL)); + break; + case Category_File: + file_getDigest(&s->str, &s->len, (Parameter*)PyCapsule_GetPointer(py_param, NULL)); + break; + case Category_Gtags: + { + GtagsParameter* param = (GtagsParameter*)PyCapsule_GetPointer(py_param, NULL); + if ( !param ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "PyCapsule_GetPointer error!\n"); + return NULL; + } + gtags_getDigest(&s->str, &s->len, param); + break; + } + case Category_Line: + line_getDigest(&s->str, &s->len, (Parameter*)PyCapsule_GetPointer(py_param, NULL)); + break; + case Category_GitDiff: + gitdiff_getDigest(&s->str, &s->len, (Parameter*)PyCapsule_GetPointer(py_param, NULL)); + break; + } + } + + QUEUE_PUT(pEngine->task_queue, tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + uint32_t results_count = 0; + for ( i = 0; i < source_size; ++i ) + { + if ( results[i].weight > MIN_WEIGHT ) + { + if ( i > results_count ) + { + results[results_count] = results[i]; + } + ++results_count; + } + } + + if ( results_count == 0 ) + { + free(pEngine->source); + free(tasks); + free(results); + return Py_BuildValue("([],[])"); + } + + if ( sort_results ) + { + if ( task_count == 1 || results_count < 60000 ) + { + qsort(results, results_count, sizeof(FeResult), compare); + } + else + { + chunk_size = (results_count + task_count - 1) / task_count; + if ( chunk_size < 2000 ) + { + chunk_size = (results_count + (task_count >> 1) - 1) / (task_count >> 1); + } + task_count = (results_count + chunk_size - 1) / chunk_size; + FeResult* buffer = (FeResult*)malloc(chunk_size * (task_count >> 1) * sizeof(FeResult)); + if ( !buffer ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + for ( i = 0; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, results_count - offset); + + tasks[i].function = Q_SORT; + tasks[i].offset = offset; + tasks[i].length = length; + QUEUE_PUT(pEngine->task_queue, tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + MergeTaskItem* merge_tasks = NULL; + merge_tasks = (MergeTaskItem*)malloc(task_count * sizeof(MergeTaskItem)); + if ( !merge_tasks ) + { + free(pEngine->source); + free(tasks); + free(results); + free(buffer); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + while ( chunk_size < results_count ) + { + uint32_t q = results_count / (chunk_size << 1); + uint32_t r = results_count % (chunk_size << 1); +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, q + r/chunk_size); +#endif + for ( i = 0; i < q; ++i ) + { + merge_tasks[i].function = MERGE; + merge_tasks[i].offset_1 = i * (chunk_size << 1); + merge_tasks[i].length_1 = chunk_size; + merge_tasks[i].length_2 = chunk_size; + merge_tasks[i].buffer = buffer + (merge_tasks[i].offset_1 >> 1); /* buffer + i * chunk_size */ + QUEUE_PUT(pEngine->task_queue, merge_tasks + i); + } + + if ( r > chunk_size ) + { + merge_tasks[i].function = MERGE; + merge_tasks[i].offset_1 = i * (chunk_size << 1); + merge_tasks[i].length_1 = chunk_size; + merge_tasks[i].length_2 = r - chunk_size; + merge_tasks[i].buffer = buffer + (merge_tasks[i].offset_1 >> 1); /* buffer + i * chunk_size */ + QUEUE_PUT(pEngine->task_queue, merge_tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + chunk_size <<= 1; + } + + free(buffer); + free(merge_tasks); + } + } + + weight_t* weights = (weight_t*)malloc(results_count * sizeof(weight_t)); + if ( !weights ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + + PyObject* text_list = PyList_New(results_count); + if ( task_count == 1 || results_count < 40000 ) + { + for ( i = 0; i < results_count; ++i ) + { + weights[i] = results[i].weight; + /* PyList_SET_ITEM() steals a reference to item. */ + /* PySequence_ITEM() return value: New reference. */ + PyList_SET_ITEM(text_list, i, PySequence_ITEM(py_source, results[i].index)); + } + } + else + { + chunk_size = (results_count + task_count - 1) / task_count; + if ( chunk_size < 8000 ) + { + chunk_size = (results_count + (task_count >> 1) - 1) / (task_count >> 1); + } + task_count = (results_count + chunk_size - 1) / chunk_size; + + PySetTaskItem* py_set_tasks = NULL; + py_set_tasks = (PySetTaskItem*)malloc(task_count * sizeof(PySetTaskItem)); + if ( !py_set_tasks ) + { + free(pEngine->source); + free(tasks); + free(results); + fprintf(stderr, "Out of memory at %s:%d\n", __FILE__, __LINE__); + return NULL; + } + +#if defined(_MSC_VER) + QUEUE_SET_TASK_COUNT(pEngine->task_queue, task_count); +#endif + for ( i = 0; i < task_count; ++i ) + { + uint32_t offset = i * chunk_size; + uint32_t length = MIN(chunk_size, results_count - offset); + + py_set_tasks[i].function = PY_SET_ITEM; + py_set_tasks[i].offset = offset; + py_set_tasks[i].length = length; + py_set_tasks[i].weights = weights; + py_set_tasks[i].text_list = text_list; + py_set_tasks[i].py_source = py_source; + QUEUE_PUT(pEngine->task_queue, py_set_tasks + i); + } + + QUEUE_JOIN(pEngine->task_queue); /* blocks until all tasks have finished */ + + free(py_set_tasks); + } + + free(pEngine->source); + free(tasks); + free(results); + + return Py_BuildValue("(NN)", createWeights(weights), text_list); +} + +static PyMethodDef fuzzyEngine_Methods[] = +{ + { "createFuzzyEngine", (PyCFunction)fuzzyEngine_createFuzzyEngine, METH_VARARGS | METH_KEYWORDS, "" }, + { "closeFuzzyEngine", (PyCFunction)fuzzyEngine_closeFuzzyEngine, METH_VARARGS, "" }, + { "initPattern", (PyCFunction)fuzzyEngine_initPattern, METH_VARARGS, "initialize the pattern." }, + { "fuzzyMatch", (PyCFunction)fuzzyEngine_fuzzyMatch, METH_VARARGS | METH_KEYWORDS, "" }, + { "fuzzyMatchEx", (PyCFunction)fuzzyEngine_fuzzyMatchEx, METH_VARARGS | METH_KEYWORDS, "" }, + { "fuzzyMatchPart", (PyCFunction)fuzzyEngine_fuzzyMatchPart, METH_VARARGS | METH_KEYWORDS, "" }, + { "getHighlights", (PyCFunction)fuzzyEngine_getHighlights, METH_VARARGS | METH_KEYWORDS, "" }, + { "guessMatch", (PyCFunction)fuzzyEngine_guessMatch, METH_VARARGS | METH_KEYWORDS, "" }, + { "merge", (PyCFunction)fuzzyEngine_merge, METH_VARARGS, "" }, + { "createRgParameter", (PyCFunction)fuzzyEngine_createRgParameter, METH_VARARGS, "" }, + { "createParameter", (PyCFunction)fuzzyEngine_createParameter, METH_VARARGS, "" }, + { "createGtagsParameter", (PyCFunction)fuzzyEngine_createGtagsParameter, METH_VARARGS, "" }, + { NULL, NULL, 0, NULL } +}; + +#if PY_MAJOR_VERSION >= 3 + +static struct PyModuleDef fuzzyEngine_module = +{ + PyModuleDef_HEAD_INIT, + "fuzzyEngine", /* name of module */ + "fuzzy matching algorithm which takes full advantage of the cpu cores.", + -1, + fuzzyEngine_Methods +}; + +PyMODINIT_FUNC PyInit_fuzzyEngine(void) +{ + PyObject* module = NULL; + module = PyModule_Create(&fuzzyEngine_module); + + if ( !module ) + return NULL; + + if ( PyModule_AddObject(module, "Category_Rg", Py_BuildValue("I", Category_Rg)) ) + { + Py_DECREF(module); + return NULL; + } + + if ( PyModule_AddObject(module, "Category_Tag", Py_BuildValue("I", Category_Tag)) ) + { + Py_DECREF(module); + return NULL; + } + + if ( PyModule_AddObject(module, "Category_File", Py_BuildValue("I", Category_File)) ) + { + Py_DECREF(module); + return NULL; + } + + if ( PyModule_AddObject(module, "Category_Gtags", Py_BuildValue("I", Category_Gtags)) ) + { + Py_DECREF(module); + return NULL; + } + + if ( PyModule_AddObject(module, "Category_Line", Py_BuildValue("I", Category_Line)) ) + { + Py_DECREF(module); + return NULL; + } + + if ( PyModule_AddObject(module, "Category_GitDiff", Py_BuildValue("I", Category_GitDiff)) ) + { + Py_DECREF(module); + return NULL; + } + + return module; +} + +#else + +PyMODINIT_FUNC initfuzzyEngine(void) +{ + PyObject* module = NULL; + module = Py_InitModule("fuzzyEngine", fuzzyEngine_Methods); + + if ( !module ) + return; + + if ( PyModule_AddObject(module, "Category_Rg", Py_BuildValue("I", Category_Rg)) ) + { + Py_DECREF(module); + return; + } + + if ( PyModule_AddObject(module, "Category_Tag", Py_BuildValue("I", Category_Tag)) ) + { + Py_DECREF(module); + return; + } + + if ( PyModule_AddObject(module, "Category_File", Py_BuildValue("I", Category_File)) ) + { + Py_DECREF(module); + return; + } + + if ( PyModule_AddObject(module, "Category_Gtags", Py_BuildValue("I", Category_Gtags)) ) + { + Py_DECREF(module); + return; + } + + if ( PyModule_AddObject(module, "Category_Line", Py_BuildValue("I", Category_Line)) ) + { + Py_DECREF(module); + return; + } + + if ( PyModule_AddObject(module, "Category_GitDiff", Py_BuildValue("I", Category_GitDiff)) ) + { + Py_DECREF(module); + return; + } +} + +#endif + diff --git a/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.c b/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.c index c4b7a7ca..766f207b 100644 --- a/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.c +++ b/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.c @@ -37,6 +37,7 @@ #if defined(_M_AMD64) || defined(_M_X64) #define FM_BITSCAN_WINDOWS64 #pragma intrinsic(_BitScanReverse64) + #pragma intrinsic(_BitScanForward64) #endif #endif @@ -47,10 +48,10 @@ { unsigned long index; #if defined(FM_BITSCAN_WINDOWS64) - if ( !_BitScanReverse64(&index, x) ) - return 0; - else + if ( _BitScanReverse64(&index, x) ) return index + 1; + else + return 0; #else if ( (x & 0xFFFFFFFF00000000) == 0 ) { @@ -69,14 +70,28 @@ #define FM_BIT_LENGTH(x) FM_BitLength(x) + #if defined(FM_BITSCAN_WINDOWS64) + + uint16_t FM_ctz(uint64_t x) { + unsigned long index; + if (_BitScanForward64(&index, x)) { + return (uint16_t)index; + } + return 64; + } + #define FM_CTZ(x) FM_ctz(x) + + #endif #elif defined(__GNUC__) #define FM_BIT_LENGTH(x) ((uint32_t)(8 * sizeof(unsigned long long) - __builtin_clzll(x))) + #define FM_CTZ(x) __builtin_ctzll(x) #elif defined(__clang__) #if __has_builtin(__builtin_clzll) #define FM_BIT_LENGTH(x) ((uint32_t)(8 * sizeof(unsigned long long) - __builtin_clzll(x))) + #define FM_CTZ(x) __builtin_ctzll(x) #endif #endif @@ -116,21 +131,33 @@ #endif -static uint64_t deBruijn = 0x022FDD63CC95386D; +#if !defined(FM_CTZ) -static uint8_t MultiplyDeBruijnBitPosition[64] = -{ - 0, 1, 2, 53, 3, 7, 54, 27, - 4, 38, 41, 8, 34, 55, 48, 28, - 62, 5, 39, 46, 44, 42, 22, 9, - 24, 35, 59, 56, 49, 18, 29, 11, - 63, 52, 6, 26, 37, 40, 33, 47, - 61, 45, 43, 21, 23, 58, 17, 10, - 51, 25, 36, 32, 60, 20, 57, 16, - 50, 31, 19, 15, 30, 14, 13, 12, -}; + static uint64_t deBruijn = 0x022FDD63CC95386D; -#define FM_CTZ(x) MultiplyDeBruijnBitPosition[((uint64_t)((x) & -(int64_t)(x)) * deBruijn) >> 58] + static uint8_t MultiplyDeBruijnBitPosition[64] = + { + 0, 1, 2, 53, 3, 7, 54, 27, + 4, 38, 41, 8, 34, 55, 48, 28, + 62, 5, 39, 46, 44, 42, 22, 9, + 24, 35, 59, 56, 49, 18, 29, 11, + 63, 52, 6, 26, 37, 40, 33, 47, + 61, 45, 43, 21, 23, 58, 17, 10, + 51, 25, 36, 32, 60, 20, 57, 16, + 50, 31, 19, 15, 30, 14, 13, 12, + }; + + #define FM_CTZ(x) MultiplyDeBruijnBitPosition[((uint64_t)((x) & -(int64_t)(x)) * deBruijn) >> 58] + +#endif + +#if defined(_MSC_VER) + #define THREAD_LOCAL thread_local +#else + #define THREAD_LOCAL __thread +#endif + +static THREAD_LOCAL uint64_t TEXT_MASK[256*2]; static uint16_t valTable[64] = { @@ -225,7 +252,6 @@ ValueElements* evaluate_nameOnly(TextContext* pText_ctxt, } if ( bits == 0 ) { - memset(val, 0, sizeof(ValueElements)); return val; } else @@ -323,6 +349,10 @@ ValueElements* evaluate_nameOnly(TextContext* pText_ctxt, score = prefix_score + pVal->score - 0.2f * (pVal->beg - i); end_pos = pVal->end; } + else + { + break; + } } } if ( score > max_score ) @@ -428,7 +458,6 @@ ValueElements* evaluate(TextContext* pText_ctxt, } if ( bits == 0 ) { - memset(val, 0, sizeof(ValueElements)); return val; } else @@ -469,13 +498,15 @@ ValueElements* evaluate(TextContext* pText_ctxt, #endif special = k == 0 ? 5 : 3; else if ( isupper(text[i]) ) - special = !isupper(text[i-1]) || (i+1 < text_len && islower(text[i+1])) ? 3 : 0; + special = (!isupper(text[i-1]) || (i+1 < text_len && islower(text[i+1])) ? + (i < 5 ? 5 : 3) : 0); /* else if ( text[i-1] == '_' || text[i-1] == '-' || text[i-1] == ' ' ) */ /* special = 3; */ /* else if ( text[i-1] == '.' ) */ /* special = 3; */ else if ( !isalnum(text[i-1]) ) - special = 3; + /* if there is an icon at the beginning, `if ( i == 0 )` won't meet */ + special = i < 5 ? 5 : 3; else special = 0; ++i; @@ -538,8 +569,13 @@ ValueElements* evaluate(TextContext* pText_ctxt, score = prefix_score + pVal->score - 0.3f * (pVal->beg - i); end_pos = pVal->end; } + else + { + break; + } } } + if ( score > max_score ) { max_score = score; @@ -688,6 +724,7 @@ float getWeight(const char* text, uint16_t text_len, } int16_t first_char_pos = -1; + uint16_t short_text_len = text_len; if ( pPattern_ctxt->is_lower ) { int16_t i; @@ -714,13 +751,22 @@ float getWeight(const char* text, uint16_t text_len, if ( last_char_pos == -1 ) return MIN_WEIGHT; - col_num = (text_len + 63) >> 6; /* (text_len + 63)/64 */ - /* uint64_t text_mask[256][col_num] */ - text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); - if ( !text_mask ) + short_text_len = last_char_pos + 1; + col_num = (short_text_len + 63) >> 6; /* (short_text_len + 63)/64 */ + if (col_num <= 2) { - fprintf(stderr, "Out of memory in getWeight()!\n"); - return MIN_WEIGHT; + memset(TEXT_MASK, 0, sizeof(TEXT_MASK)); + text_mask = TEXT_MASK; + } + else + { + /* uint64_t text_mask[256][col_num] */ + text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); + if ( !text_mask ) + { + fprintf(stderr, "Out of memory in getWeight()!\n"); + return MIN_WEIGHT; + } } char c; for ( i = first_char_pos; i <= last_char_pos; ++i ) @@ -792,13 +838,22 @@ float getWeight(const char* text, uint16_t text_len, if ( last_char_pos == -1 ) return MIN_WEIGHT; - col_num = (text_len + 63) >> 6; - /* uint64_t text_mask[256][col_num] */ - text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); - if ( !text_mask ) + short_text_len = last_char_pos + 1; + col_num = (short_text_len + 63) >> 6; + if (col_num <= 2) { - fprintf(stderr, "Out of memory in getWeight()!\n"); - return MIN_WEIGHT; + memset(TEXT_MASK, 0, sizeof(TEXT_MASK)); + text_mask = TEXT_MASK; + } + else + { + /* uint64_t text_mask[256][col_num] */ + text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); + if ( !text_mask ) + { + fprintf(stderr, "Out of memory in getWeight()!\n"); + return MIN_WEIGHT; + } } char c; int16_t i; @@ -830,7 +885,10 @@ float getWeight(const char* text, uint16_t text_len, if ( j < pattern_len ) { - free(text_mask); + if (col_num > 2) + { + free(text_mask); + } return MIN_WEIGHT; } @@ -838,7 +896,7 @@ float getWeight(const char* text, uint16_t text_len, { int16_t i; j = 0; - for ( i = first_char_pos; i < text_len; ++i ) + for ( i = first_char_pos; i < short_text_len; ++i ) { if ( j < pPattern_ctxt->actual_pattern_len ) { @@ -854,17 +912,20 @@ float getWeight(const char* text, uint16_t text_len, if ( j < pPattern_ctxt->actual_pattern_len ) { - free(text_mask); + if (col_num > 2) + { + free(text_mask); + } return MIN_WEIGHT; } } TextContext text_ctxt; text_ctxt.text = text; - text_ctxt.text_len = text_len; + text_ctxt.text_len = short_text_len; text_ctxt.text_mask = text_mask; text_ctxt.col_num = col_num; - text_ctxt.offset = 0; + text_ctxt.offset = first_char_pos; ValueElements val[64]; memset(val, 0, sizeof(val)); @@ -875,7 +936,10 @@ float getWeight(const char* text, uint16_t text_len, uint16_t beg = pVal->beg; uint16_t end = pVal->end; - free(text_mask); + if (col_num > 2) + { + free(text_mask); + } return score + (1 >> beg) + 1.0f/(beg + end) + 1.0f/text_len; } @@ -885,9 +949,12 @@ float getWeight(const char* text, uint16_t text_len, float score = pVal->score; uint16_t beg = pVal->beg; - free(text_mask); + if (col_num > 2) + { + free(text_mask); + } - return score + (float)pattern_len/text_len + (float)(pattern_len << 1)/(text_len - beg); + return score + (float)(pattern_len<<1)/text_len + (float)pattern_len/(text_len - beg); } } @@ -1255,19 +1322,20 @@ HighlightGroup* evaluateHighlights(TextContext* pText_ctxt, max_prefix_score = prefix_score; pText_ctxt->offset = i; HighlightGroup* pGroup = evaluateHighlights(pText_ctxt, pPattern_ctxt, k + n, groups); - if ( pGroup ) + if ( pGroup && pGroup->end ) { - if ( pGroup->end ) - { - score = prefix_score + pGroup->score - 0.3f * (pGroup->beg - i); - cur_highlights.score = score; - cur_highlights.beg = i - n; - cur_highlights.end = pGroup->end; - cur_highlights.positions[0].col = i - n + 1; - cur_highlights.positions[0].len = n; - memcpy(cur_highlights.positions + 1, pGroup->positions, pGroup->end_index * sizeof(HighlightPos)); - cur_highlights.end_index = pGroup->end_index + 1; - } + score = prefix_score + pGroup->score - 0.3f * (pGroup->beg - i); + cur_highlights.score = score; + cur_highlights.beg = i - n; + cur_highlights.end = pGroup->end; + cur_highlights.positions[0].col = i - n + 1; + cur_highlights.positions[0].len = n; + memcpy(cur_highlights.positions + 1, pGroup->positions, pGroup->end_index * sizeof(HighlightPos)); + cur_highlights.end_index = pGroup->end_index + 1; + } + else + { + break; } } } @@ -1449,9 +1517,10 @@ HighlightGroup* getHighlights(const char* text, } } + int16_t first_char_pos = -1; + uint16_t short_text_len = text_len; if ( pPattern_ctxt->is_lower ) { - int16_t first_char_pos = -1; int16_t i; for ( i = 0; i < text_len; ++i ) { @@ -1472,13 +1541,22 @@ HighlightGroup* getHighlights(const char* text, } } - col_num = (text_len + 63) >> 6; /* (text_len + 63)/64 */ - /* uint64_t text_mask[256][col_num] */ - text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); - if ( !text_mask ) + short_text_len = last_char_pos + 1; + col_num = (short_text_len + 63) >> 6; /* (short_text_len + 63)/64 */ + if (col_num <= 2) { - fprintf(stderr, "Out of memory in getHighlights()!\n"); - return NULL; + memset(TEXT_MASK, 0, sizeof(TEXT_MASK)); + text_mask = TEXT_MASK; + } + else + { + /* uint64_t text_mask[256][col_num] */ + text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); + if ( !text_mask ) + { + fprintf(stderr, "Out of memory in getHighlights()!\n"); + return NULL; + } } char c; for ( i = first_char_pos; i <= last_char_pos; ++i ) @@ -1491,7 +1569,6 @@ HighlightGroup* getHighlights(const char* text, } else { - int16_t first_char_pos = -1; if ( isupper(first_char) ) { int16_t i; @@ -1543,13 +1620,22 @@ HighlightGroup* getHighlights(const char* text, } } - col_num = (text_len + 63) >> 6; - /* uint64_t text_mask[256][col_num] */ - text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); - if ( !text_mask ) + short_text_len = last_char_pos + 1; + col_num = (short_text_len + 63) >> 6; + if (col_num <= 2) { - fprintf(stderr, "Out of memory in getHighlights()!\n"); - return NULL; + memset(TEXT_MASK, 0, sizeof(TEXT_MASK)); + text_mask = TEXT_MASK; + } + else + { + /* uint64_t text_mask[256][col_num] */ + text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); + if ( !text_mask ) + { + fprintf(stderr, "Out of memory in getHighlights()!\n"); + return NULL; + } } char c; @@ -1576,17 +1662,20 @@ HighlightGroup* getHighlights(const char* text, TextContext text_ctxt; text_ctxt.text = text; - text_ctxt.text_len = text_len; + text_ctxt.text_len = short_text_len; text_ctxt.text_mask = text_mask; text_ctxt.col_num = col_num; - text_ctxt.offset = 0; + text_ctxt.offset = first_char_pos; /* HighlightGroup* groups[pattern_len] */ HighlightGroup** groups = (HighlightGroup**)calloc(pattern_len, sizeof(HighlightGroup*)); if ( !groups ) { fprintf(stderr, "Out of memory in getHighlights()!\n"); - free(text_mask); + if (col_num > 2) + { + free(text_mask); + } return NULL; } @@ -1596,7 +1685,10 @@ HighlightGroup* getHighlights(const char* text, else pGroup = evaluateHighlights(&text_ctxt, pPattern_ctxt, 0, groups); - free(text_mask); + if (col_num > 2) + { + free(text_mask); + } uint16_t i; for ( i = 0; i < pattern_len; ++i ) { diff --git a/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.cpp b/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.cpp new file mode 100644 index 00000000..d5f696ce --- /dev/null +++ b/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.cpp @@ -0,0 +1,2005 @@ +/** + * Copyright (C) 2017 Yggdroot + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PY_SSIZE_T_CLEAN + #define PY_SSIZE_T_CLEAN +#endif + +#include +#include +#include +#include +#include +#include "fuzzyMatch.h" + + +#if defined(_MSC_VER) && \ + (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_X64)) + + #define FM_BITSCAN_WINDOWS + + #include + #pragma intrinsic(_BitScanReverse) + + #if defined(_M_AMD64) || defined(_M_X64) + #define FM_BITSCAN_WINDOWS64 + #pragma intrinsic(_BitScanReverse64) + #pragma intrinsic(_BitScanForward64) + #endif + +#endif + +#if defined(FM_BITSCAN_WINDOWS) + + uint32_t FM_BitLength(uint64_t x) + { + unsigned long index; + #if defined(FM_BITSCAN_WINDOWS64) + if ( _BitScanReverse64(&index, x) ) + return index + 1; + else + return 0; + #else + if ( (x & 0xFFFFFFFF00000000) == 0 ) + { + if ( !_BitScanReverse(&index, (unsigned long)x) ) + return 0; + else + return index + 1; + } + else + { + _BitScanReverse(&index, (unsigned long)(x >> 32)); + return index + 33; + } + #endif + } + + #define FM_BIT_LENGTH(x) FM_BitLength(x) + + #if defined(FM_BITSCAN_WINDOWS64) + + uint16_t FM_ctz(uint64_t x) { + unsigned long index; + if (_BitScanForward64(&index, x)) { + return (uint16_t)index; + } + return 64; + } + #define FM_CTZ(x) FM_ctz(x) + + #endif +#elif defined(__GNUC__) + + #define FM_BIT_LENGTH(x) ((uint32_t)(8 * sizeof(unsigned long long) - __builtin_clzll(x))) + #define FM_CTZ(x) __builtin_ctzll(x) + +#elif defined(__clang__) + + #if __has_builtin(__builtin_clzll) + #define FM_BIT_LENGTH(x) ((uint32_t)(8 * sizeof(unsigned long long) - __builtin_clzll(x))) + #define FM_CTZ(x) __builtin_ctzll(x) + #endif + +#endif + +#if !defined(FM_BIT_LENGTH) + + static uint8_t clz_table_8bit[256] = + { + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + uint32_t FM_BIT_LENGTH(uint64_t x) + { + uint32_t n = 0; + if ((x & 0xFFFFFFFF00000000) == 0) {n = 32; x <<= 32;} + if ((x & 0xFFFF000000000000) == 0) {n += 16; x <<= 16;} + if ((x & 0xFF00000000000000) == 0) {n += 8; x <<= 8;} + n += (uint32_t)clz_table_8bit[x >> 56]; + + return 64 - n; + } + +#endif + +#if !defined(FM_CTZ) + + static uint64_t deBruijn = 0x022FDD63CC95386D; + + static uint8_t MultiplyDeBruijnBitPosition[64] = + { + 0, 1, 2, 53, 3, 7, 54, 27, + 4, 38, 41, 8, 34, 55, 48, 28, + 62, 5, 39, 46, 44, 42, 22, 9, + 24, 35, 59, 56, 49, 18, 29, 11, + 63, 52, 6, 26, 37, 40, 33, 47, + 61, 45, 43, 21, 23, 58, 17, 10, + 51, 25, 36, 32, 60, 20, 57, 16, + 50, 31, 19, 15, 30, 14, 13, 12, + }; + + #define FM_CTZ(x) MultiplyDeBruijnBitPosition[((uint64_t)((x) & -(int64_t)(x)) * deBruijn) >> 58] + +#endif + +#if defined(_MSC_VER) + #define THREAD_LOCAL thread_local +#else + #define THREAD_LOCAL __thread +#endif + +static THREAD_LOCAL uint64_t TEXT_MASK[256*2]; + +static uint16_t valTable[64] = +{ + 0, 1, 4, 7, 13, 19, 25, 31, + 37, 43, 49, 55, 61, 67, 73, 79, + 85, 91, 97, 103, 109, 115, 121, 127, + 133, 139, 145, 151, 157, 163, 169, 175, + 181, 187, 193, 199, 205, 211, 217, 223, + 229, 235, 241, 247, 253, 259, 265, 271, + 277, 283, 289, 295, 301, 307, 313, 319, + 325, 331, 337, 343, 349, 355, 361, 367 +}; + +typedef struct TextContext +{ + const char* text; + uint64_t* text_mask; + uint16_t text_len; + uint16_t col_num; + uint16_t offset; +}TextContext; + +typedef struct ValueElements +{ + float score; + uint16_t beg; + uint16_t end; +}ValueElements; + +PatternContext* initPattern(const char* pattern, uint16_t pattern_len) +{ + PatternContext* pPattern_ctxt = (PatternContext*)malloc(sizeof(PatternContext)); + if ( !pPattern_ctxt ) + { + fprintf(stderr, "Out of memory in initPattern()!\n"); + return NULL; + } + pPattern_ctxt->actual_pattern_len = pattern_len; + if ( pattern_len >= 64 ) + { + pattern_len = 63; + } + pPattern_ctxt->pattern = pattern; + pPattern_ctxt->pattern_len = pattern_len; + memset(pPattern_ctxt->pattern_mask, -1, sizeof(pPattern_ctxt->pattern_mask)); + + uint16_t i; + for ( i = 0; i < pattern_len; ++i ) + { + pPattern_ctxt->pattern_mask[(uint8_t)pattern[i]] ^= (1LL << i); + if ( islower(pattern[i]) && pPattern_ctxt->pattern_mask[(uint8_t)toupper(pattern[i])] != -1 ) + { + pPattern_ctxt->pattern_mask[(uint8_t)toupper(pattern[i])] ^= (1LL << i); + } + } + pPattern_ctxt->is_lower = 1; + + for ( i = 0; i < pattern_len; ++i ) + { + if ( isupper(pattern[i]) ) + { + pPattern_ctxt->is_lower = 0; + break; + } + } + + return pPattern_ctxt; +} + +ValueElements* evaluate_nameOnly(TextContext* pText_ctxt, + PatternContext* pPattern_ctxt, + uint16_t k, + ValueElements val[]) +{ + uint64_t* text_mask = pText_ctxt->text_mask; + uint16_t col_num = pText_ctxt->col_num; + uint16_t j = pText_ctxt->offset; + + const char* pattern = pPattern_ctxt->pattern; + uint16_t base_offset = pattern[k] * col_num; + uint64_t x = text_mask[base_offset + (j >> 6)] >> (j & 63); + uint16_t i = 0; + + if ( x == 0 ) + { + uint64_t bits = 0; + uint16_t col = 0; + for ( col = (j >> 6) + 1; col < col_num; ++col ) + { + if ( (bits = text_mask[base_offset + col]) != 0 ) + break; + } + if ( bits == 0 ) + { + memset(val, 0, sizeof(ValueElements)); + return val; + } + else + { + i = (col << 6) + FM_CTZ(bits); + } + } + else + { + i = j + FM_CTZ(x); + } + + /** + * e.g., text = '~abc~~AbcD~~', pattern = 'abcd' + * j > 0 means val[k].beg > 0, means k in val + */ + if ( j > 0 && val[k].beg >= j ) + return val + k; + + uint16_t beg = 0; + uint16_t end = 0; + + uint16_t max_prefix_score = 0; + float max_score = MIN_WEIGHT; + + const char* text = pText_ctxt->text; + uint16_t text_len = pText_ctxt->text_len; + uint16_t pattern_len = pPattern_ctxt->pattern_len - k; + int64_t* pattern_mask = pPattern_ctxt->pattern_mask; + + uint16_t special = 0; + if ( i == 0 ) + special = 3; + else if ( isupper(text[i]) ) + special = !isupper(text[i-1]) || (i+1 < text_len && islower(text[i+1])) ? 3 : 0; + /* else if ( text[i-1] == '_' || text[i-1] == '-' || text[i-1] == ' ' ) */ + /* special = 3; */ + /* else if ( text[i-1] == '.' ) */ + /* special = 3; */ + else if ( !isalnum(text[i-1]) ) + special = 3; + else + special = 0; + ++i; + int64_t d = -2; /* ~1 */ + int64_t last = d; + while ( i < text_len ) + { + last = d; + char c = text[i]; + /* c in pattern */ + if ( pattern_mask[(uint8_t)c] != -1 ) + d = (d << 1) | (pattern_mask[(uint8_t)c] >> k); + /** + * text = 'xxABC', pattern = 'abc'; text[i] == 'B' + * text = 'xxABC', pattern = 'abc'; text[i] == 'C' + * NOT text = 'xxABCd', pattern = 'abc'; text[i] == 'C' + * 'Cd' is considered as a word + */ + else if ( isupper(text[i-1]) && pattern_mask[(uint8_t)tolower(c)] != -1 + && (i+1 == text_len || !islower(text[i+1])) ) + d = (d << 1) | (pattern_mask[(uint8_t)tolower(c)] >> k); + else + d = ~0; + + if ( d >= last ) + { + float score = MIN_WEIGHT; + uint16_t end_pos = 0; + uint16_t n = FM_BIT_LENGTH(~last); + /* e.g., text = '~~abcd~~~~', pattern = 'abcd' */ + if ( n == pattern_len ) + { + score = (float)(special > 0 ? (n > 1 ? valTable[n+1] : valTable[n]) + special : valTable[n]); + if ( special > 0 ) + { + val[k].score = score; + val[k].beg = i - n; + val[k].end = i; + return val + k; + } + else + end_pos = i; + } + else + { + uint16_t prefix_score = special > 0 ? (n > 1 ? valTable[n+1] : valTable[n]) + special : valTable[n]; + if ( prefix_score > max_prefix_score ) + { + max_prefix_score = prefix_score; + pText_ctxt->offset = i; + ValueElements* pVal = evaluate_nameOnly(pText_ctxt, pPattern_ctxt, k + n, val); + if ( pVal->end ) + { + score = prefix_score + pVal->score - 0.2f * (pVal->beg - i); + end_pos = pVal->end; + } + } + } + if ( score > max_score ) + { + max_score = score; + beg = i - n; + end = end_pos; + } + /* e.g., text = '~_ababc~~~~', pattern = 'abc' */ + special = 0; + } + + /* + * e.g., text = 'a~c~~~~ab~c', pattern = 'abc', + * to find the index of the second 'a' + * `d == last` is for the case when text = 'kpi_oos1', pattern = 'kos' + */ + if ( d == ~0 || d == last ) + { + x = text_mask[base_offset + (i >> 6)] >> (i & 63); + + if ( x == 0 ) + { + uint64_t bits = 0; + uint16_t col = 0; + for ( col = (i >> 6) + 1; col < col_num; ++col ) + { + if ( (bits = text_mask[base_offset + col]) != 0 ) + break; + } + if ( bits == 0 ) + break; + else + i = (col << 6) + FM_CTZ(bits); + } + else + { + i += FM_CTZ(x); + } + + if ( isupper(text[i]) ) + special = !isupper(text[i-1]) || (i+1 < text_len && islower(text[i+1])) ? 3 : 0; + /* else if ( text[i-1] == '_' || text[i-1] == '-' || text[i-1] == ' ' ) */ + /* special = 3; */ + /* else if ( text[i-1] == '.' ) */ + /* special = 3; */ + else if ( !isalnum(text[i-1]) ) + special = 3; + else + special = 0; + d = -2; + ++i; + } + else + ++i; + } + + /* e.g., text = '~~~~abcd', pattern = 'abcd' */ + if ( i == text_len ) + { + if ( ~d >> (pattern_len - 1) ) + { + float score = (float)(special > 0 ? (pattern_len > 1 ? valTable[pattern_len + 1] : valTable[pattern_len]) + special + : valTable[pattern_len]); + if ( score > max_score ) + { + max_score = score; + beg = i - pattern_len; + end = i; + } + } + } + + val[k].score = max_score; + val[k].beg = beg; + val[k].end = end; + + return val + k; +} + +ValueElements* evaluate(TextContext* pText_ctxt, + PatternContext* pPattern_ctxt, + uint16_t k, + ValueElements val[]) +{ + uint64_t* text_mask = pText_ctxt->text_mask; + uint16_t col_num = pText_ctxt->col_num; + uint16_t j = pText_ctxt->offset; + + const char* pattern = pPattern_ctxt->pattern; + uint16_t base_offset = pattern[k] * col_num; + uint64_t x = text_mask[base_offset + (j >> 6)] >> (j & 63); + uint16_t i = 0; + + if ( x == 0 ) + { + uint64_t bits = 0; + uint16_t col = 0; + for ( col = (j >> 6) + 1; col < col_num; ++col ) + { + if ( (bits = text_mask[base_offset + col]) != 0 ) + break; + } + if ( bits == 0 ) + { + return val; + } + else + { + i = (col << 6) + FM_CTZ(bits); + } + } + else + { + i = j + FM_CTZ(x); + } + + /** + * e.g., text = '~abc~~AbcD~~', pattern = 'abcd' + * j > 0 means val[k].beg > 0, means k in val + */ + if ( j > 0 && val[k].beg >= j ) + return val + k; + + uint16_t beg = 0; + uint16_t end = 0; + + uint16_t max_prefix_score = 0; + float max_score = MIN_WEIGHT; + + const char* text = pText_ctxt->text; + uint16_t text_len = pText_ctxt->text_len; + uint16_t pattern_len = pPattern_ctxt->pattern_len - k; + int64_t* pattern_mask = pPattern_ctxt->pattern_mask; + + uint16_t special = 0; + if ( i == 0 ) + special = 5; +#if defined(_MSC_VER) + else if ( text[i-1] == '\\' || text[i-1] == '/' ) +#else + else if ( text[i-1] == '/' ) +#endif + special = k == 0 ? 5 : 3; + else if ( isupper(text[i]) ) + special = (!isupper(text[i-1]) || (i+1 < text_len && islower(text[i+1])) ? + (i < 5 ? 5 : 3) : 0); + /* else if ( text[i-1] == '_' || text[i-1] == '-' || text[i-1] == ' ' ) */ + /* special = 3; */ + /* else if ( text[i-1] == '.' ) */ + /* special = 3; */ + else if ( !isalnum(text[i-1]) ) + /* if there is an icon at the beginning, `if ( i == 0 )` won't meet */ + special = i < 5 ? 5 : 3; + else + special = 0; + ++i; + int64_t d = -2; /* ~1 */ + int64_t last = d; + while ( i < text_len ) + { + last = d; + char c = text[i]; + /* c in pattern */ + if ( pattern_mask[(uint8_t)c] != -1 ) + d = (d << 1) | (pattern_mask[(uint8_t)c] >> k); + /** + * text = 'xxABC', pattern = 'abc'; text[i] == 'B' + * text = 'xxABC', pattern = 'abc'; text[i] == 'C' + * NOT text = 'xxABCd', pattern = 'abc'; text[i] == 'C' + * 'Cd' is considered as a word + */ + /* else if ( isupper(text[i-1]) && pattern_mask[(uint8_t)tolower(c)] != -1 */ + /* && (i+1 == text_len || !islower(text[i+1])) ) */ + else if ( pattern_mask[(uint8_t)tolower(c)] != -1 ) + d = (d << 1) | (pattern_mask[(uint8_t)tolower(c)] >> k); + else + d = ~0; + + if ( d >= last ) + { + float score = MIN_WEIGHT; + uint16_t end_pos = 0; + uint16_t n = FM_BIT_LENGTH(~last); + /* e.g., text = '~~abcd~~~~', pattern = 'abcd' */ + if ( n == pattern_len ) + { + score = (float)(special > 0 ? (n > 1 ? valTable[n+1] : valTable[n]) + special : valTable[n]); + if ( (k == 0 && special == 5) || (k > 0 && special > 0) ) + { + val[k].score = score; + val[k].beg = i - n; + val[k].end = i; + return val + k; + } + else + end_pos = i; + } + else + { + uint16_t prefix_score = special > 0 ? (n > 1 ? valTable[n+1] : valTable[n]) + special : valTable[n]; + /** + * e.g., text = 'AbcxxAbcyyde', pattern = 'abcde' + * prefer matching 'Abcyyde' + */ + if ( prefix_score > max_prefix_score + || (special > 0 && prefix_score == max_prefix_score) ) + { + max_prefix_score = prefix_score; + pText_ctxt->offset = i; + ValueElements* pVal = evaluate(pText_ctxt, pPattern_ctxt, k + n, val); + if ( pVal->end ) + { + score = prefix_score + pVal->score - 0.3f * (pVal->beg - i); + end_pos = pVal->end; + } + else + { + break; + } + } + } + + if ( score > max_score ) + { + max_score = score; + beg = i - n; + end = end_pos; + } + /* e.g., text = '~_ababc~~~~', pattern = 'abc' */ + special = 0; + } + + /* + * e.g., text = 'a~c~~~~ab~c', pattern = 'abc', + * to find the index of the second 'a' + * `d == last` is for the case when text = 'kpi_oos1', pattern = 'kos' + */ + if ( d == ~0 || d == last ) + { + x = text_mask[base_offset + (i >> 6)] >> (i & 63); + + if ( x == 0 ) + { + uint64_t bits = 0; + uint16_t col = 0; + for ( col = (i >> 6) + 1; col < col_num; ++col ) + { + if ( (bits = text_mask[base_offset + col]) != 0 ) + break; + } + if ( bits == 0 ) + break; + else + i = (col << 6) + FM_CTZ(bits); + } + else + { + i += FM_CTZ(x); + } + +#if defined(_MSC_VER) + if ( text[i-1] == '\\' || text[i-1] == '/' ) +#else + if ( text[i-1] == '/' ) +#endif + special = k == 0 ? 5 : 3; + else if ( isupper(text[i]) ) + special = !isupper(text[i-1]) || (i+1 < text_len && islower(text[i+1])) ? 3 : 0; + /* else if ( text[i-1] == '_' || text[i-1] == '-' || text[i-1] == ' ' ) */ + /* special = 3; */ + /* else if ( text[i-1] == '.' ) */ + /* special = 3; */ + else if ( !isalnum(text[i-1]) ) + special = 3; + else + special = 0; + d = -2; + ++i; + } + else + ++i; + } + + /* e.g., text = '~~~~abcd', pattern = 'abcd' */ + if ( i == text_len ) + { + if ( ~d >> (pattern_len - 1) ) + { + float score = (float)(special > 0 ? (pattern_len > 1 ? valTable[pattern_len + 1] : valTable[pattern_len]) + special + : valTable[pattern_len]); + if ( score > max_score ) + { + max_score = score; + beg = i - pattern_len; + end = i; + } + } + } + + val[k].score = max_score; + val[k].beg = beg; + val[k].end = end; + + return val + k; +} + +float getWeight(const char* text, uint16_t text_len, + PatternContext* pPattern_ctxt, + uint8_t is_name_only) +{ + if ( !text || !pPattern_ctxt ) + return MIN_WEIGHT; + + uint16_t j = 0; + uint16_t col_num = 0; + uint64_t* text_mask = NULL; + const char* pattern = pPattern_ctxt->pattern; + uint16_t pattern_len = pPattern_ctxt->pattern_len; + int64_t* pattern_mask = pPattern_ctxt->pattern_mask; + char first_char = pattern[0]; + char last_char = pattern[pattern_len - 1]; + + /* maximum number of int16_t is (1 << 15) - 1 */ + if ( text_len >= (1 << 15) ) + { + text_len = (1 << 15) - 1; + } + + if ( pattern_len == 1 ) + { + if ( isupper(first_char) ) + { + int16_t first_char_pos = -1; + int16_t i; + for ( i = 0; i < text_len; ++i ) + { + if ( text[i] == first_char ) + { + first_char_pos = i; + break; + } + } + if ( first_char_pos == -1 ) + return MIN_WEIGHT; + else + return 1.0f/(first_char_pos + 1) + 1.0f/text_len; + } + else + { + int16_t first_char_pos = -1; + int16_t i; + for ( i = 0; i < text_len; ++i ) + { + if ( tolower(text[i]) == first_char ) + { + if ( first_char_pos == -1 ) + first_char_pos = i; + + if ( isupper(text[i]) || i == 0 || !isalnum(text[i-1]) ) + return 2 + 1.0f/(i + 1) + 1.0f/text_len; + } + } + if ( first_char_pos == -1 ) + return MIN_WEIGHT; + else + return 1.0f/(first_char_pos + 1) + 1.0f/text_len; + } + } + + int16_t first_char_pos = -1; + uint16_t short_text_len = text_len; + if ( pPattern_ctxt->is_lower ) + { + int16_t i; + for ( i = 0; i < text_len; ++i ) + { + if ( tolower(text[i]) == first_char ) + { + first_char_pos = i; + break; + } + } + if ( first_char_pos == -1 ) + return MIN_WEIGHT; + + int16_t last_char_pos = -1; + for ( i = text_len - 1; i >= first_char_pos; --i ) + { + if ( tolower(text[i]) == last_char ) + { + last_char_pos = i; + break; + } + } + if ( last_char_pos == -1 ) + return MIN_WEIGHT; + + short_text_len = last_char_pos + 1; + col_num = (short_text_len + 63) >> 6; /* (short_text_len + 63)/64 */ + if (col_num <= 2) + { + memset(TEXT_MASK, 0, sizeof(TEXT_MASK)); + text_mask = TEXT_MASK; + } + else + { + /* uint64_t text_mask[256][col_num] */ + text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); + if ( !text_mask ) + { + fprintf(stderr, "Out of memory in getWeight()!\n"); + return MIN_WEIGHT; + } + } + char c; + for ( i = first_char_pos; i <= last_char_pos; ++i ) + { + c = tolower(text[i]); + /* c in pattern */ + if ( pattern_mask[(uint8_t)c] != -1 ) + { + text_mask[(uint8_t)c * col_num + (i >> 6)] |= 1ULL << (i & 63); + if ( j < pattern_len && c == pattern[j] ) + ++j; + } + } + } + else + { + if ( isupper(first_char) ) + { + int16_t i; + for ( i = 0; i < text_len; ++i ) + { + if ( text[i] == first_char ) + { + first_char_pos = i; + break; + } + } + } + else + { + int16_t i; + for ( i = 0; i < text_len; ++i ) + { + if ( tolower(text[i]) == first_char ) + { + first_char_pos = i; + break; + } + } + } + if ( first_char_pos == -1 ) + return MIN_WEIGHT; + + int16_t last_char_pos = -1; + if ( isupper(last_char) ) + { + int16_t i; + for ( i = text_len - 1; i >= first_char_pos; --i ) + { + if ( text[i] == last_char ) + { + last_char_pos = i; + break; + } + } + } + else + { + int16_t i; + for ( i = text_len - 1; i >= first_char_pos; --i ) + { + if ( tolower(text[i]) == last_char ) + { + last_char_pos = i; + break; + } + } + } + if ( last_char_pos == -1 ) + return MIN_WEIGHT; + + short_text_len = last_char_pos + 1; + col_num = (short_text_len + 63) >> 6; + if (col_num <= 2) + { + memset(TEXT_MASK, 0, sizeof(TEXT_MASK)); + text_mask = TEXT_MASK; + } + else + { + /* uint64_t text_mask[256][col_num] */ + text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); + if ( !text_mask ) + { + fprintf(stderr, "Out of memory in getWeight()!\n"); + return MIN_WEIGHT; + } + } + char c; + int16_t i; + for ( i = first_char_pos; i <= last_char_pos; ++i ) + { + c = text[i]; + if ( isupper(c) ) + { + /* c in pattern */ + if ( pattern_mask[(uint8_t)c] != -1 ) + text_mask[(uint8_t)c * col_num + (i >> 6)] |= 1ULL << (i & 63); + if ( pattern_mask[(uint8_t)tolower(c)] != -1 ) + text_mask[(uint8_t)tolower(c) * col_num + (i >> 6)] |= 1ULL << (i & 63); + if ( j < pattern_len && c == toupper(pattern[j]) ) + ++j; + } + else + { + /* c in pattern */ + if ( pattern_mask[(uint8_t)c] != -1 ) + { + text_mask[(uint8_t)c * col_num + (i >> 6)] |= 1ULL << (i & 63); + if ( j < pattern_len && c == pattern[j] ) + ++j; + } + } + } + } + + if ( j < pattern_len ) + { + if (col_num > 2) + { + free(text_mask); + } + return MIN_WEIGHT; + } + + if ( pPattern_ctxt->actual_pattern_len >= 64 ) + { + int16_t i; + j = 0; + for ( i = first_char_pos; i < short_text_len; ++i ) + { + if ( j < pPattern_ctxt->actual_pattern_len ) + { + if ( (pPattern_ctxt->is_lower && tolower(text[i]) == pattern[j]) + || text[i] == pattern[j] ) + { + ++j; + } + } + else + break; + } + + if ( j < pPattern_ctxt->actual_pattern_len ) + { + if (col_num > 2) + { + free(text_mask); + } + return MIN_WEIGHT; + } + } + + TextContext text_ctxt; + text_ctxt.text = text; + text_ctxt.text_len = short_text_len; + text_ctxt.text_mask = text_mask; + text_ctxt.col_num = col_num; + text_ctxt.offset = first_char_pos; + + ValueElements val[64]; + memset(val, 0, sizeof(val)); + if ( is_name_only ) + { + ValueElements* pVal = evaluate_nameOnly(&text_ctxt, pPattern_ctxt, 0, val); + float score = pVal->score; + uint16_t beg = pVal->beg; + uint16_t end = pVal->end; + + if (col_num > 2) + { + free(text_mask); + } + + return score + (1 >> beg) + 1.0f/(beg + end) + 1.0f/text_len; + } + else + { + ValueElements* pVal = evaluate(&text_ctxt, pPattern_ctxt, 0, val); + float score = pVal->score; + uint16_t beg = pVal->beg; + + if (col_num > 2) + { + free(text_mask); + } + + return score + (float)(pattern_len<<1)/text_len + (float)pattern_len/(text_len - beg); + } +} + + +HighlightGroup* evaluateHighlights_nameOnly(TextContext* pText_ctxt, + PatternContext* pPattern_ctxt, + uint16_t k, + HighlightGroup* groups[]) +{ + uint16_t j = pText_ctxt->offset; + + if ( groups[k] && groups[k]->beg >= j ) + return groups[k]; + + uint64_t* text_mask = pText_ctxt->text_mask; + uint16_t col_num = pText_ctxt->col_num; + + const char* pattern = pPattern_ctxt->pattern; + uint16_t base_offset = pattern[k] * col_num; + uint64_t x = text_mask[base_offset + (j >> 6)] >> (j & 63); + uint16_t i = 0; + + if ( x == 0 ) + { + uint64_t bits = 0; + uint16_t col = 0; + for ( col = (j >> 6) + 1; col < col_num; ++col ) + { + if ( (bits = text_mask[base_offset + col]) != 0 ) + break; + } + if ( bits == 0 ) + { + return NULL; + } + else + { + i = (col << 6) + FM_CTZ(bits); + } + } + else + { + i = j + FM_CTZ(x); + } + + uint16_t max_prefix_score = 0; + float max_score = MIN_WEIGHT; + + if ( !groups[k] ) + { + groups[k] = (HighlightGroup*)calloc(1, sizeof(HighlightGroup)); + if ( !groups[k] ) + { + fprintf(stderr, "Out of memory in evaluateHighlights_nameOnly()!\n"); + return NULL; + } + } + else + { + memset(groups[k], 0, sizeof(HighlightGroup)); + } + + HighlightGroup cur_highlights; + memset(&cur_highlights, 0, sizeof(HighlightGroup)); + + const char* text = pText_ctxt->text; + uint16_t text_len = pText_ctxt->text_len; + uint16_t pattern_len = pPattern_ctxt->pattern_len - k; + int64_t* pattern_mask = pPattern_ctxt->pattern_mask; + + uint16_t special = 0; + if ( i == 0 ) + special = 3; + else if ( isupper(text[i]) ) + special = !isupper(text[i-1]) || (i+1 < text_len && islower(text[i+1])) ? 3 : 0; + /* else if ( text[i-1] == '_' || text[i-1] == '-' || text[i-1] == ' ' ) */ + /* special = 3; */ + /* else if ( text[i-1] == '.' ) */ + /* special = 3; */ + else if ( !isalnum(text[i-1]) ) + special = 3; + else + special = 0; + ++i; + int64_t d = -2; /* ~1 */ + int64_t last = d; + while ( i < text_len ) + { + last = d; + char c = text[i]; + /* c in pattern */ + if ( pattern_mask[(uint8_t)c] != -1 ) + d = (d << 1) | (pattern_mask[(uint8_t)c] >> k); + /** + * text = 'xxABC', pattern = 'abc'; text[i] == 'B' + * text = 'xxABC', pattern = 'abc'; text[i] == 'C' + * NOT text = 'xxABCd', pattern = 'abc'; text[i] == 'C' + * 'Cd' is considered as a word + */ + else if ( isupper(text[i-1]) && pattern_mask[(uint8_t)tolower(c)] != -1 + && (i+1 == text_len || !islower(text[i+1])) ) + d = (d << 1) | (pattern_mask[(uint8_t)tolower(c)] >> k); + else + d = ~0; + + if ( d >= last ) + { + float score = MIN_WEIGHT; + uint16_t n = FM_BIT_LENGTH(~last); + /* e.g., text = '~~abcd~~~~', pattern = 'abcd' */ + if ( n == pattern_len ) + { + score = (float)(special > 0 ? (n > 1 ? valTable[n+1] : valTable[n]) + special : valTable[n]); + cur_highlights.score = score; + cur_highlights.beg = i - n; + cur_highlights.end = i; + cur_highlights.end_index = 1; + cur_highlights.positions[0].col = i - n + 1; + cur_highlights.positions[0].len = n; + if ( special > 0 ) + { + memcpy(groups[k], &cur_highlights, sizeof(HighlightGroup)); + return groups[k]; + } + } + else + { + uint16_t prefix_score = special > 0 ? (n > 1 ? valTable[n+1] : valTable[n]) + special : valTable[n]; + if ( prefix_score > max_prefix_score ) + { + max_prefix_score = prefix_score; + pText_ctxt->offset = i; + HighlightGroup* pGroup = evaluateHighlights_nameOnly(pText_ctxt, pPattern_ctxt, k + n, groups); + if ( pGroup ) + { + if ( pGroup->end ) + { + score = prefix_score + pGroup->score - 0.2f * (pGroup->beg - i); + cur_highlights.score = score; + cur_highlights.beg = i - n; + cur_highlights.end = pGroup->end; + cur_highlights.positions[0].col = i - n + 1; + cur_highlights.positions[0].len = n; + memcpy(cur_highlights.positions + 1, pGroup->positions, pGroup->end_index * sizeof(HighlightPos)); + cur_highlights.end_index = pGroup->end_index + 1; + } + } + } + } + if ( score > max_score ) + { + max_score = score; + memcpy(groups[k], &cur_highlights, sizeof(HighlightGroup)); + } + /* e.g., text = '~_ababc~~~~', pattern = 'abc' */ + special = 0; + } + + /* + * e.g., text = 'a~c~~~~ab~c', pattern = 'abc', + * to find the index of the second 'a' + * `d == last` is for the case when text = 'kpi_oos1', pattern = 'kos' + */ + if ( d == ~0 || d == last ) + { + x = text_mask[base_offset + (i >> 6)] >> (i & 63); + + if ( x == 0 ) + { + uint64_t bits = 0; + uint16_t col = 0; + for ( col = (i >> 6) + 1; col < col_num; ++col ) + { + if ( (bits = text_mask[base_offset + col]) != 0 ) + break; + } + if ( bits == 0 ) + break; + else + i = (col << 6) + FM_CTZ(bits); + } + else + { + i += FM_CTZ(x); + } + + if ( isupper(text[i]) ) + special = !isupper(text[i-1]) || (i+1 < text_len && islower(text[i+1])) ? 3 : 0; + /* else if ( text[i-1] == '_' || text[i-1] == '-' || text[i-1] == ' ' ) */ + /* special = 3; */ + /* else if ( text[i-1] == '.' ) */ + /* special = 3; */ + else if ( !isalnum(text[i-1]) ) + special = 3; + else + special = 0; + d = -2; + ++i; + } + else + ++i; + } + + /* e.g., text = '~~~~abcd', pattern = 'abcd' */ + if ( i == text_len ) + { + if ( ~d >> (pattern_len - 1) ) + { + float score = (float)(special > 0 ? (pattern_len > 1 ? valTable[pattern_len + 1] : valTable[pattern_len]) + special + : valTable[pattern_len]); + if ( score > max_score ) + { + groups[k]->score = score; + groups[k]->beg = i - pattern_len; + groups[k]->end = i; + groups[k]->positions[0].col = i - pattern_len + 1; + groups[k]->positions[0].len = pattern_len; + groups[k]->end_index = 1; + } + } + } + + return groups[k]; +} + + +HighlightGroup* evaluateHighlights(TextContext* pText_ctxt, + PatternContext* pPattern_ctxt, + uint16_t k, + HighlightGroup* groups[]) +{ + uint16_t j = pText_ctxt->offset; + + if ( groups[k] && groups[k]->beg >= j ) + return groups[k]; + + uint64_t* text_mask = pText_ctxt->text_mask; + uint16_t col_num = pText_ctxt->col_num; + + const char* pattern = pPattern_ctxt->pattern; + uint16_t base_offset = pattern[k] * col_num; + uint64_t x = text_mask[base_offset + (j >> 6)] >> (j & 63); + uint16_t i = 0; + + if ( x == 0 ) + { + uint64_t bits = 0; + uint16_t col = 0; + for ( col = (j >> 6) + 1; col < col_num; ++col ) + { + if ( (bits = text_mask[base_offset + col]) != 0 ) + break; + } + if ( bits == 0 ) + { + return NULL; + } + else + { + i = (col << 6) + FM_CTZ(bits); + } + } + else + { + i = j + FM_CTZ(x); + } + + uint16_t max_prefix_score = 0; + float max_score = MIN_WEIGHT; + + if ( !groups[k] ) + { + groups[k] = (HighlightGroup*)calloc(1, sizeof(HighlightGroup)); + if ( !groups[k] ) + { + fprintf(stderr, "Out of memory in evaluateHighlights()!\n"); + return NULL; + } + } + else + { + memset(groups[k], 0, sizeof(HighlightGroup)); + } + + HighlightGroup cur_highlights; + memset(&cur_highlights, 0, sizeof(HighlightGroup)); + + const char* text = pText_ctxt->text; + uint16_t text_len = pText_ctxt->text_len; + uint16_t pattern_len = pPattern_ctxt->pattern_len - k; + int64_t* pattern_mask = pPattern_ctxt->pattern_mask; + + uint16_t special = 0; + if ( i == 0 ) + special = 5; +#if defined(_MSC_VER) + else if ( text[i-1] == '\\' || text[i-1] == '/' ) +#else + else if ( text[i-1] == '/' ) +#endif + special = k == 0 ? 5 : 3; + else if ( isupper(text[i]) ) + special = !isupper(text[i-1]) || (i+1 < text_len && islower(text[i+1])) ? 3 : 0; + /* else if ( text[i-1] == '_' || text[i-1] == '-' || text[i-1] == ' ' ) */ + /* special = 3; */ + /* else if ( text[i-1] == '.' ) */ + /* special = 3; */ + else if ( !isalnum(text[i-1]) ) + special = 3; + else + special = 0; + ++i; + int64_t d = -2; /* ~1 */ + int64_t last = d; + while ( i < text_len ) + { + last = d; + char c = text[i]; + /* c in pattern */ + if ( pattern_mask[(uint8_t)c] != -1 ) + d = (d << 1) | (pattern_mask[(uint8_t)c] >> k); + /** + * text = 'xxABC', pattern = 'abc'; text[i] == 'B' + * text = 'xxABC', pattern = 'abc'; text[i] == 'C' + * NOT text = 'xxABCd', pattern = 'abc'; text[i] == 'C' + * 'Cd' is considered as a word + */ + /* else if ( isupper(text[i-1]) && pattern_mask[(uint8_t)tolower(c)] != -1 */ + /* && (i+1 == text_len || !islower(text[i+1])) ) */ + else if ( pattern_mask[(uint8_t)tolower(c)] != -1 ) + d = (d << 1) | (pattern_mask[(uint8_t)tolower(c)] >> k); + else + d = ~0; + + if ( d >= last ) + { + float score = MIN_WEIGHT; + uint16_t n = FM_BIT_LENGTH(~last); + /* e.g., text = '~~abcd~~~~', pattern = 'abcd' */ + if ( n == pattern_len ) + { + score = (float)(special > 0 ? (n > 1 ? valTable[n+1] : valTable[n]) + special : valTable[n]); + cur_highlights.score = score; + cur_highlights.beg = i - n; + cur_highlights.end = i; + cur_highlights.end_index = 1; + cur_highlights.positions[0].col = i - n + 1; + cur_highlights.positions[0].len = n; + if ( (k == 0 && special == 5) || (k > 0 && special > 0) ) + { + memcpy(groups[k], &cur_highlights, sizeof(HighlightGroup)); + return groups[k]; + } + } + else + { + uint16_t prefix_score = special > 0 ? (n > 1 ? valTable[n+1] : valTable[n]) + special : valTable[n]; + /** + * e.g., text = 'AbcxxAbcyyde', pattern = 'abcde' + * prefer matching 'Abcyyde' + */ + if ( prefix_score > max_prefix_score + || (special > 0 && prefix_score == max_prefix_score) ) + { + max_prefix_score = prefix_score; + pText_ctxt->offset = i; + HighlightGroup* pGroup = evaluateHighlights(pText_ctxt, pPattern_ctxt, k + n, groups); + if ( pGroup && pGroup->end ) + { + score = prefix_score + pGroup->score - 0.3f * (pGroup->beg - i); + cur_highlights.score = score; + cur_highlights.beg = i - n; + cur_highlights.end = pGroup->end; + cur_highlights.positions[0].col = i - n + 1; + cur_highlights.positions[0].len = n; + memcpy(cur_highlights.positions + 1, pGroup->positions, pGroup->end_index * sizeof(HighlightPos)); + cur_highlights.end_index = pGroup->end_index + 1; + } + else + { + break; + } + } + } + if ( score > max_score ) + { + max_score = score; + memcpy(groups[k], &cur_highlights, sizeof(HighlightGroup)); + } + /* e.g., text = '~_ababc~~~~', pattern = 'abc' */ + special = 0; + } + + /* + * e.g., text = 'a~c~~~~ab~c', pattern = 'abc', + * to find the index of the second 'a' + * `d == last` is for the case when text = 'kpi_oos1', pattern = 'kos' + */ + if ( d == ~0 || d == last ) + { + x = text_mask[base_offset + (i >> 6)] >> (i & 63); + + if ( x == 0 ) + { + uint64_t bits = 0; + uint16_t col = 0; + for ( col = (i >> 6) + 1; col < col_num; ++col ) + { + if ( (bits = text_mask[base_offset + col]) != 0 ) + break; + } + if ( bits == 0 ) + break; + else + i = (col << 6) + FM_CTZ(bits); + } + else + { + i += FM_CTZ(x); + } + +#if defined(_MSC_VER) + if ( text[i-1] == '\\' || text[i-1] == '/' ) +#else + if ( text[i-1] == '/' ) +#endif + special = k == 0 ? 5 : 3; + else if ( isupper(text[i]) ) + special = !isupper(text[i-1]) || (i+1 < text_len && islower(text[i+1])) ? 3 : 0; + /* else if ( text[i-1] == '_' || text[i-1] == '-' || text[i-1] == ' ' ) */ + /* special = 3; */ + /* else if ( text[i-1] == '.' ) */ + /* special = 3; */ + else if ( !isalnum(text[i-1]) ) + special = 3; + else + special = 0; + d = -2; + ++i; + } + else + ++i; + } + + /* e.g., text = '~~~~abcd', pattern = 'abcd' */ + if ( i == text_len ) + { + if ( ~d >> (pattern_len - 1) ) + { + float score = (float)(special > 0 ? (pattern_len > 1 ? valTable[pattern_len + 1] : valTable[pattern_len]) + special + : valTable[pattern_len]); + if ( score > max_score ) + { + groups[k]->score = score; + groups[k]->beg = i - pattern_len; + groups[k]->end = i; + groups[k]->positions[0].col = i - pattern_len + 1; + groups[k]->positions[0].len = pattern_len; + groups[k]->end_index = 1; + } + } + } + + return groups[k]; +} + +/** + * return a list of pair [col, length], where `col` is the column number(start + * from 1, the value must correspond to the byte index of `text`) and `length` + * is the length of the highlight in bytes. + * e.g., [ [2,3], [6,2], [10,4], ... ] + */ +HighlightGroup* getHighlights(const char* text, + uint16_t text_len, + PatternContext* pPattern_ctxt, + uint8_t is_name_only) +{ + if ( !text || !pPattern_ctxt ) + return NULL; + + uint16_t col_num = 0; + uint64_t* text_mask = NULL; + const char* pattern = pPattern_ctxt->pattern; + uint16_t pattern_len = pPattern_ctxt->pattern_len; + int64_t* pattern_mask = pPattern_ctxt->pattern_mask; + char first_char = pattern[0]; + char last_char = pattern[pattern_len - 1]; + + /* maximum number of int16_t is (1 << 15) - 1 */ + if ( text_len >= (1 << 15) ) + { + text_len = (1 << 15) - 1; + } + + if ( pattern_len == 1 ) + { + if ( isupper(first_char) ) + { + int16_t first_char_pos = -1; + int16_t i; + for ( i = 0; i < text_len; ++i ) + { + if ( text[i] == first_char ) + { + first_char_pos = i; + break; + } + } + if ( first_char_pos == -1 ) + return NULL; + else + { + HighlightGroup* pGroup = (HighlightGroup*)malloc(sizeof(HighlightGroup)); + if ( !pGroup ) + { + fprintf(stderr, "Out of memory in getHighlights()!\n"); + return NULL; + } + pGroup->positions[0].col = first_char_pos + 1; + pGroup->positions[0].len = 1; + pGroup->end_index = 1; + + return pGroup; + } + } + else + { + int16_t first_char_pos = -1; + int16_t i; + for ( i = 0; i < text_len; ++i ) + { + if ( tolower(text[i]) == first_char ) + { + if ( first_char_pos == -1 ) + first_char_pos = i; + + if ( isupper(text[i]) || i == 0 || !isalnum(text[i-1]) ) + { + first_char_pos = i; + break; + } + } + } + if ( first_char_pos == -1 ) + return NULL; + else + { + HighlightGroup* pGroup = (HighlightGroup*)malloc(sizeof(HighlightGroup)); + if ( !pGroup ) + { + fprintf(stderr, "Out of memory in getHighlights()!\n"); + return NULL; + } + pGroup->positions[0].col = first_char_pos + 1; + pGroup->positions[0].len = 1; + pGroup->end_index = 1; + + return pGroup; + } + } + } + + int16_t first_char_pos = -1; + uint16_t short_text_len = text_len; + if ( pPattern_ctxt->is_lower ) + { + int16_t i; + for ( i = 0; i < text_len; ++i ) + { + if ( tolower(text[i]) == first_char ) + { + first_char_pos = i; + break; + } + } + + int16_t last_char_pos = -1; + for ( i = text_len - 1; i >= first_char_pos; --i ) + { + if ( tolower(text[i]) == last_char ) + { + last_char_pos = i; + break; + } + } + + short_text_len = last_char_pos + 1; + col_num = (short_text_len + 63) >> 6; /* (short_text_len + 63)/64 */ + if (col_num <= 2) + { + memset(TEXT_MASK, 0, sizeof(TEXT_MASK)); + text_mask = TEXT_MASK; + } + else + { + /* uint64_t text_mask[256][col_num] */ + text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); + if ( !text_mask ) + { + fprintf(stderr, "Out of memory in getHighlights()!\n"); + return NULL; + } + } + char c; + for ( i = first_char_pos; i <= last_char_pos; ++i ) + { + c = tolower(text[i]); + /* c in pattern */ + if ( pattern_mask[(uint8_t)c] != -1 ) + text_mask[(uint8_t)c * col_num + (i >> 6)] |= 1ULL << (i & 63); + } + } + else + { + if ( isupper(first_char) ) + { + int16_t i; + for ( i = 0; i < text_len; ++i ) + { + if ( text[i] == first_char ) + { + first_char_pos = i; + break; + } + } + } + else + { + int16_t i; + for ( i = 0; i < text_len; ++i ) + { + if ( tolower(text[i]) == first_char ) + { + first_char_pos = i; + break; + } + } + } + + int16_t last_char_pos = -1; + if ( isupper(last_char) ) + { + int16_t i; + for ( i = text_len - 1; i >= first_char_pos; --i ) + { + if ( text[i] == last_char ) + { + last_char_pos = i; + break; + } + } + } + else + { + int16_t i; + for ( i = text_len - 1; i >= first_char_pos; --i ) + { + if ( tolower(text[i]) == last_char ) + { + last_char_pos = i; + break; + } + } + } + + short_text_len = last_char_pos + 1; + col_num = (short_text_len + 63) >> 6; + if (col_num <= 2) + { + memset(TEXT_MASK, 0, sizeof(TEXT_MASK)); + text_mask = TEXT_MASK; + } + else + { + /* uint64_t text_mask[256][col_num] */ + text_mask = (uint64_t*)calloc(col_num << 8, sizeof(uint64_t)); + if ( !text_mask ) + { + fprintf(stderr, "Out of memory in getHighlights()!\n"); + return NULL; + } + } + + char c; + int16_t i; + for ( i = first_char_pos; i <= last_char_pos; ++i ) + { + c = text[i]; + if ( isupper(c) ) + { + /* c in pattern */ + if ( pattern_mask[(uint8_t)c] != -1 ) + text_mask[(uint8_t)c * col_num + (i >> 6)] |= 1ULL << (i & 63); + if ( pattern_mask[(uint8_t)tolower(c)] != -1 ) + text_mask[(uint8_t)tolower(c) * col_num + (i >> 6)] |= 1ULL << (i & 63); + } + else + { + /* c in pattern */ + if ( pattern_mask[(uint8_t)c] != -1 ) + text_mask[(uint8_t)c * col_num + (i >> 6)] |= 1ULL << (i & 63); + } + } + } + + TextContext text_ctxt; + text_ctxt.text = text; + text_ctxt.text_len = short_text_len; + text_ctxt.text_mask = text_mask; + text_ctxt.col_num = col_num; + text_ctxt.offset = first_char_pos; + + /* HighlightGroup* groups[pattern_len] */ + HighlightGroup** groups = (HighlightGroup**)calloc(pattern_len, sizeof(HighlightGroup*)); + if ( !groups ) + { + fprintf(stderr, "Out of memory in getHighlights()!\n"); + if (col_num > 2) + { + free(text_mask); + } + return NULL; + } + + HighlightGroup* pGroup = NULL; + if ( is_name_only ) + pGroup = evaluateHighlights_nameOnly(&text_ctxt, pPattern_ctxt, 0, groups); + else + pGroup = evaluateHighlights(&text_ctxt, pPattern_ctxt, 0, groups); + + if (col_num > 2) + { + free(text_mask); + } + uint16_t i; + for ( i = 0; i < pattern_len; ++i ) + { + if ( groups[i] && groups[i] != pGroup ) + free(groups[i]); + } + free(groups); + + return pGroup; +} + +/** + * e.g., /usr/src/example.tar.gz + * `dirname` is "/usr/src" + * `basename` is "example.tar.gz" + * `filename` is "example.tar", `suffix` is ".gz" + */ +uint32_t getPathWeight(const char* filename, + const char* suffix, + const char* dirname, + const char* path, uint32_t path_len) +{ + uint32_t filename_lcp = 0; + uint32_t filename_prefix = 0; + uint32_t dirname_lcp = 0; + uint32_t is_suffix_diff = 0; + uint32_t is_basename_same = 0; + uint32_t is_dirname_same = 0; + + const char* filename_start = path; + const char* p = path + path_len; + const char* p1 = NULL; + + while ( p >= path ) + { +#if defined(_MSC_VER) + if ( *p == '\\' || *p == '/' ) +#else + if ( *p == '/' ) +#endif + { + filename_start = p + 1; + break; + } + --p; + } + + if ( *suffix != '\0' ) + { + p = filename_start; + p1 = filename; + while ( *p != '\0' && *p == *p1 ) + { + ++filename_lcp; + ++p; + ++p1; + } + + filename_prefix = filename_lcp; + + if ( filename_lcp > 0 ) + { + if ( (*p >= 'a' && *p <= 'z') || (*p >= '0' && *p <= '9') + || (*p1 >= 'a' && *p1 <= 'z') || (*p1 >= '0' && *p1 <= '9') ) + { + --p; + while ( p > filename_start ) + { + if ( *p >= 'a' && *p <= 'z' ) + { + --p; + } + else + { + break; + } + } + filename_prefix = (uint32_t)(p - filename_start); + } + else if ( (*p >= 'A' && *p <= 'Z') && (*p1 >= 'A' && *p1 <= 'Z') + && (*(p-1) >= 'A' && *(p-1) <= 'Z') ) + { + --p; + while ( p > filename_start ) + { + if ( *p >= 'A' && *p <= 'Z' ) + { + --p; + } + else + { + break; + } + } + filename_prefix = (uint32_t)(p - filename_start); + } + } + + p = path + path_len - 1; + while ( p > filename_start ) + { + if ( *p == '.' ) + { + if ( strcmp(suffix, p) != 0 ) + { + if ( filename_lcp > 0 ) + is_suffix_diff = 1; + } + else if ( *p1 == '\0' && filename_lcp == p - filename_start ) + { + is_basename_same = 1; + } + break; + } + --p; + } + } + else + { + is_basename_same = strcmp(filename, filename_start) == 0; + } + + p = path; + p1 = dirname; +#if defined(_MSC_VER) + while ( p < filename_start ) + { + if ( *p1 == '\\' ) + { + if ( *p == '\\' || *p == '/' ) + { + ++dirname_lcp; + } + else + { + break; + } + } + else if ( *p != *p1 ) + { + break; + } + ++p; + ++p1; + } +#else + while ( p < filename_start && *p == *p1 ) + { + if ( *p == '/' ) + { + ++dirname_lcp; + } + ++p; + ++p1; + } +#endif + /** + * e.g., dirname = "abc" , path = "abc/test.h" + * p1 != dirname is to avoid such a case: + * e.g., buffer name is "aaa.h", path is "/abc/def.h" + */ +#if defined(_MSC_VER) + if ( *p1 == '\0' && p1 != dirname && (*p == '\\' || *p == '/') ) +#else + if ( *p1 == '\0' && p1 != dirname && *p == '/' ) +#endif + { + ++dirname_lcp; + } + + /* if dirname is empty, filename_start == path */ + is_dirname_same = filename_start - p == 1 || (*dirname == '\0' && filename_start == path) ; + + /* dirname/filename+suffix is the same as path */ + if ( is_basename_same && is_dirname_same ) + { + return 0; + } + + if ( filename_start == path && *dirname == '\0') + { + dirname_lcp = 1; + } + + return (((filename_prefix + 1) << 24) | (dirname_lcp << 12) | (is_dirname_same << 11) + | filename_lcp) + (is_suffix_diff << 2) - path_len; +} + +static void delPatternContext(PyObject* obj) +{ + free(PyCapsule_GetPointer(obj, NULL)); +} + +static PyObject* fuzzyMatchC_initPattern(PyObject* self, PyObject* args) +{ + const char* pattern; + Py_ssize_t pattern_len; + + if ( !PyArg_ParseTuple(args, "s#:initPattern", &pattern, &pattern_len) ) + return NULL; + + PatternContext* pCtxt = initPattern(pattern, (uint16_t)pattern_len); + + return PyCapsule_New(pCtxt, NULL, delPatternContext); +} + +static PyObject* fuzzyMatchC_getWeight(PyObject* self, PyObject* args, PyObject* kwargs) +{ + const char* text; + Py_ssize_t text_len; + PyObject* py_patternCtxt; + uint8_t is_name_only; + static char* kwlist[] = {"text", "pattern", "is_name_only", NULL}; + + if ( !PyArg_ParseTupleAndKeywords(args, kwargs, "s#Ob:getWeight", kwlist, &text, + &text_len, &py_patternCtxt, &is_name_only) ) + return NULL; + + PatternContext* pCtxt = (PatternContext*)PyCapsule_GetPointer(py_patternCtxt, NULL); + if ( !pCtxt ) + return NULL; + + return Py_BuildValue("f", getWeight(text, (uint16_t)text_len, pCtxt, is_name_only)); +} + +static PyObject* fuzzyMatchC_getHighlights(PyObject* self, PyObject* args, PyObject* kwargs) +{ + const char* text; + Py_ssize_t text_len; + PyObject* py_patternCtxt; + uint8_t is_name_only; + static char* kwlist[] = {"text", "pattern", "is_name_only", NULL}; + + if ( !PyArg_ParseTupleAndKeywords(args, kwargs, "s#Ob:getHighlights", kwlist, &text, + &text_len, &py_patternCtxt, &is_name_only) ) + return NULL; + + PatternContext* pCtxt = (PatternContext*)PyCapsule_GetPointer(py_patternCtxt, NULL); + if ( !pCtxt ) + return NULL; + + HighlightGroup* pGroup = getHighlights(text, (uint16_t)text_len, pCtxt, is_name_only); + if ( !pGroup ) + return NULL; + + PyObject* list = PyList_New(pGroup->end_index); + uint16_t i; + for ( i = 0; i < pGroup->end_index; ++i ) + { + PyList_SetItem(list, i, Py_BuildValue("[H,H]", pGroup->positions[i].col, pGroup->positions[i].len)); + } + free(pGroup); + + return list; +} + +static PyMethodDef fuzzyMatchC_Methods[] = +{ + { "initPattern", (PyCFunction)fuzzyMatchC_initPattern, METH_VARARGS, "initialize the pattern." }, + { "getWeight", (PyCFunction)fuzzyMatchC_getWeight, METH_VARARGS | METH_KEYWORDS, "" }, + { "getHighlights", (PyCFunction)fuzzyMatchC_getHighlights, METH_VARARGS | METH_KEYWORDS, "" }, + { NULL, NULL, 0, NULL } +}; + +#if PY_MAJOR_VERSION >= 3 + +static struct PyModuleDef fuzzyMatchC_module = +{ + PyModuleDef_HEAD_INIT, + "fuzzyMatchC", /* name of module */ + "fuzzy match algorithm written in C.", + -1, + fuzzyMatchC_Methods +}; + +PyMODINIT_FUNC PyInit_fuzzyMatchC(void) +{ + PyObject* module = NULL; + module = PyModule_Create(&fuzzyMatchC_module); + if ( !module ) + return NULL; + + if ( PyModule_AddObject(module, "MIN_WEIGHT", Py_BuildValue("f", (float)MIN_WEIGHT)) ) + { + Py_DECREF(module); + return NULL; + } + + return module; +} + +#else + +PyMODINIT_FUNC initfuzzyMatchC(void) +{ + PyObject* module = NULL; + module = Py_InitModule("fuzzyMatchC", fuzzyMatchC_Methods); + if ( !module ) + return; + + if ( PyModule_AddObject(module, "MIN_WEIGHT", Py_BuildValue("f", (float)MIN_WEIGHT)) ) + { + Py_DECREF(module); + return; + } +} + +#endif + +int main(int argc, const char* argv[]) +{ + + printf("%d\n", FM_BIT_LENGTH(0x2f00)); + + return 0; +} + diff --git a/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.h b/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.h index 575b2984..daddb33f 100644 --- a/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.h +++ b/autoload/leaderf/fuzzyMatch_C/fuzzyMatch.h @@ -19,7 +19,7 @@ #include "mystdint.h" -#define MIN_WEIGHT (-10000.0f) +#define MIN_WEIGHT (-1000000.0f) typedef struct PatternContext { diff --git a/autoload/leaderf/fuzzyMatch_C/setup.py b/autoload/leaderf/fuzzyMatch_C/setup.py index bbd07fd1..fd2cea8d 100644 --- a/autoload/leaderf/fuzzyMatch_C/setup.py +++ b/autoload/leaderf/fuzzyMatch_C/setup.py @@ -1,41 +1,62 @@ # -*- coding: utf-8 -*- import os +import platform +import subprocess +import sys try: - from setuptools import setup - from setuptools import Extension + from setuptools import setup, Extension + from setuptools.command.build_ext import build_ext except ImportError: - from distutils.core import setup - from distutils.extension import Extension + print("\nsetuptools is not installed. Attempting to install...") + print(" ".join([sys.executable, "-m", "pip", "install", "setuptools"])) + try: + subprocess.check_call([sys.executable, "-m", "pip", "install", "setuptools"]) + from setuptools import setup, Extension + from setuptools.command.build_ext import build_ext + except Exception as e: + print("\nFailed to install setuptools: {}".format(e)) + print("Please turn to https://stackoverflow.com/questions/69919970/no-module-named-distutils-util-but-distutils-is-installed/76691103#76691103 for help") + sys.exit(1) + +class BuildExt(build_ext): + def build_extensions(self): + if os.name == 'nt' and "MSC" in platform.python_compiler(): + pass + elif os.name == 'posix': + for ext in self.extensions: + ext.extra_compile_args = ['-std=c99'] + build_ext.build_extensions(self) if os.name == 'nt': - from distutils.msvccompiler import get_build_version - - if get_build_version() < 14.0: # Visual Studio 2015 - if get_build_version() >= 8.0: - from distutils.msvc9compiler import MSVCCompiler - else: - from distutils.msvccompiler import MSVCCompiler - - # Because the msvc compiler does not support c99, - # treat .c files as .cpp files - MSVCCompiler._c_extensions = [] - MSVCCompiler._cpp_extensions = ['.c', '.cc', '.cpp', '.cxx'] - - -module1 = Extension("fuzzyMatchC", - sources = ["fuzzyMatch.c"]) - -module2 = Extension("fuzzyEngine", - sources = ["fuzzyMatch.c", "fuzzyEngine.c"]) - - -setup(name = "fuzzyEngine", - version = "2.0", - description = "fuzzy match algorithm written in C.", - author = "Yggdroot", - author_email = "archofortune@gmail.com", - url = "https://github.com/Yggdroot/LeaderF", - license = "Apache License 2.0", - ext_modules = [module1, module2] - ) + module1 = Extension( + "fuzzyMatchC", + sources=["fuzzyMatch.cpp"], + ) + + module2 = Extension( + "fuzzyEngine", + sources=["fuzzyMatch.cpp", "fuzzyEngine.cpp"], + ) +else: + module1 = Extension( + "fuzzyMatchC", + sources=["fuzzyMatch.c"], + ) + + module2 = Extension( + "fuzzyEngine", + sources=["fuzzyMatch.c", "fuzzyEngine.c"], + ) + +setup( + name="fuzzyEngine", + version="2.0", + description="Fuzzy match algorithm written in C.", + author="Yggdroot", + author_email="archofortune@gmail.com", + url="https://github.com/Yggdroot/LeaderF", + license="Apache License 2.0", + ext_modules=[module1, module2], + cmdclass={"build_ext": BuildExt}, +) diff --git a/autoload/leaderf/python/leaderf/anyExpl.py b/autoload/leaderf/python/leaderf/anyExpl.py index 259189f4..f0c2e31b 100644 --- a/autoload/leaderf/python/leaderf/anyExpl.py +++ b/autoload/leaderf/python/leaderf/anyExpl.py @@ -16,7 +16,7 @@ from .asyncExecutor import AsyncExecutor -""" +r""" let g:Lf_Extensions = { \ "apple": { \ "source": [], "grep -r '%s' *", funcref (arguments), {"command": "ls" or funcref(arguments)} @@ -28,13 +28,13 @@ \ "format_list": funcref ([], arguments), \ "need_exit": funcref (line, arguments), \ "accept": funcref (line, arguments), - \ "preview": funcref (orig_buf_nr, orig_cursor, arguments), + \ "preview": funcref (orig_buf_num, orig_cursor, arguments), \ "supports_name_only": 0, \ "get_digest": funcref (line, mode), \ "before_enter": funcref (arguments), - \ "after_enter": funcref (orig_buf_nr, orig_cursor, arguments), - \ "bang_enter": funcref (orig_buf_nr, orig_cursor, arguments), - \ "before_exit": funcref (orig_buf_nr, orig_cursor, arguments), + \ "after_enter": funcref (orig_buf_num, orig_cursor, arguments), + \ "bang_enter": funcref (orig_buf_num, orig_cursor, arguments), + \ "before_exit": funcref (orig_buf_num, orig_cursor, arguments), \ "after_exit": funcref (arguments), \ "highlights_def": { \ "Lf_hl_apple": '^\s*\zs\d\+', @@ -72,7 +72,7 @@ def setConfig(self, category, config): def getContent(self, *args, **kwargs): source = self._config.get("source") if not source: - return None + return [] if isinstance(source, list): result = source @@ -275,15 +275,15 @@ def _afterEnter(self): super(AnyExplManager, self)._afterEnter() after_enter = self._config.get("after_enter") if after_enter: - orig_buf_nr = self._getInstance().getOriginalPos()[2].number + orig_buf_num = self._getInstance().getOriginalPos()[2].number line, col = self._getInstance().getOriginalCursor() try: if self._getInstance().getWinPos() == 'popup': lfCmd("""call win_execute(%d, "call %s(%d, [%d, %d], %s)")""" - % (self._getInstance().getPopupWinId(), after_enter, orig_buf_nr, line, col+1, str(self._arguments))) + % (self._getInstance().getPopupWinId(), after_enter, orig_buf_num, line, col+1, str(self._arguments))) else: after_enter = lfFunction(after_enter) - after_enter(orig_buf_nr, [line, col+1], self._arguments) + after_enter(orig_buf_num, [line, col+1], self._arguments) except vim.error as err: raise Exception("Error occurred in user defined %s: %s" % (str(after_enter), err)) @@ -321,15 +321,15 @@ def _bangEnter(self): super(AnyExplManager, self)._bangEnter() bang_enter = self._config.get("bang_enter") if bang_enter: - orig_buf_nr = self._getInstance().getOriginalPos()[2].number + orig_buf_num = self._getInstance().getOriginalPos()[2].number line, col = self._getInstance().getOriginalCursor() try: if self._getInstance().getWinPos() == 'popup': lfCmd("""call win_execute(%d, "call %s(%d, [%d, %d], %s)")""" - % (self._getInstance().getPopupWinId(), bang_enter, orig_buf_nr, line, col+1, str(self._arguments))) + % (self._getInstance().getPopupWinId(), bang_enter, orig_buf_num, line, col+1, str(self._arguments))) else: bang_enter = lfFunction(bang_enter) - bang_enter(orig_buf_nr, [line, col+1], self._arguments) + bang_enter(orig_buf_num, [line, col+1], self._arguments) except vim.error as err: raise Exception("Error occurred in user defined %s: %s" % (str(bang_enter), err)) @@ -337,15 +337,15 @@ def _beforeExit(self): super(AnyExplManager, self)._beforeExit() before_exit = self._config.get("before_exit") if before_exit: - orig_buf_nr = self._getInstance().getOriginalPos()[2].number + orig_buf_num = self._getInstance().getOriginalPos()[2].number line, col = self._getInstance().getOriginalCursor() try: if self._getInstance().getWinPos() == 'popup': lfCmd("""call win_execute(%d, "call %s(%d, [%d, %d], %s)")""" - % (self._getInstance().getPopupWinId(), before_exit, orig_buf_nr, line, col+1, str(self._arguments))) + % (self._getInstance().getPopupWinId(), before_exit, orig_buf_num, line, col+1, str(self._arguments))) else: before_exit = lfFunction(before_exit) - before_exit(orig_buf_nr, [line, col+1], self._arguments) + before_exit(orig_buf_num, [line, col+1], self._arguments) except vim.error as err: raise Exception("Error occurred in user defined %s: %s" % (str(before_exit), err)) @@ -367,24 +367,28 @@ def startExplorer(self, win_pos, *args, **kwargs): super(AnyExplManager, self).startExplorer(win_pos, *args, **kwargs) def _previewInPopup(self, *args, **kwargs): + if len(args) == 0 or args[0] == '': + return + line = args[0] preview = self._config.get("preview") if preview: - orig_buf_nr = self._getInstance().getOriginalPos()[2].number + orig_buf_num = self._getInstance().getOriginalPos()[2].number l, c = self._getInstance().getOriginalCursor() try: preview = lfFunction(preview) - result = preview(orig_buf_nr, [l, c+1], line, self._arguments) + result = preview(orig_buf_num, [l, c+1], line, self._arguments) if result: filename, line_num, jump_cmd = result # for backward compatibility if isinstance(filename, int): # it is a buffer number - pass - elif lfEval("bufloaded('%s')" % escQuote(filename)) == '1': + lfCmd("silent call bufload(%d)" % filename) + else: if not self._has_nvim: # py3 in nvim return str, in vim return bytes filename = lfBytes2Str(filename) - filename = int(lfEval("bufnr('%s')" % escQuote(filename))) # actually, it's a buffer number + if lfEval("bufloaded('%s')" % escQuote(filename)) == '1': + filename = int(lfEval("bufnr('%s')" % escQuote(filename))) # actually, it's a buffer number self._createPopupPreview("", filename, line_num, lfBytes2Str(jump_cmd) if not self._has_nvim else jump_cmd) except vim.error as err: raise Exception("Error occurred in user defined %s: %s" % (str(preview), err)) @@ -716,6 +720,12 @@ def _default_action(self, category, positional_args, arguments, *args, **kwargs) elif category == "jumps": from .jumpsExpl import jumpsExplManager manager = jumpsExplManager + elif category == "git": + from .gitExpl import gitExplManager + manager = gitExplManager + elif category == "coc": + from .cocExpl import cocExplManager + manager = cocExplManager else: import ctypes manager_id = lfFunction(lfEval("g:Lf_PythonExtensions['%s'].manager_id" % category))() @@ -739,11 +749,14 @@ def _default_action(self, category, positional_args, arguments, *args, **kwargs) arguments["win_pos"] = win_pos[2:] if "--cword" in arguments: - kwargs["pattern"] = lfEval("expand('')") + arguments["--input"]= [lfEval("expand('')")] kwargs["arguments"] = arguments kwargs["positional_args"] = positional_args + if lfEval("has('patch-8.1.1615') || has('nvim-0.5.0')") == '0': + win_pos = "--bottom" + manager.startExplorer(win_pos[2:], *args, **kwargs) def start(self, arg_line, *args, **kwargs): @@ -769,11 +782,25 @@ def start(self, arg_line, *args, **kwargs): parser = subparsers.add_parser(category, usage=gtags_usage, formatter_class=LfHelpFormatter, help=help, epilog="If [!] is given, enter normal mode directly.") else: parser = subparsers.add_parser(category, help=help, formatter_class=LfHelpFormatter, epilog="If [!] is given, enter normal mode directly.") - group = parser.add_argument_group('specific arguments') - self._add_argument(group, arg_def, positional_args) - group = parser.add_argument_group("common arguments") - self._add_argument(group, lfEval("g:Lf_CommonArguments"), positional_args) + if isinstance(arg_def, dict): + subsubparsers = parser.add_subparsers(title="subcommands", description="", help="") + group = parser.add_argument_group("common arguments") + self._add_argument(group, lfEval("g:Lf_CommonArguments"), positional_args) + for command, args in arg_def.items(): + help = lfEval("g:Lf_Helps").get(category + "-" + command, "") + subparser = subsubparsers.add_parser(command, help=help, formatter_class=LfHelpFormatter) + group = subparser.add_argument_group('specific arguments') + self._add_argument(group, args, positional_args) + + group = subparser.add_argument_group("common arguments") + self._add_argument(group, lfEval("g:Lf_CommonArguments"), positional_args) + else: + group = parser.add_argument_group('specific arguments') + self._add_argument(group, arg_def, positional_args) + + group = parser.add_argument_group("common arguments") + self._add_argument(group, lfEval("g:Lf_CommonArguments"), positional_args) parser.set_defaults(start=partial(self._default_action, category, positional_args)) diff --git a/autoload/leaderf/python/leaderf/asyncExecutor.py b/autoload/leaderf/python/leaderf/asyncExecutor.py index d3566949..037db53b 100644 --- a/autoload/leaderf/python/leaderf/asyncExecutor.py +++ b/autoload/leaderf/python/leaderf/asyncExecutor.py @@ -15,16 +15,18 @@ lfDEVNULL = open(os.devnull) +max_count = int(lfEval("g:Lf_MaxCount")) + class AsyncExecutor(object): """ A class to implement executing a command in subprocess, then read the output asynchronously. """ def __init__(self): - self._errQueue = Queue.Queue() + self._errQueue = None self._process = None self._finished = False - self._max_count = int(lfEval("g:Lf_MaxCount")) + self._max_count = max_count def _readerThread(self, fd, queue): try: @@ -35,13 +37,15 @@ def _readerThread(self, fd, queue): finally: queue.put(None) - def execute(self, cmd, encoding=None, cleanup=None, env=None, raise_except=True, format_line=None): + def execute(self, cmd, encoding=None, cleanup=None, env=None, + raise_except=True, format_line=None, cwd=None): if os.name == 'nt': self._process = subprocess.Popen(cmd, bufsize=-1, stdin=lfDEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, + cwd=cwd, env=env, universal_newlines=False) else: @@ -51,9 +55,11 @@ def execute(self, cmd, encoding=None, cleanup=None, env=None, raise_except=True, stderr=subprocess.PIPE, preexec_fn=os.setsid, shell=True, + cwd=cwd, env=env, universal_newlines=False) + self._errQueue = Queue.Queue() self._finished = False stderr_thread = threading.Thread(target=self._readerThread, @@ -102,7 +108,7 @@ def read(source): err = b"".join(iter(self._errQueue.get, None)) if err and raise_except: - raise Exception(lfBytes2Str(err, encoding)) + raise Exception(cmd + "\n" + lfBytes2Str(err) + lfBytes2Str(err, encoding)) except ValueError: pass finally: @@ -114,6 +120,8 @@ def read(source): self._process.poll() except IOError: pass + except AttributeError: + pass if cleanup: cleanup() @@ -150,7 +158,7 @@ def read(source): err = b"".join(iter(self._errQueue.get, None)) if err and raise_except: - raise Exception(err) + raise Exception(lfEncode(err) + err) except ValueError: pass finally: @@ -162,6 +170,8 @@ def read(source): self._process.poll() except IOError: pass + except AttributeError: + pass if cleanup: cleanup() @@ -182,6 +192,7 @@ def killProcess(self): except OSError: pass + self._process.poll() self._process = None class Result(object): @@ -201,8 +212,14 @@ def join_left(self, iterable): return self def __iter__(self): - return self._g + return self + + def __next__(self): + return next(self._g) + # for python2 + def next(self): + return next(self._g) if __name__ == "__main__": executor = AsyncExecutor() diff --git a/autoload/leaderf/python/leaderf/bufExpl.py b/autoload/leaderf/python/leaderf/bufExpl.py index 20d578c8..4dc8d196 100644 --- a/autoload/leaderf/python/leaderf/bufExpl.py +++ b/autoload/leaderf/python/leaderf/bufExpl.py @@ -52,6 +52,9 @@ def getContent(self, *args, **kwargs): buffers = {w.buffer.number: w.buffer for w in vim.current.tabpage.windows if os.path.basename(w.buffer.name) != "LeaderF"} + if lfEval("has('nvim')") == '0': + buffers = {number : buffer for number, buffer in buffers.items() + if buffer.options["buftype"] != b"terminal"} # e.g., 12 u %a+-  aaa.txt bufnr_len = len(lfEval("bufnr('$')")) @@ -220,37 +223,37 @@ def _afterEnter(self): winid = None if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufNumber'', ''^\s*\zs\d\+'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufNumber'', ''^\s*\zs\d\+'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufIndicators'', ''^\s*\d\+\s*\zsu\=\s*[#%%]\=...'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufIndicators'', ''^\s*\d\+\s*\zsu\=\s*[#%%]\=...'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufModified'', ''^\s*\d\+\s*u\=\s*[#%%]\=.+\s*\zs.*$'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufModified'', ''^\s*\d\+\s*u\=\s*[#%%]\=.+\s*\zs.*$'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufNomodifiable'', ''^\s*\d\+\s*u\=\s*[#%%]\=..-\s*\zs.*$'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufNomodifiable'', ''^\s*\d\+\s*u\=\s*[#%%]\=..-\s*\zs.*$'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufDirname'', '' \zs".*"$'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufDirname'', '' \zs".*"$'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) winid = self._getInstance().getPopupWinId() else: - id = int(lfEval("matchadd('Lf_hl_bufNumber', '^\s*\zs\d\+')")) + id = int(lfEval(r"matchadd('Lf_hl_bufNumber', '^\s*\zs\d\+')")) self._match_ids.append(id) - id = int(lfEval("matchadd('Lf_hl_bufIndicators', '^\s*\d\+\s*\zsu\=\s*[#%]\=...')")) + id = int(lfEval(r"matchadd('Lf_hl_bufIndicators', '^\s*\d\+\s*\zsu\=\s*[#%]\=...')")) self._match_ids.append(id) - id = int(lfEval("matchadd('Lf_hl_bufModified', '^\s*\d\+\s*u\=\s*[#%]\=.+\s*\zs.*$')")) + id = int(lfEval(r"matchadd('Lf_hl_bufModified', '^\s*\d\+\s*u\=\s*[#%]\=.+\s*\zs.*$')")) self._match_ids.append(id) - id = int(lfEval("matchadd('Lf_hl_bufNomodifiable', '^\s*\d\+\s*u\=\s*[#%]\=..-\s*\zs.*$')")) + id = int(lfEval(r"matchadd('Lf_hl_bufNomodifiable', '^\s*\d\+\s*u\=\s*[#%]\=..-\s*\zs.*$')")) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_bufDirname', ' \zs".*"$')''')) + id = int(lfEval(r'''matchadd('Lf_hl_bufDirname', ' \zs".*"$')''')) self._match_ids.append(id) # devicons @@ -289,9 +292,16 @@ def deleteBuffer(self, wipe=0): lfCmd("setlocal nomodifiable") def _previewInPopup(self, *args, **kwargs): + if len(args) == 0 or args[0] == '': + return + line = args[0] buf_number = int(re.sub(r"^.*?(\d+).*$", r"\1", line)) - self._createPopupPreview(vim.buffers[buf_number].name, buf_number, 0) + if lfEval("bufloaded(%d)" % buf_number) == '0': + lfCmd("silent call bufload(%d)" % buf_number) + + jump_cmd = 'silent! normal! g`"' + self._createPopupPreview(vim.buffers[buf_number].name, buf_number, 0, jump_cmd) #***************************************************** diff --git a/autoload/leaderf/python/leaderf/bufTagExpl.py b/autoload/leaderf/python/leaderf/bufTagExpl.py index 501e53a4..c36d9543 100644 --- a/autoload/leaderf/python/leaderf/bufTagExpl.py +++ b/autoload/leaderf/python/leaderf/bufTagExpl.py @@ -78,7 +78,8 @@ def _getTagList(self): yield itertools.chain(tag_list, itertools.chain.from_iterable(exe_taglist)) def _getTagResult(self, buffer): - if not buffer.name or lfEval("bufloaded(%d)" % buffer.number) == '0': + if (not buffer.name or lfEval("bufloaded(%d)" % buffer.number) == '0' + or lfEval("&bt") != ''): return [] changedtick = int(lfEval("getbufvar(%d, 'changedtick')" % buffer.number)) # there is no change since last call @@ -108,7 +109,7 @@ def _getTagResult(self, buffer): tmp_file = tempfile.NamedTemporaryFile with tmp_file(mode='w+', suffix='_'+os.path.basename(buffer.name), delete=False) as f: - for line in buffer[:]: + for line in buffer: f.write(line + '\n') file_name = f.name # {tagname}{tagfile}{tagaddress}[;"{tagfield}..] @@ -211,7 +212,6 @@ class BufTagExplManager(Manager): def __init__(self): super(BufTagExplManager, self).__init__() self._supports_preview = int(lfEval("g:Lf_PreviewCode")) - self._orig_line = '' def _getExplClass(self): return BufTagExplorer @@ -225,24 +225,24 @@ def _acceptSelection(self, *args, **kwargs): line = args[0] if line[0].isspace(): # if g:Lf_PreviewCode == 1 buffer = args[1] - line_nr = args[2] + line_num = args[2] if self._getInstance().isReverseOrder(): - line = buffer[line_nr] + line = buffer[line_num] else: - line = buffer[line_nr - 2] + line = buffer[line_num - 2] # {tag} {kind} {scope} {file}:{line} {buf_number} items = re.split(" *\t *", line) tagname = items[0] - line_nr = items[3].rsplit(":", 1)[1] + line_num = items[3].rsplit(":", 1)[1] buf_number = items[4] if kwargs.get("mode", '') == 't': buf_name = lfEval("bufname(%s)" % buf_number) - lfDrop('tab', buf_name, line_nr) + lfDrop('tab', buf_name, line_num) else: - lfCmd("hide buffer +%s %s" % (line_nr, buf_number)) + lfCmd("hide buffer +%s %s" % (line_num, buf_number)) if "preview" not in kwargs: lfCmd("norm! ^") - lfCmd("call search('\V%s', 'Wc', line('.'))" % escQuote(tagname)) + lfCmd(r"call search('\V%s', 'Wc', line('.'))" % escQuote(tagname)) lfCmd("norm! zv") lfCmd("norm! zz") @@ -305,42 +305,42 @@ def _afterEnter(self): lfCmd("autocmd VimLeavePre * call leaderf#BufTag#cleanup()") lfCmd("augroup END") if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagKind'', ''^[^\t]*\t\zs\S\+'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagKind'', ''^[^\t]*\t\zs\S\+'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagScopeType'', ''[^\t]*\t\S\+\s*\zs\w\+:'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagScopeType'', ''[^\t]*\t\S\+\s*\zs\w\+:'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagScope'', ''^[^\t]*\t\S\+\s*\(\w\+:\)\=\zs\S\+'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagScope'', ''^[^\t]*\t\S\+\s*\(\w\+:\)\=\zs\S\+'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagDirname'', ''[^\t]*\t\S\+\s*\S\+\s*\zs[^\t]\+'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagDirname'', ''[^\t]*\t\S\+\s*\S\+\s*\zs[^\t]\+'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagLineNum'', ''\d\+\t\ze\d\+$'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagLineNum'', ''\d\+\t\ze\d\+$'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagCode'', ''^\s\+.*'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_buftagCode'', ''^\s\+.*'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) else: - id = int(lfEval('''matchadd('Lf_hl_buftagKind', '^[^\t]*\t\zs\S\+')''')) + id = int(lfEval(r'''matchadd('Lf_hl_buftagKind', '^[^\t]*\t\zs\S\+')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_buftagScopeType', '[^\t]*\t\S\+\s*\zs\w\+:')''')) + id = int(lfEval(r'''matchadd('Lf_hl_buftagScopeType', '[^\t]*\t\S\+\s*\zs\w\+:')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_buftagScope', '^[^\t]*\t\S\+\s*\(\w\+:\)\=\zs\S\+')''')) + id = int(lfEval(r'''matchadd('Lf_hl_buftagScope', '^[^\t]*\t\S\+\s*\(\w\+:\)\=\zs\S\+')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_buftagDirname', '[^\t]*\t\S\+\s*\S\+\s*\zs[^\t]\+')''')) + id = int(lfEval(r'''matchadd('Lf_hl_buftagDirname', '[^\t]*\t\S\+\s*\S\+\s*\zs[^\t]\+')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_buftagLineNum', '\d\+\t\ze\d\+$')''')) + id = int(lfEval(r'''matchadd('Lf_hl_buftagLineNum', '\d\+\t\ze\d\+$')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_buftagCode', '^\s\+.*')''')) + id = int(lfEval(r'''matchadd('Lf_hl_buftagCode', '^\s\+.*')''')) self._match_ids.append(id) def _beforeExit(self): @@ -470,38 +470,6 @@ def _toDown(self): def removeCache(self, buf_number): self._getExplorer().removeCache(buf_number) - def _previewResult(self, preview): - if self._getInstance().getWinPos() == 'floatwin': - self._cli.buildPopupPrompt() - - if lfEval("get(g:, 'Lf_PreviewInPopup', 0)") == '1': - if self._orig_line != self._getInstance().currentLine: - self._closePreviewPopup() - else: - return - - if not self._needPreview(preview): - return - - line = self._getInstance().currentLine - line_nr = self._getInstance().window.cursor[0] - - if lfEval("get(g:, 'Lf_PreviewInPopup', 0)") == '1': - self._previewInPopup(line, self._getInstance().buffer, line_nr) - return - - orig_pos = self._getInstance().getOriginalPos() - cur_pos = (vim.current.tabpage, vim.current.window, vim.current.buffer) - - saved_eventignore = vim.options['eventignore'] - vim.options['eventignore'] = 'BufLeave,WinEnter,BufEnter' - try: - vim.current.tabpage, vim.current.window, vim.current.buffer = orig_pos - self._acceptSelection(line, self._getInstance().buffer, line_nr, preview=True) - finally: - vim.current.tabpage, vim.current.window, vim.current.buffer = cur_pos - vim.options['eventignore'] = saved_eventignore - def _bangEnter(self): super(BufTagExplManager, self)._bangEnter() if "--all" in self._arguments and not self._is_content_list: @@ -515,6 +483,7 @@ def _bangEnter(self): self._relocateCursor() def _bangReadFinished(self): + super(BufTagExplManager, self)._bangReadFinished() self._relocateCursor() def _relocateCursor(self): @@ -526,7 +495,7 @@ def _relocateCursor(self): inst = self._getInstance() if inst.empty(): return - orig_buf_nr = inst.getOriginalPos()[2].number + orig_buf_num = inst.getOriginalPos()[2].number orig_line = inst.getOriginalCursor()[0] tags = [] for index, line in enumerate(inst.buffer, 1): @@ -537,10 +506,10 @@ def _relocateCursor(self): elif index & 1 == 0: continue items = re.split(" *\t *", line) - line_nr = int(items[3].rsplit(":", 1)[1]) + line_num = int(items[3].rsplit(":", 1)[1]) buf_number = int(items[4]) - if orig_buf_nr == buf_number: - tags.append((index, buf_number, line_nr)) + if orig_buf_num == buf_number: + tags.append((index, buf_number, line_num)) if self._getInstance().isReverseOrder(): tags = tags[::-1] @@ -567,25 +536,27 @@ def _relocateCursor(self): lfCmd(str(index)) lfCmd("norm! zz") + self._previewResult(False) + def _previewInPopup(self, *args, **kwargs): - if len(args) == 0: + if len(args) == 0 or args[0] == '': return line = args[0] if line[0].isspace(): # if g:Lf_PreviewCode == 1 buffer = args[1] - line_nr = args[2] + line_num = args[2] if self._getInstance().isReverseOrder(): - line = buffer[line_nr] + line = buffer[line_num] else: - line = buffer[line_nr - 2] + line = buffer[line_num - 2] # {tag} {kind} {scope} {file}:{line} {buf_number} items = re.split(" *\t *", line) tagname = items[0] - line_nr = items[3].rsplit(":", 1)[1] + line_num = items[3].rsplit(":", 1)[1] buf_number = int(items[4]) - self._createPopupPreview(tagname, buf_number, line_nr) + self._createPopupPreview(tagname, buf_number, line_num) #***************************************************** diff --git a/autoload/leaderf/python/leaderf/cli.py b/autoload/leaderf/python/leaderf/cli.py index db2b6729..fc7c3219 100644 --- a/autoload/leaderf/python/leaderf/cli.py +++ b/autoload/leaderf/python/leaderf/cli.py @@ -18,11 +18,17 @@ def deco(*args, **kwargs): if lfEval("exists('g:lf_gcr_stack')") == '0': lfCmd("let g:lf_gcr_stack = []") lfCmd("call add(g:lf_gcr_stack, &gcr)") - lfCmd("set gcr=a:invisible") - if lfEval("exists('g:lf_t_ve_stack')") == '0': - lfCmd("let g:lf_t_ve_stack = []") - lfCmd("call add(g:lf_t_ve_stack, &t_ve)") - lfCmd("set t_ve=") + if lfEval("has('nvim')") == '1': + lfCmd("hi Cursor blend=100") + lfCmd("set gcr+=a:ver1-Cursor/lCursor") + else: + lfCmd("set gcr=a:invisible") + + if lfEval("exists('g:lf_t_ve_stack')") == '0': + lfCmd("let g:lf_t_ve_stack = []") + lfCmd("call add(g:lf_t_ve_stack, &t_ve)") + lfCmd("set t_ve=") + lfCmd("let g:Lf_ttimeoutlen_orig = &ttimeoutlen") lfCmd("set ttimeoutlen=0") try: @@ -32,8 +38,11 @@ def deco(*args, **kwargs): lfCmd("let &ttimeoutlen = g:Lf_ttimeoutlen_orig") lfCmd("set gcr&") lfCmd("let &gcr = remove(g:lf_gcr_stack, -1)") - lfCmd("set t_ve&") - lfCmd("let &t_ve = remove(g:lf_t_ve_stack, -1)") + if lfEval("has('nvim')") == '1': + lfCmd("hi Cursor blend=0") + else: + lfCmd("set t_ve&") + lfCmd("let &t_ve = remove(g:lf_t_ve_stack, -1)") return deco @@ -59,17 +68,34 @@ def __init__(self): self._running_status = 0 self._input_buf_namespace = None self._setDefaultMode() + self._is_live = False self._additional_prompt_string = '' + self._quick_select = False + self.last_char = '' self._spin_symbols = lfEval("get(g:, 'Lf_SpinSymbols', [])") if not self._spin_symbols: if platform.system() == "Linux": - self._spin_symbols = ['△', '▲', '▷', '▶', '▽', '▼', '◁', '◀'] + self._spin_symbols = ['✵', '⋆', '✶','✷','✸','✹', '✺'] else: self._spin_symbols = ['🌘', '🌗', '🌖', '🌕', '🌔', '🌓', '🌒', '🌑'] def setInstance(self, instance): self._instance = instance + def setArguments(self, arguments): + self._arguments = arguments + quick_select = self._arguments.get("--quick-select", [0]) + if len(quick_select) == 0: + quick_select_value = 1 + else: + quick_select_value = int(quick_select[0]) + + if "--quick-select" in self._arguments: + self._quick_select = not self._instance.isReverseOrder() and bool(quick_select_value) + else: + self._quick_select = (not self._instance.isReverseOrder() + and bool(int(lfEval("get(g:, 'Lf_QuickSelect', 0)")))) + def _setDefaultMode(self): mode = lfEval("g:Lf_DefaultMode") if mode == 'NameOnly': # nameOnly mode @@ -86,6 +112,7 @@ def _setDefaultMode(self): self._is_full_path = True def setCurrentMode(self, mode): + self._is_live = False if mode == 'NameOnly': # nameOnly mode self._is_fuzzy = True self._is_full_path = False @@ -95,6 +122,9 @@ def setCurrentMode(self, mode): elif mode == 'Fuzzy': # fuzzy mode self._is_fuzzy = True self._is_full_path = False + elif mode == 'Live': # live mode + self._is_fuzzy = False + self._is_live = True else: # regex mode self._is_fuzzy = False @@ -165,28 +195,6 @@ def setPattern(self, pattern): self._cursor_pos = len(self._cmdline) self._buildPattern() - # https://github.com/neovim/neovim/issues/6538 - def _buildNvimPrompt(self): - lfCmd("redraw") - if self._is_fuzzy: - if self._is_full_path: - lfCmd("echohl Constant | echon '>F> {}' | echohl NONE".format(self._additional_prompt_string)) - else: - lfCmd("echohl Constant | echon '>>> {}' | echohl NONE".format(self._additional_prompt_string)) - else: - lfCmd("echohl Constant | echon 'R>> {}' | echohl NONE".format(self._additional_prompt_string)) - - lfCmd("echohl Normal | echon '%s' | echohl NONE" % - escQuote(''.join(self._cmdline[:self._cursor_pos]))) - if self._cursor_pos < len(self._cmdline): - lfCmd("hi! default link Lf_hl_cursor Cursor") - lfCmd("echohl Lf_hl_cursor | echon '%s' | echohl NONE" % - escQuote(''.join(self._cmdline[self._cursor_pos]))) - lfCmd("echohl Normal | echon '%s' | echohl NONE" % - escQuote(''.join(self._cmdline[self._cursor_pos+1:]))) - else: - lfCmd("hi! default link Lf_hl_cursor NONE") - def _buildPopupPrompt(self): self._instance.mimicCursor() @@ -195,6 +203,8 @@ def _buildPopupPrompt(self): prompt = ' >F> {}'.format(self._additional_prompt_string) else: prompt = ' >>> {}'.format(self._additional_prompt_string) + elif self._is_live: + prompt = ' >>> {}'.format(self._additional_prompt_string) else: prompt = ' R>> {}'.format(self._additional_prompt_string) @@ -202,6 +212,8 @@ def _buildPopupPrompt(self): input_window = self._instance.getPopupInstance().input_win content_winid = self._instance.getPopupInstance().content_win.id input_win_width = input_window.width + if lfEval("get(g:, 'Lf_PopupShowBorder', 1)") == '1' and lfEval("has('nvim')") == '0': + input_win_width -= 2 if self._instance.getWinPos() == 'popup': lfCmd("""call win_execute(%d, 'let line_num = line(".")')""" % content_winid) line_num = lfEval("line_num") @@ -238,7 +250,7 @@ def _buildPopupPrompt(self): sep, part3, sep_width=len(sep), - part1_width=part1_width, + part1_width=max(0, part1_width), part2_width=len(part2), part3_width=len(part3)) if self._instance.getWinPos() == 'popup': @@ -323,23 +335,16 @@ def buildPopupPrompt(self): lfCmd("silent! redraw") def _buildPrompt(self): - if lfEval("has('nvim')") == '1' and self._instance.getWinPos() != 'floatwin': - self._buildNvimPrompt() - return - if self._idle and datetime.now() - self._start_time < timedelta(milliseconds=500): # 500ms return else: self._start_time = datetime.now() if self._blinkon: - if self._instance.getWinPos() in ('popup', 'floatwin'): - lfCmd("hi! default link Lf_hl_cursor Lf_hl_popup_cursor") - else: - lfCmd("hi! default link Lf_hl_cursor Cursor") + lfCmd("hi! default link Lf_hl_cursor Lf_hl_popup_cursor") else: lfCmd("hi! default link Lf_hl_cursor NONE") - if lfEval("g:Lf_CursorBlink") == '1': + if lfEval("g:Lf_CursorBlink") == '1' and 0: self._blinkon = not self._blinkon elif self._idle: lfCmd("silent! redraw") @@ -354,6 +359,8 @@ def _buildPrompt(self): lfCmd("echohl Constant | echon '>F> {}' | echohl NONE".format(self._additional_prompt_string)) else: lfCmd("echohl Constant | echon '>>> {}' | echohl NONE".format(self._additional_prompt_string)) + elif self._is_live: + lfCmd("echohl Constant | echon '>>> {}' | echohl NONE".format(self._additional_prompt_string)) else: lfCmd("echohl Constant | echon 'R>> {}' | echohl NONE".format(self._additional_prompt_string)) @@ -369,11 +376,17 @@ def _buildPrompt(self): lfCmd("redraw") def _buildPattern(self): + case_insensitive = "--case-insensitive" in self._arguments if self._is_fuzzy: if self._and_delimiter in ''.join(self._cmdline).lstrip(self._and_delimiter) \ and self._delimiter not in self._cmdline: self._is_and_mode = True - patterns = re.split(r'['+self._and_delimiter+']+', ''.join(self._cmdline).strip(self._and_delimiter)) + if case_insensitive: + patterns = re.split(r'['+self._and_delimiter+']+', + ''.join(self._cmdline).strip(self._and_delimiter).lower()) + else: + patterns = re.split(r'['+self._and_delimiter+']+', + ''.join(self._cmdline).strip(self._and_delimiter)) pattern_dict = OrderedDict([]) for p in patterns: if p in pattern_dict: @@ -391,13 +404,20 @@ def _buildPattern(self): self._supports_refine) and self._delimiter in self._cmdline): self._refine = True idx = self._cmdline.index(self._delimiter) - self._pattern = (''.join(self._cmdline[:idx]), - ''.join(self._cmdline[idx+1:])) + if case_insensitive: + self._pattern = (''.join(self._cmdline[:idx]).lower(), + ''.join(self._cmdline[idx+1:]).lower()) + else: + self._pattern = (''.join(self._cmdline[:idx]), + ''.join(self._cmdline[idx+1:])) if self._pattern == ('', ''): self._pattern = None else: self._refine = False - self._pattern = ''.join(self._cmdline) + if case_insensitive: + self._pattern = ''.join(self._cmdline).lower() + else: + self._pattern = ''.join(self._cmdline) else: self._is_and_mode = False self._pattern = ''.join(self._cmdline) @@ -405,7 +425,7 @@ def _buildPattern(self): def _join(self, cmdline): if not cmdline: return '' - cmd = ['%s\[^%s]\{-}' % (c, c) for c in cmdline[0:-1]] + cmd = [r'%s\[^%s]\{-}' % (c, c) for c in cmdline[0:-1]] cmd.append(cmdline[-1]) regex = ''.join(cmd) return regex @@ -427,31 +447,31 @@ def highlightMatches(self): cmdline = [r'\/' if c == '/' else r'\\' if c == '\\' else c for c in self._cmdline] # \/ for syn match if self._is_full_path: - regex = '\c\V' + self._join(cmdline) + regex = r'\c\V' + self._join(cmdline) lfCmd("syn match Lf_hl_match display /%s/ containedin=" "Lf_hl_nonHelp, Lf_hl_dirname, Lf_hl_filename contained" % regex) else: if self._refine: idx = self._cmdline.index(self._delimiter) - regex = ('\c\V' + self._join(cmdline[:idx]), - '\c\V' + self._join(cmdline[idx+1:])) - if regex[0] == '\c\V' and regex[1] == '\c\V': + regex = (r'\c\V' + self._join(cmdline[:idx]), + r'\c\V' + self._join(cmdline[idx+1:])) + if regex[0] == r'\c\V' and regex[1] == r'\c\V': pass - elif regex[0] == '\c\V': + elif regex[0] == r'\c\V': lfCmd("syn match Lf_hl_match display /%s/ " "containedin=Lf_hl_dirname, Lf_hl_filename " "contained" % regex[1]) - elif regex[1] == '\c\V': + elif regex[1] == r'\c\V': lfCmd("syn match Lf_hl_match display /%s/ " "containedin=Lf_hl_filename contained" % regex[0]) else: lfCmd("syn match Lf_hl_match display /%s/ " "containedin=Lf_hl_filename contained" % regex[0]) lfCmd("syn match Lf_hl_match_refine display " - "/%s\(\.\*\[\/]\)\@=/ containedin=" + r"/%s\(\.\*\[\/]\)\@=/ containedin=" "Lf_hl_dirname contained" % regex[1]) else: - regex = '\c\V' + self._join(cmdline) + regex = r'\c\V' + self._join(cmdline) lfCmd("syn match Lf_hl_match display /%s/ " "containedin=Lf_hl_filename contained" % regex) else: @@ -523,7 +543,7 @@ def writeHistory(self, category): else: pattern = self._pattern - history_dir = os.path.join(lfEval("g:Lf_CacheDirectory"), '.LfCache', 'history', category) + history_dir = os.path.join(lfEval("g:Lf_CacheDirectory"), 'LeaderF', 'history', category) if self._is_fuzzy: history_file = os.path.join(history_dir, 'fuzzy.txt') else: @@ -553,7 +573,7 @@ def writeHistory(self, category): f.writelines(lines) def previousHistory(self, category): - history_dir = os.path.join(lfEval("g:Lf_CacheDirectory"), '.LfCache', 'history', category) + history_dir = os.path.join(lfEval("g:Lf_CacheDirectory"), 'LeaderF', 'history', category) if self._is_fuzzy: history_file = os.path.join(history_dir, 'fuzzy.txt') else: @@ -576,7 +596,7 @@ def previousHistory(self, category): return True def nextHistory(self, category): - history_dir = os.path.join(lfEval("g:Lf_CacheDirectory"), '.LfCache', 'history', category) + history_dir = os.path.join(lfEval("g:Lf_CacheDirectory"), 'LeaderF', 'history', category) if self._is_fuzzy: history_file = os.path.join(history_dir, 'fuzzy.txt') else: @@ -643,6 +663,7 @@ def input(self, callback): start = time.time() update = False prefix = "" + block = "1" while 1: if len(self._instance._manager._content) < 60000: @@ -659,7 +680,7 @@ def input(self, callback): time.sleep(0.001) if lfEval("get(g:, 'Lf_NoAsync', 0)") == '0': - lfCmd("let nr = getchar(1)") + lfCmd("let nr = getchar({})".format(block)) if lfEval("!type(nr) && nr == 0") == '1': self._idle = True if lfEval("has('nvim') && exists('g:GuiLoaded')") == '1': @@ -675,40 +696,22 @@ def input(self, callback): start = time.time() else: try: - callback() - except Exception: - lfPrintTraceback() - break - - continue - # https://groups.google.com/forum/#!topic/vim_dev/gg-l-kaCz_M - # '<80>^B' is , '<80>^D' is , - # '<80>^H' is , '<80>^L' is - elif lfEval("type(nr) != 0") == '1': - lfCmd("call getchar(0)") - lfCmd("call feedkeys('a') | call getchar()") - self._idle = True - if lfEval("has('nvim') && exists('g:GuiLoaded')") == '1': - time.sleep(0.009) # this is to solve issue 375 leaderF hangs in nvim-qt - - if update == True: - if time.time() - start >= threshold: - update = False - if ''.join(self._cmdline).startswith(prefix): - yield '' - else: - yield '' - start = time.time() - else: - try: - callback() + ret = callback() + if ret == 100: + block = "" + if self._instance.getWinPos() in ('popup', 'floatwin'): + self.buildPopupPrompt() + else: + lfCmd("redraw") except Exception: lfPrintTraceback() break continue else: - lfCmd("let nr = getchar()") + if block == "1": + lfCmd("let nr = getchar()") + block = "1" lfCmd("let ch = !type(nr) ? nr2char(nr) : nr") self._blinkon = True else: @@ -718,11 +721,17 @@ def input(self, callback): self._blinkon = True if lfEval("!type(nr) && nr >= 0x20") == '1': + char = lfEval("ch") + if self._quick_select and char in "0123456789": + self.last_char = char + yield '' + continue + if update == False: update = True prefix = ''.join(self._cmdline) - self._insert(lfEval("ch")) + self._insert(char) self._buildPattern() if self._pattern is None or (self._refine and self._pattern[1] == ''): # e.g. abc; continue @@ -736,7 +745,7 @@ def input(self, callback): else: cmd = '' for (key, value) in self._key_dict.items(): - if lfEval('ch ==# "\%s"' % key) == '1': + if lfEval(r'ch ==# "\%s"' % key) == '1': cmd = value break if equal(cmd, ''): @@ -752,9 +761,10 @@ def input(self, callback): self._buildPattern() yield '' elif equal(cmd, ''): - self._is_fuzzy = not self._is_fuzzy - self._buildPattern() - yield '' + if not self._is_live: + self._is_fuzzy = not self._is_fuzzy + self._buildPattern() + yield '' elif equal(cmd, '') or equal(cmd, ''): if not self._pattern and self._refine == False: continue @@ -803,13 +813,9 @@ def input(self, callback): elif equal(cmd, ''): self._toRight() elif equal(cmd, ''): - yield '' - yield '' - yield '' + yield '' elif equal(cmd, ''): - yield '' - yield '' - yield '' + yield '' elif equal(cmd, ''): yield '' else: diff --git a/autoload/leaderf/python/leaderf/cocExpl.py b/autoload/leaderf/python/leaderf/cocExpl.py new file mode 100644 index 00000000..ad41d942 --- /dev/null +++ b/autoload/leaderf/python/leaderf/cocExpl.py @@ -0,0 +1,578 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import vim +import re +import os +import os.path +import urllib.parse +from functools import wraps +from .utils import * +from .explorer import * +from .manager import * + + +def workingDirectory(func): + @wraps(func) + def deco(self, *args, **kwargs): + if self._getExplorer()._cmd_work_dir == lfGetCwd(): + return func(self, *args, **kwargs) + + # https://github.com/neovim/neovim/issues/8336 + if lfEval("has('nvim')") == '1': + chdir = vim.chdir + else: + chdir = os.chdir + orig_cwd = lfGetCwd() + chdir(self._getExplorer()._cmd_work_dir) + try: + return func(self, *args, **kwargs) + finally: + chdir(orig_cwd) + + return deco + +#***************************************************** +# CocExplorer +#***************************************************** +class CocExplorer(Explorer): + def __init__(self): + self._pattern_regex = [] + self._cmd_work_dir = "" + + def getContent(self, *args, **kwargs): + self._cmd_work_dir = lfGetCwd() + commands = lfEval("leaderf#Coc#Commands()") + return [list(item)[0] for item in commands] + + def getStlCategory(self): + return 'Coc' + + def getStlCurDir(self): + return escQuote(lfEncode(self._cmd_work_dir)) + + def supportsNameOnly(self): + return False + + def getPatternRegex(self): + return self._pattern_regex + + def getFileLine(self, file_name, line_num, file_contents): + if lfEval("bufloaded('%s')" % escQuote(file_name)) == '1': + return lfEval("getbufline('{}', {}, {})[0]".format(file_name, line_num, line_num)) + else: + if file_name not in file_contents: + with lfOpen(file_name, 'r', errors='ignore') as f: + file_contents[file_name] = f.readlines() + + return file_contents[file_name][line_num-1].rstrip("\r\n") + + def generateContent(self, items): + self._pattern_regex = [] + content = [] + file_contents = {} + for item in items: + try: + file_path = lfRelpath(urllib.parse.unquote(item["uri"][7:])) + line_num = int(item["range"]["start"]["line"]) + col_num = int(item["range"]["start"]["character"]) + line = self.getFileLine(file_path, line_num + 1, file_contents) + if len(self._pattern_regex) == 0: + end_line_num = int(item["range"]["end"]["line"]) + end_col_num = int(item["range"]["end"]["character"]) + if end_line_num == line_num: + self._pattern_regex.append(line[col_num: end_col_num]) + + content.append("{}:{}:{}:{}".format(file_path, line_num+1, col_num+1, line)) + except: + pass + + return content + + +class DefinitionsExplorer(CocExplorer): + def getContent(self, *args, **kwargs): + try: + self._cmd_work_dir = lfGetCwd() + definitions = lfEval("CocAction('definitions')") + return self.generateContent(definitions) + except vim.error as e: + lfPrintError(str(e).split('\n')[-1]) + return None + + def supportsNameOnly(self): + return True + + def getStlCategory(self): + return 'definitions' + + +class DeclarationsExplorer(CocExplorer): + def getContent(self, *args, **kwargs): + try: + self._cmd_work_dir = lfGetCwd() + declarations = lfEval("CocAction('declarations')") + return self.generateContent(declarations) + except vim.error as e: + lfPrintError(str(e).split('\n')[-1]) + return None + + def supportsNameOnly(self): + return True + + def getStlCategory(self): + return 'declarations' + + +class ImplementationsExplorer(CocExplorer): + def getContent(self, *args, **kwargs): + try: + self._cmd_work_dir = lfGetCwd() + implementations = lfEval("CocAction('implementations')") + return self.generateContent(implementations) + except vim.error as e: + lfPrintError(str(e).split('\n')[-1]) + return None + + def supportsNameOnly(self): + return True + + def getStlCategory(self): + return 'implementations' + + +class TypeDefinitionsExplorer(CocExplorer): + def getContent(self, *args, **kwargs): + try: + self._cmd_work_dir = lfGetCwd() + typeDefinitions = lfEval("CocAction('typeDefinitions')") + return self.generateContent(typeDefinitions) + except vim.error as e: + lfPrintError(str(e).split('\n')[-1]) + return None + + def supportsNameOnly(self): + return True + + def getStlCategory(self): + return 'typeDefinitions' + + +class ReferencesExplorer(CocExplorer): + def getContent(self, *args, **kwargs): + try: + self._cmd_work_dir = lfGetCwd() + + arguments_dict = kwargs.get("arguments", {}) + if "--exclude-declaration" in arguments_dict: + references = lfEval("CocAction('references', 1)") + else: + references = lfEval("CocAction('references')") + + return self.generateContent(references) + except vim.error as e: + lfPrintError(str(e).split('\n')[-1]) + return None + + def supportsNameOnly(self): + return True + + def getStlCategory(self): + return 'references' + + +#***************************************************** +# CocExplManager +#***************************************************** +class CocExplManager(Manager): + def __init__(self): + super(CocExplManager, self).__init__() + self._managers = {} + + def _getExplClass(self): + return CocExplorer + + def _defineMaps(self): + lfCmd("call leaderf#Coc#Maps({})".format(id(self))) + + def _createHelp(self): + help = [] + help.append('" //o : open file under cursor') + help.append('" x : open file under cursor in a horizontally split window') + help.append('" v : open file under cursor in a vertically split window') + help.append('" t : open file under cursor in a new tabpage') + help.append('" i/ : switch to input mode') + help.append('" p : preview the file') + help.append('" q : quit') + help.append('" : toggle this help') + help.append('" : close the preview window or quit') + help.append('" ---------------------------------------------------------') + return help + + def _getDigest(self, line, mode): + """ + 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 + """ + return line + + def _getDigestStartPos(self, line, mode): + """ + 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 + """ + return 0 + + def createManager(self, subcommand): + if subcommand == "definitions": + return DefinitionsExplManager() + elif subcommand == "declarations": + return DeclarationsExplManager() + elif subcommand == "implementations": + return ImplementationsExplManager() + elif subcommand == "typeDefinitions": + return TypeDefinitionsExplManager() + elif subcommand == "references": + return ReferencesExplManager() + else: + return super(CocExplManager, self) + + def getExplManager(self, subcommand): + if subcommand not in self._managers: + self._managers[subcommand] = self.createManager(subcommand) + return self._managers[subcommand] + + def startExplorer(self, win_pos, *args, **kwargs): + arguments_dict = kwargs.get("arguments", {}) + if "--recall" in arguments_dict: + self._arguments.update(arguments_dict) + else: + self.setArguments(arguments_dict) + + arg_list = self._arguments.get("arg_line", 'coc').split() + arg_list = [item for item in arg_list if not item.startswith('-')] + if len(arg_list) == 1: + subcommand = "" + else: + subcommand = arg_list[1] + self.getExplManager(subcommand).startExplorer(win_pos, *args, **kwargs) + + def _acceptSelection(self, *args, **kwargs): + if len(args) == 0: + return + + line = args[0] + cmd = line + try: + lfCmd(cmd) + except vim.error: + lfPrintTraceback() + + def getSource(self, line): + commands = lfEval("leaderf#Coc#Commands()") + for cmd in commands: + if line in cmd: + return cmd[line] + + return None + + def _previewInPopup(self, *args, **kwargs): + if len(args) == 0 or args[0] == '': + return + + line = args[0] + source = self.getSource(line) + + self._createPopupPreview("", source, 0) + + def _createPreviewWindow(self, config, source, line_num, jump_cmd): + if lfEval("has('nvim')") == '1': + lfCmd("noautocmd let scratch_buffer = nvim_create_buf(0, 1)") + lfCmd("noautocmd call setbufline(scratch_buffer, 1, '{}')".format(escQuote(source))) + lfCmd("noautocmd call nvim_buf_set_option(scratch_buffer, 'bufhidden', 'wipe')") + lfCmd("noautocmd call nvim_buf_set_option(scratch_buffer, 'undolevels', -1)") + + self._preview_winid = int(lfEval("nvim_open_win(scratch_buffer, 0, {})" + .format(json.dumps(config)))) + else: + lfCmd("noautocmd let winid = popup_create('{}', {})" + .format(escQuote(source), json.dumps(config))) + self._preview_winid = int(lfEval("winid")) + + self._setWinOptions(self._preview_winid) + + def _useExistingWindow(self, title, source, line_num, jump_cmd): + self.setOptionsForCursor() + + if lfEval("has('nvim')") == '1': + lfCmd("""call win_execute({}, "call nvim_buf_set_lines(0, 0, -1, v:false, ['{}'])")""" + .format(self._preview_winid, escQuote(source))) + else: + lfCmd("noautocmd call popup_settext({}, '{}')" + .format(self._preview_winid, escQuote(source))) + + def _beforeExit(self): + super(CocExplManager, self)._beforeExit() + for k, v in self._cursorline_dict.items(): + if k.valid: + k.options["cursorline"] = v + self._cursorline_dict.clear() + + +class CommonExplManager(CocExplManager): + def _afterEnter(self): + super(CocExplManager, self)._afterEnter() + if self._getInstance().getWinPos() == 'popup': + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_rgFileName'', ''^.\{-}\ze\:\d\+:'', 10)')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_rgLineNumber'', ''^.\{-}\zs:\d\+:'', 10)')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_rgColumnNumber'', ''^.\{-}:\d\+:\zs\d\+:'', 10)')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + self._match_ids.append(id) + try: + for i in self._getExplorer().getPatternRegex(): + i = r'\<{}\>'.format(i) + lfCmd("""call win_execute(%d, "let matchid = matchadd('Lf_hl_rgHighlight', '%s', 9)")""" + % (self._getInstance().getPopupWinId(), re.sub(r'\\(?!")', r'\\\\', escQuote(i)))) + id = int(lfEval("matchid")) + self._match_ids.append(id) + except vim.error: + pass + else: + id = int(lfEval(r"matchadd('Lf_hl_rgFileName', '^.\{-}\ze\:\d\+:', 10)")) + self._match_ids.append(id) + id = int(lfEval(r"matchadd('Lf_hl_rgLineNumber', '^.\{-}\zs:\d\+:', 10)")) + self._match_ids.append(id) + id = int(lfEval(r"matchadd('Lf_hl_rgColumnNumber', '^.\{-}:\d\+:\zs\d\+:', 10)")) + self._match_ids.append(id) + + try: + for i in self._getExplorer().getPatternRegex(): + i = r'\<{}\>'.format(i) + id = int(lfEval("matchadd('Lf_hl_rgHighlight', '%s', 9)" % escQuote(i))) + self._match_ids.append(id) + except vim.error: + pass + + def setStlMode(self, mode): + if mode == "FullPath": + mode = "WholeLine" + elif mode == "NameOnly": + mode = "Contents" + super(CocExplManager, self).setStlMode(mode) + + def _getFileInfo(self, args): + line = args[0] + + m = re.match(r'^(.+?):(\d+):(\d+):', line) + if m is None: + return (None, None, None) + + file, line_num, col_num = m.group(1, 2, 3) + if not os.path.isabs(file): + file = os.path.join(self._getInstance().getCwd(), lfDecode(file)) + i = 1 + while not os.path.exists(lfDecode(file)): + m = re.match(r'^(.+?(?::\d+.*?){%d}):(\d+):(\d+):' % i, line) + if m is None: + return (None, None, None) + i += 1 + file, line_num, col_num = m.group(1, 2, 3) + if not os.path.isabs(file): + file = os.path.join(self._getInstance().getCwd(), lfDecode(file)) + + file = os.path.normpath(lfEncode(file)) + + return (file, line_num, col_num) + + @workingDirectory + def _acceptSelection(self, *args, **kwargs): + if len(args) == 0: + return + + file, line_num, col_num = self._getFileInfo(args) + if file is None: + return + + try: + is_shown = False + if kwargs.get("mode", '') == 't': + if lfEval("get(g:, 'Lf_JumpToExistingWindow', 1)") == '1' \ + and lfEval("bufloaded('%s')" % escQuote(file)) == '1': + winid = int(lfEval("bufwinid('%s')" % escQuote(file))) + start_line = int(lfEval("line('w0', %d)" % winid)) + end_line = int(lfEval("line('w$', %d)" % winid)) + lfDrop('tab', file, line_num) + if start_line <= int(line_num) <= end_line: + is_shown = True + else: + lfCmd("tabe %s | %s" % (escSpecial(file), line_num)) + else: + if lfEval("get(g:, 'Lf_JumpToExistingWindow', 1)") == '1' \ + and lfEval("bufloaded('%s')" % escQuote(file)) == '1': + winid = int(lfEval("bufwinid('%s')" % escQuote(file))) + start_line = int(lfEval("line('w0', %d)" % winid)) + end_line = int(lfEval("line('w$', %d)" % winid)) + lfDrop('', file, line_num) + if start_line <= int(line_num) <= end_line: + is_shown = True + else: + lfCmd("hide edit +%s %s" % (line_num, escSpecial(file))) + + lfCmd("norm! ^zv") + lfCmd("norm! {}|".format(col_num)) + + if is_shown == False: + lfCmd("norm! zz") + + if "preview" not in kwargs: + lfCmd("setlocal cursorline! | redraw | sleep 150m | setlocal cursorline!") + + if vim.current.window not in self._cursorline_dict: + self._cursorline_dict[vim.current.window] = vim.current.window.options["cursorline"] + + lfCmd("setlocal cursorline") + except vim.error as e: # E37 + if 'E325' not in str(e).split(':'): + lfPrintTraceback() + + def _highlightInPreview(self): + if lfEval("has('nvim')") != '1': + try: + for i in self._getExplorer().getPatternRegex(): + i = r'\<{}\>'.format(i) + lfCmd("""call win_execute(%d, "let matchid = matchadd('Lf_hl_rgHighlight', '%s', 9)")""" + % (self._preview_winid, re.sub(r'\\(?!")', r'\\\\', escQuote(i)))) + id = int(lfEval("matchid")) + self._match_ids.append(id) + except vim.error: + pass + else: + cur_winid = lfEval("win_getid()") + lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) + if lfEval("win_getid()") != cur_winid: + try: + for i in self._getExplorer().getPatternRegex(): + i = r'\<{}\>'.format(i) + id = int(lfEval("matchadd('Lf_hl_rgHighlight', '%s', 9)" % escQuote(i))) + self._match_ids.append(id) + except vim.error: + pass + lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) + + def _previewInPopup(self, *args, **kwargs): + if len(args) == 0 or args[0] == '': + return + + file, line_num, col_num = self._getFileInfo(args) + if file is None: + return + + if lfEval("bufloaded('%s')" % escQuote(file)) == '1': + source = int(lfEval("bufadd('%s')" % escQuote(file))) + else: + source = file + + self._createPopupPreview("", source, line_num) + self._highlightInPreview() + + def _getDigest(self, line, mode): + """ + 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 line.split(":", 3)[-1] + else: + return line.split(":", 1)[0] + + def _getDigestStartPos(self, line, mode): + """ + 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: + file_path, line_num, column, content = line.split(":", 3) + return lfBytesLen(file_path + line_num + column) + 3 + + def _createPreviewWindow(self, config, source, line_num, jump_cmd): + return super(CocExplManager, self)._createPreviewWindow(config, source, line_num, jump_cmd) + + def _useExistingWindow(self, title, source, line_num, jump_cmd): + return super(CocExplManager, self)._useExistingWindow(title, source, line_num, jump_cmd) + + def startExplorer(self, win_pos, *args, **kwargs): + return super(CocExplManager, self).startExplorer(win_pos, *args, **kwargs) + + def autoJump(self, content): + if "--auto-jump" in self._arguments and isinstance(content, list) and len(content) == 1: + mode = self._arguments["--auto-jump"][0] if len(self._arguments["--auto-jump"]) else "" + self._accept(content[0], mode) + return True + + return False + + +class DefinitionsExplManager(CommonExplManager): + def _getExplorer(self): + if self._explorer is None: + self._explorer = DefinitionsExplorer() + return self._explorer + + +class DeclarationsExplManager(CommonExplManager): + def _getExplorer(self): + if self._explorer is None: + self._explorer = DeclarationsExplorer() + return self._explorer + + +class ImplementationsExplManager(CommonExplManager): + def _getExplorer(self): + if self._explorer is None: + self._explorer = ImplementationsExplorer() + return self._explorer + + +class TypeDefinitionsExplManager(CommonExplManager): + def _getExplorer(self): + if self._explorer is None: + self._explorer = TypeDefinitionsExplorer() + return self._explorer + + +class ReferencesExplManager(CommonExplManager): + def _getExplorer(self): + if self._explorer is None: + self._explorer = ReferencesExplorer() + return self._explorer + + +#***************************************************** +# cocExplManager is a singleton +#***************************************************** +cocExplManager = CocExplManager() + +__all__ = ['cocExplManager'] diff --git a/autoload/leaderf/python/leaderf/colorschemeExpl.py b/autoload/leaderf/python/leaderf/colorschemeExpl.py index 171418f2..b60f3c42 100644 --- a/autoload/leaderf/python/leaderf/colorschemeExpl.py +++ b/autoload/leaderf/python/leaderf/colorschemeExpl.py @@ -71,7 +71,6 @@ def getStlCurDir(self): class ColorschemeExplManager(Manager): def __init__(self): super(ColorschemeExplManager, self).__init__() - self._orig_line = '' def _getExplClass(self): return ColorschemeExplorer @@ -124,7 +123,7 @@ def _beforeExit(self): super(ColorschemeExplManager, self)._beforeExit() def _previewResult(self, preview): - if not self._needPreview(preview): + if not self._needPreview(preview, True): return self._acceptSelection(self._getInstance().currentLine) diff --git a/autoload/leaderf/python/leaderf/commandExpl.py b/autoload/leaderf/python/leaderf/commandExpl.py index 8760b7f6..3f308cac 100644 --- a/autoload/leaderf/python/leaderf/commandExpl.py +++ b/autoload/leaderf/python/leaderf/commandExpl.py @@ -12,7 +12,7 @@ RE_USER_DEFINED_COMMAND = re.compile(r"^.{4}(\w+)") # index.txt line -# "|:silent| :sil[ent] ..." +# "|:silent| :sil[ent] ..." # ^^^^^^ RE_BUILT_IN_COMMAND = re.compile(r"^\|:([^|]+)\|") @@ -33,7 +33,7 @@ def getFreshContent(self, *args, **kwargs): result_list = [] # user-defined Ex commands - result = lfEval("execute('command')") + result = lfEval("leaderf#execute('command')") for line in result.splitlines()[2:]: match = RE_USER_DEFINED_COMMAND.match(line) diff --git a/autoload/leaderf/python/leaderf/devicons.py b/autoload/leaderf/python/leaderf/devicons.py index 29d595fa..7b7798c0 100644 --- a/autoload/leaderf/python/leaderf/devicons.py +++ b/autoload/leaderf/python/leaderf/devicons.py @@ -48,7 +48,7 @@ 'Rmd' : '', 'es' : '', 'less' : '', 'rej' : '', 'Smd' : '', 'ex' : '', 'lhs' : '', 'rlib' : '', 'ai' : '', 'exs' : '', 'lisp' : '', 'rmd' : '', - 'awk' : '', 'f#' : '', 'lock' : '', 'rmeta' : '', + 'awk' : '', 'f#' : '', 'lock' : '', 'rmeta' : '', 'bash' : '', 'fish' : '', 'log' : '', 'rs' : '', 'bat' : '', 'fs' : '', 'lsp' : '', 'rss' : '', 'bin' : '', 'fsi' : '', 'lua' : '', 'sass' : '', @@ -85,7 +85,7 @@ 'el' : '', 'jsonp' : '', 'pyw' : '', 'xml' : '', 'elm' : '', 'jsx' : '', 'rb' : '', 'xul' : '', 'yaml' : '', 'yaws' : '', 'yml' : '', 'zip' : '', - 'zsh' : '', + 'zsh' : '', 'nix' : '󱄅', } fileNodesExtensionSymbols.update(lfEval("get(g:, 'Lf_DevIconsExtensionSymbols', {})")) @@ -353,10 +353,13 @@ devicons_palette["dark"].update(lfEval("get(get(g:, 'Lf_DevIconsPalette', {}), 'dark', {})")) devicons_palette["light"].update(lfEval("get(get(g:, 'Lf_DevIconsPalette', {}), 'light', {})")) -if os.name == 'nt' or lfEval('&ambiwidth') == "double": - _spaces = ' ' +if os.name == "nt" or lfEval('&ambiwidth') == "double": + _spaces = lfEval("get(g:, 'Lf_SpacesAfterIcon', ' ')") else: - _spaces = ' ' + _spaces = lfEval("get(g:, 'Lf_SpacesAfterIcon', ' ')") + +if _spaces == "": + _spaces = ' ' _default_palette = { "gui": "NONE", @@ -420,10 +423,13 @@ def _getExt(file): def setAmbiwidth(val): global _spaces - if os.name == 'nt' or val == "double": - _spaces = ' ' + if os.name == "nt" or val == "double": + _spaces = lfEval("get(g:, 'Lf_SpacesAfterIcon', ' ')") else: - _spaces = ' ' + _spaces = lfEval("get(g:, 'Lf_SpacesAfterIcon', ' ')") + + if _spaces == "": + _spaces = ' ' # To use asynchronously def webDevIconsGetFileTypeSymbol(file, isdir=False): @@ -448,7 +454,7 @@ def _normalize_name(val): return RE_CANNOT_USE_FOR_HIGHLIGHT.sub('__', val) def _matchadd(icons, pattern, priority, winid): - """ + r""" Enable ignore case (\c flag) """ ids = [] @@ -472,7 +478,7 @@ def _matchadd(icons, pattern, priority, winid): return ids def matchaddDevIconsDefault(pattern, winid=None): - """ + r""" pattern: It will be converted to the following __icon__ => icon @@ -486,7 +492,7 @@ def convertor(pattern, _, glyph): return _matchadd({'default': fileNodesDefaultSymbol}, pattern, 9, winid) def matchaddDevIconsExact(pattern, winid=None): - """ + r""" pattern: It will be converted to the following __icon__ => icon @@ -498,7 +504,7 @@ def matchaddDevIconsExact(pattern, winid=None): return _matchadd(fileNodesExactSymbols, pattern, 8, winid) def matchaddDevIconsExtension(pattern, winid=None): - """ + r""" pattern: It will be converted to the following __icon__ => icon diff --git a/autoload/leaderf/python/leaderf/diff.py b/autoload/leaderf/python/leaderf/diff.py new file mode 100644 index 00000000..197f4808 --- /dev/null +++ b/autoload/leaderf/python/leaderf/diff.py @@ -0,0 +1,99 @@ +from difflib import * + +class LfDiffer(Differ): + # copyed from https://github.com/python/cpython tag: v3.12.3 + # f6650f9ad73359051f3e558c2431a109bc016664 + def _fancy_replace(self, a, alo, ahi, b, blo, bhi): + r""" + When replacing one block of lines with another, search the blocks + for *similar* lines; the best-matching pair (if any) is used as a + synch point, and intraline difference marking is done on the + similar pair. Lots of work, but often worth it. + + Example: + + >>> d = Differ() + >>> results = d._fancy_replace(['abcDefghiJkl\n'], 0, 1, + ... ['abcdefGhijkl\n'], 0, 1) + >>> print(''.join(results), end="") + - abcDefghiJkl + ? ^ ^ ^ + + abcdefGhijkl + ? ^ ^ ^ + """ + + # don't synch up unless the lines have a similarity score of at + # least cutoff; best_ratio tracks the best score seen so far + best_ratio, cutoff = 0.54, 0.55 + cruncher = SequenceMatcher(self.charjunk) + eqi, eqj = None, None # 1st indices of equal lines (if any) + + # search for the pair that matches best without being identical + # (identical lines must be junk lines, & we don't want to synch up + # on junk -- unless we have to) + for j in range(blo, bhi): + bj = b[j] + cruncher.set_seq2(bj) + for i in range(alo, ahi): + ai = a[i] + if ai == bj: + if eqi is None: + eqi, eqj = i, j + continue + cruncher.set_seq1(ai) + # computing similarity is expensive, so use the quick + # upper bounds first -- have seen this speed up messy + # compares by a factor of 3. + # note that ratio() is only expensive to compute the first + # time it's called on a sequence pair; the expensive part + # of the computation is cached by cruncher + if cruncher.real_quick_ratio() > best_ratio and \ + cruncher.quick_ratio() > best_ratio and \ + cruncher.ratio() > best_ratio: + best_ratio, best_i, best_j = cruncher.ratio(), i, j + if best_ratio < cutoff: + # no non-identical "pretty close" pair + if eqi is None: + # no identical pair either -- treat it as a straight replace + yield from self._plain_replace(a, alo, ahi, b, blo, bhi) + return + # no close pair, but an identical pair -- synch up on that + best_i, best_j, best_ratio = eqi, eqj, 1.0 + else: + # there's a close pair, so forget the identical pair (if any) + eqi = None + + # a[best_i] very similar to b[best_j]; eqi is None iff they're not + # identical + + # pump out diffs from before the synch point + yield from self._fancy_helper(a, alo, best_i, b, blo, best_j) + + # do intraline marking on the synch pair + aelt, belt = a[best_i], b[best_j] + if eqi is None: + # pump out a '-', '?', '+', '?' quad for the synched lines + atags = btags = "" + cruncher.set_seqs(aelt, belt) + for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes(): + la, lb = ai2 - ai1, bj2 - bj1 + if tag == 'replace': + atags += '^' * la + btags += '^' * lb + elif tag == 'delete': + atags += '-' * la + elif tag == 'insert': + btags += '+' * lb + elif tag == 'equal': + atags += ' ' * la + btags += ' ' * lb + else: + raise ValueError('unknown tag %r' % (tag,)) + yield from self._qformat(aelt, belt, atags, btags) + else: + # the synch pair is identical + yield ' ' + aelt + + # pump out diffs from after the synch point + yield from self._fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi) + diff --git a/autoload/leaderf/python/leaderf/fileExpl.py b/autoload/leaderf/python/leaderf/fileExpl.py index ba5691e6..16e58af4 100644 --- a/autoload/leaderf/python/leaderf/fileExpl.py +++ b/autoload/leaderf/python/leaderf/fileExpl.py @@ -58,7 +58,7 @@ def __init__(self): self._cur_dir = '' self._content = [] self._cache_dir = os.path.join(lfEval("g:Lf_CacheDirectory"), - '.LfCache', + 'LeaderF', 'python' + lfEval("g:Lf_PythonVersion"), 'file') self._cache_index = os.path.join(self._cache_dir, 'cacheIndex') @@ -107,7 +107,7 @@ def _getFileList(self, dir): target = i if target != -1: - lines[target] = re.sub('^\S*', + lines[target] = re.sub(r'^\S*', '%.3f' % time.time(), lines[target]) f.seek(0) @@ -185,7 +185,7 @@ def _refresh(self): target = i if target != -1: - lines[target] = re.sub('^\S*', '%.3f' % time.time(), lines[target]) + lines[target] = re.sub(r'^\S*', '%.3f' % time.time(), lines[target]) f.seek(0) f.truncate(0) f.writelines(lines) @@ -433,7 +433,7 @@ def _buildCmd(self, dir, **kwargs): followlinks = "" if lfEval("g:Lf_ShowRelativePath") == '1': - strip = "| sed 's#^\./##'" + strip = r"| sed 's#^\./##'" else: strip = "" @@ -490,7 +490,7 @@ def _writeCache(self, content): return # update the time - lines[target] = re.sub('^\S*', + lines[target] = re.sub(r'^\S*', '%.3f' % time.time(), lines[target]) f.seek(0) @@ -545,7 +545,7 @@ def _getFilesFromCache(self): if target != -1: # already cached # update the time - lines[target] = re.sub('^\S*', + lines[target] = re.sub(r'^\S*', '%.3f' % time.time(), lines[target]) f.seek(0) @@ -580,6 +580,48 @@ def setContent(self, content): if lfEval("g:Lf_UseCache") == '1': self._writeCache(content) + def getContentFromMultiDirs(self, dirs, **kwargs): + no_ignore = kwargs.get("arguments", {}).get("--no-ignore") + if no_ignore != self._no_ignore: + self._no_ignore = no_ignore + arg_changes = True + else: + arg_changes = False + + dirs = { os.path.abspath(os.path.expanduser(lfDecode(dir.strip('"').rstrip('\\/')))) for dir in dirs } + if arg_changes or lfEval("g:Lf_UseMemoryCache") == '0' or dirs != self._cur_dir or \ + not self._content: + self._cur_dir = dirs + + cmd = '' + for dir in dirs: + if not os.path.exists(dir): + lfCmd("echoe ' Unknown directory `%s`'" % dir) + return None + + command = self._buildCmd(dir, **kwargs) + if command: + if cmd == '': + cmd = command + else: + cmd += ' && ' + command + + if cmd: + executor = AsyncExecutor() + self._executor.append(executor) + if cmd.split(None, 1)[0] == "dir": + content = executor.execute(cmd, format_line) + else: + if lfEval("get(g:, 'Lf_ShowDevIcons', 1)") == "1": + content = executor.execute(cmd, encoding=lfEval("&encoding"), format_line=format_line) + else: + content = executor.execute(cmd, encoding=lfEval("&encoding")) + self._cmd_start_time = time.time() + return content + + return self._content + + def getContent(self, *args, **kwargs): files = kwargs.get("arguments", {}).get("--file", []) if files: @@ -589,6 +631,9 @@ def getContent(self, *args, **kwargs): self._cmd_work_dir = "" directory = kwargs.get("arguments", {}).get("directory") + if directory and len(directory) > 1: + return self.getContentFromMultiDirs(directory, **kwargs) + if directory and directory[0] not in ['""', "''"]: dir = directory[0].strip('"').rstrip('\\/') if os.path.exists(os.path.expanduser(lfDecode(dir))): @@ -599,8 +644,7 @@ def getContent(self, *args, **kwargs): dir = os.path.abspath(os.path.expanduser(lfDecode(dir))) self._cmd_work_dir = dir else: - lfCmd("echohl ErrorMsg | redraw | echon " - "'Unknown directory `%s`' | echohl NONE" % dir) + lfCmd("echoe ' Unknown directory `%s`'" % dir) return None no_ignore = kwargs.get("arguments", {}).get("--no-ignore") @@ -699,31 +743,6 @@ def _createHelp(self): help.append('" ---------------------------------------------------------') return help - def _nearestAncestor(self, markers, path): - """ - return the nearest ancestor path(including itself) of `path` that contains - one of files or directories in `markers`. - `markers` is a list of file or directory names. - """ - if os.name == 'nt': - # e.g. C:\\ - root = os.path.splitdrive(os.path.abspath(path))[0] + os.sep - else: - root = '/' - - path = os.path.abspath(path) - while path != root: - for name in markers: - if os.path.exists(os.path.join(path, name)): - return path - path = os.path.abspath(os.path.join(path, "..")) - - for name in markers: - if os.path.exists(os.path.join(path, name)): - return path - - return "" - def _afterEnter(self): super(FileExplManager, self)._afterEnter() lfCmd("augroup Lf_File") @@ -780,14 +799,14 @@ def startExplorer(self, win_pos, *args, **kwargs): cur_buf_name = lfDecode(vim.current.buffer.name) fall_back = False if 'a' in mode: - working_dir = self._nearestAncestor(root_markers, self._orig_cwd) + working_dir = nearestAncestor(root_markers, self._orig_cwd) if working_dir: # there exists a root marker in nearest ancestor path chdir(working_dir) else: fall_back = True elif 'A' in mode: if cur_buf_name: - working_dir = self._nearestAncestor(root_markers, os.path.dirname(cur_buf_name)) + working_dir = nearestAncestor(root_markers, os.path.dirname(cur_buf_name)) else: working_dir = "" if working_dir: # there exists a root marker in nearest ancestor path @@ -809,6 +828,9 @@ def startExplorer(self, win_pos, *args, **kwargs): @removeDevIcons def _previewInPopup(self, *args, **kwargs): + if len(args) == 0 or args[0] == '': + return + line = args[0] if not os.path.isabs(line): if self._getExplorer()._cmd_work_dir: @@ -820,7 +842,9 @@ def _previewInPopup(self, *args, **kwargs): source = int(lfEval("bufadd('%s')" % escQuote(line))) else: source = line - self._createPopupPreview(line, source, 0) + + jump_cmd = 'silent! normal! g`"' + self._createPopupPreview(line, source, 0, jump_cmd) @removeDevIcons def _acceptSelection(self, *args, **kwargs): @@ -867,9 +891,19 @@ def _acceptSelection(self, *args, **kwargs): and not vim.current.buffer.options["modified"]): lfCmd("setlocal bufhidden=wipe") - lfCmd("hide edit %s" % escSpecial(file)) - except vim.error: # E37 - lfPrintTraceback() + m = lfEval("get(g:, 'Lf_FileActions', {})") + if m != {}: + try: + extension = os.path.splitext(file)[-1] + filecmd = m[extension] + lfCmd("%s %s" % (filecmd, escSpecial(file))) + except KeyError: + lfCmd("hide edit %s" % escSpecial(file)) + else: + lfCmd("hide edit %s" % escSpecial(file)) + except vim.error as e: # E37 + if 'E325' not in str(e).split(':'): + lfPrintTraceback() #***************************************************** # fileExplManager is a singleton diff --git a/autoload/leaderf/python/leaderf/functionExpl.py b/autoload/leaderf/python/leaderf/functionExpl.py index 2eb4527d..5c39a624 100644 --- a/autoload/leaderf/python/leaderf/functionExpl.py +++ b/autoload/leaderf/python/leaderf/functionExpl.py @@ -104,7 +104,8 @@ def _getFunctionList(self): yield itertools.chain(func_list, itertools.chain.from_iterable(exe_taglist)) def _getFunctionResult(self, buffer): - if not buffer.name or lfEval("bufloaded(%d)" % buffer.number) == '0': + if (not buffer.name or lfEval("bufloaded(%d)" % buffer.number) == '0' + or lfEval("&bt") != ''): return [] changedtick = int(lfEval("getbufvar(%d, 'changedtick')" % buffer.number)) # there is no change since last call @@ -127,7 +128,7 @@ def _getFunctionResult(self, buffer): tmp_file = tempfile.NamedTemporaryFile with tmp_file(mode='w+', suffix='_'+os.path.basename(buffer.name), delete=False) as f: - for line in buffer[:]: + for line in buffer: f.write(line + '\n') file_name = f.name # {tagname}{tagfile}{tagaddress};"{kind} @@ -207,7 +208,6 @@ def cleanup(self): class FunctionExplManager(Manager): def __init__(self): super(FunctionExplManager, self).__init__() - self._orig_line = '' def _getExplClass(self): return FunctionExplorer @@ -221,12 +221,12 @@ def _acceptSelection(self, *args, **kwargs): line = args[0] # {kind} {code} {file} {line} line = line.rsplit("\t", 1)[1][1:-1] # file:line buf_number - line_nr, buf_number = line.rsplit(":", 1)[1].split() + line_num, buf_number = line.rsplit(":", 1)[1].split() if kwargs.get("mode", '') == 't': buf_name = lfEval("bufname(%s)" % buf_number) - lfDrop('tab', buf_name, line_nr) + lfDrop('tab', buf_name, line_num) else: - lfCmd("hide buffer +%s %s" % (line_nr, buf_number)) + lfCmd("hide buffer +%s %s" % (line_num, buf_number)) lfCmd("norm! ^zv") lfCmd("norm! zz") @@ -289,42 +289,42 @@ def _afterEnter(self): lfCmd("autocmd VimLeavePre * call leaderf#Function#cleanup()") lfCmd("augroup END") if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcKind'', ''^\w'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcKind'', ''^\w'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcReturnType'', ''^\w\t\zs.\{-}\ze\s*[~]\=\w\+\W\{-}[(\[]'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcReturnType'', ''^\w\t\zs.\{-}\ze\s*[~]\=\(\w\|[#:]\)\+\W\{-}[(\[]'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcScope'', ''\w*\(<[^>]*>\)\=::'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcScope'', ''\w*\(<[^>]*>\)\=::'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcName'', ''^\w\t.\{-}\s*\zs[~]\=\w\+\W\{-}\ze[(\[]'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcName'', ''^\w\t.\{-}\s*\zs[~]\=\(\w\|[#:]\)\+\W\{-}\ze[(\[]'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcDirname'', ''\t\zs\[.*:\d\+ \d\+]$'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcDirname'', ''\t\zs\[.*:\d\+ \d\+]$'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcLineNum'', '':\zs\d\+\ze \d\+]$'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_funcLineNum'', '':\zs\d\+\ze \d\+]$'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) else: - id = int(lfEval('''matchadd('Lf_hl_funcKind', '^\w')''')) + id = int(lfEval(r'''matchadd('Lf_hl_funcKind', '^\w')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_funcReturnType', '^\w\t\zs.\{-}\ze\s*[~]\=\w\+\W\{-}[(\[]')''')) + id = int(lfEval(r'''matchadd('Lf_hl_funcReturnType', '^\w\t\zs.\{-}\ze\s*[~]\=\(\w\|[#:]\)\+\W\{-}[(\[]')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_funcScope', '\w*\(<[^>]*>\)\=::')''')) + id = int(lfEval(r'''matchadd('Lf_hl_funcScope', '\w*\(<[^>]*>\)\=::')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_funcName', '^\w\t.\{-}\s*\zs[~]\=\w\+\W\{-}\ze[(\[]')''')) + id = int(lfEval(r'''matchadd('Lf_hl_funcName', '^\w\t.\{-}\s*\zs[~]\=\(\w\|[#:]\)\+\W\{-}\ze[(\[]')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_funcDirname', '\t\zs\[.*:\d\+ \d\+]$')''')) + id = int(lfEval(r'''matchadd('Lf_hl_funcDirname', '\t\zs\[.*:\d\+ \d\+]$')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_funcLineNum', ':\zs\d\+\ze \d\+]$')''')) + id = int(lfEval(r'''matchadd('Lf_hl_funcLineNum', ':\zs\d\+\ze \d\+]$')''')) self._match_ids.append(id) def _beforeExit(self): @@ -343,36 +343,6 @@ def _supportsRefine(self): def removeCache(self, buf_number): self._getExplorer().removeCache(buf_number) - def _previewResult(self, preview): - if self._getInstance().getWinPos() == 'floatwin': - self._cli.buildPopupPrompt() - - if lfEval("get(g:, 'Lf_PreviewInPopup', 0)") == '1': - if self._orig_line != self._getInstance().currentLine: - self._closePreviewPopup() - else: - return - - if not self._needPreview(preview): - return - - line = self._getInstance().currentLine - if lfEval("get(g:, 'Lf_PreviewInPopup', 0)") == '1': - self._previewInPopup(line) - return - - orig_pos = self._getInstance().getOriginalPos() - cur_pos = (vim.current.tabpage, vim.current.window, vim.current.buffer) - - saved_eventignore = vim.options['eventignore'] - vim.options['eventignore'] = 'BufLeave,WinEnter,BufEnter' - try: - vim.current.tabpage, vim.current.window, vim.current.buffer = orig_pos - self._acceptSelection(line, preview=True) - finally: - vim.current.tabpage, vim.current.window, vim.current.buffer = cur_pos - vim.options['eventignore'] = saved_eventignore - def _bangEnter(self): super(FunctionExplManager, self)._bangEnter() if "--all" in self._arguments and not self._is_content_list: @@ -386,6 +356,7 @@ def _bangEnter(self): self._relocateCursor() def _bangReadFinished(self): + super(FunctionExplManager, self)._bangReadFinished() self._relocateCursor() def _relocateCursor(self): @@ -397,15 +368,15 @@ def _relocateCursor(self): inst = self._getInstance() if inst.empty(): return - orig_buf_nr = inst.getOriginalPos()[2].number + orig_buf_num = inst.getOriginalPos()[2].number orig_line = inst.getOriginalCursor()[0] tags = [] for index, line in enumerate(inst.buffer, 1): line = line.rsplit("\t", 1)[1][1:-1] - line_nr, buf_number = line.rsplit(":", 1)[1].split() - line_nr, buf_number = int(line_nr), int(buf_number) - if orig_buf_nr == buf_number: - tags.append((index, buf_number, line_nr)) + line_num, buf_number = line.rsplit(":", 1)[1].split() + line_num, buf_number = int(line_num), int(buf_number) + if orig_buf_num == buf_number: + tags.append((index, buf_number, line_num)) if self._getInstance().isReverseOrder(): tags = tags[::-1] @@ -428,21 +399,26 @@ def _relocateCursor(self): else: lfCmd("call leaderf#ResetPopupOptions(%d, 'filter', function('leaderf#NormalModeFilter', [%d]))" % (self._getInstance().getPopupWinId(), id(self))) + self._cli._buildPopupPrompt() else: lfCmd(str(index)) + if self._getInstance().getWinPos() == 'floatwin': + self._cli._buildPopupPrompt() lfCmd("norm! zz") + self._previewResult(False) + def _previewInPopup(self, *args, **kwargs): - if len(args) == 0: + if len(args) == 0 or args[0] == '': return line = args[0] # {kind} {code} {file} {line} line = line.rsplit("\t", 1)[1][1:-1] # file:line buf_number - line_nr, buf_number = line.rsplit(":", 1)[1].split() + line_num, buf_number = line.rsplit(":", 1)[1].split() buf_number = int(buf_number) - self._createPopupPreview("", buf_number, line_nr) + self._createPopupPreview("", buf_number, line_num) #***************************************************** diff --git a/autoload/leaderf/python/leaderf/gitExpl.py b/autoload/leaderf/python/leaderf/gitExpl.py new file mode 100644 index 00000000..1b9e246f --- /dev/null +++ b/autoload/leaderf/python/leaderf/gitExpl.py @@ -0,0 +1,4912 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import vim +import re +import os +import sys +import os.path +import json +import bisect +import ctypes +import tempfile +import itertools +from pathlib import PurePath +from difflib import SequenceMatcher +from itertools import islice +from functools import partial +from enum import Enum +from collections import OrderedDict +from datetime import datetime +from .utils import * +from .explorer import * +from .manager import * +from .diff import LfDiffer +from .devicons import ( + webDevIconsGetFileTypeSymbol, + matchaddDevIconsDefault, + matchaddDevIconsExact, + matchaddDevIconsExtension, +) + +def ensureWorkingDirectory(func): + @wraps(func) + def deco(self, *args, **kwargs): + try: + orig_cwd = lfGetCwd() + changed = False + if self._project_root != orig_cwd: + changed = True + lfChdir(self._project_root) + + return func(self, *args, **kwargs) + finally: + if changed: + lfChdir(orig_cwd) + + return deco + +def lfGetFilePath(source): + """ + source is a tuple like (b90f76fc1, bad07e644, R099, src/version.c, src/version2.c) + """ + return source[3] if source[4] == "" else source[4] + +#***************************************************** +# GitExplorer +#***************************************************** +class GitExplorer(Explorer): + def __init__(self): + self._executor = [] + self._show_icon = lfEval("get(g:, 'Lf_ShowDevIcons', 1)") == "1" + + def getContent(self, *args, **kwargs): + commands = lfEval("leaderf#Git#Commands()") + return [list(item)[0] for item in commands] + + def formatLine(self, line): + pass + + def getStlCategory(self): + return 'Git' + + def getStlCurDir(self): + return escQuote(lfEncode(lfGetCwd())) + + def supportsNameOnly(self): + return False + + def cleanup(self): + for exe in self._executor: + exe.killProcess() + self._executor = [] + + +class GitDiffExplorer(GitExplorer): + def __init__(self): + super(GitDiffExplorer, self).__init__() + self._source_info = {} + + def supportsNameOnly(self): + return True + + def getContent(self, *args, **kwargs): + arguments_dict = kwargs.get("arguments", {}) + + if "content" in arguments_dict: + return arguments_dict["content"] + + executor = AsyncExecutor() + self._executor.append(executor) + + self._source_info = {} + + cmd = "git diff --no-color --raw --no-abbrev" + if "--cached" in arguments_dict: + cmd += " --cached" + if "extra" in arguments_dict: + cmd += " " + " ".join(arguments_dict["extra"]) + content = executor.execute(cmd, encoding=lfEval("&encoding"), format_line=self.formatLine) + return content + + def formatLine(self, line): + """ + :000000 100644 000000000 5b01d33aa A runtime/syntax/json5.vim + :100644 100644 671b269c0 ef52cddf4 M runtime/syntax/nix.vim + :100644 100644 69671c59c 084f8cdb4 M runtime/syntax/zsh.vim + :100644 100644 b90f76fc1 bad07e644 R099 src/version.c src/version2.c + :100644 000000 b5825eb19 000000000 D src/testdir/dumps + + ':100644 100644 72943a1 dbee026 R050\thello world.txt\thello world2.txt' + """ + tmp = line.split(sep='\t') + file_names = (tmp[1], tmp[2] if len(tmp) == 3 else "") + blob_status = tmp[0].split() + self._source_info[file_names] = (blob_status[2], blob_status[3], blob_status[4], + file_names[0], file_names[1]) + icon = webDevIconsGetFileTypeSymbol(file_names[0]) if self._show_icon else "" + return "{:<4} {}{}{}".format(blob_status[4], icon, file_names[0], + "" if file_names[1] == "" else "\t=>\t" + file_names[1] ) + + def getStlCategory(self): + return 'Git_diff' + + def getSourceInfo(self): + return self._source_info + + +class GitLogExplorer(GitExplorer): + def __init__(self): + super(GitLogExplorer, self).__init__() + self.orig_name = {} + self.patches = {} + + def generateContent(self, content): + for line1, line2, _ in itertools.zip_longest(content, content, content): + commit_id = line1.split(None, 1)[0] + self.orig_name[commit_id] = line2 + yield line1 + + def generateContentPatches(self, content): + result = [] + commit = None + for line in content: + if line.startswith("$"): + result.append(line[1:]) + commit = line.split(None, 1)[0].lstrip("$") + self.patches[commit] = [] + else: + self.patches[commit].append(line) + + return result + + def getContent(self, *args, **kwargs): + self.orig_name.clear() + self.patches = {} + + arguments_dict = kwargs.get("arguments", {}) + + executor = AsyncExecutor() + self._executor.append(executor) + + options = GitLogExplorer.generateOptions(arguments_dict) + cmd = 'git log {} --pretty=format:"%h%d %s"'.format(options) + if "--current-file" in arguments_dict and "current_file" in arguments_dict: + cmd += " --name-only --follow -- {}".format(arguments_dict["current_file"]) + elif "--current-line" in arguments_dict and "current_file" in arguments_dict: + cmd = 'git log {} --pretty=format:"$%h%d %s"'.format(options) + cmd += " -L{},{}:{}".format(arguments_dict["current_line_num"], + arguments_dict["current_line_num"], + arguments_dict["current_file"]) + content = executor.execute(cmd, encoding=lfEval("&encoding")) + return self.generateContentPatches(content) + + if "extra" in arguments_dict: + cmd += " " + " ".join(arguments_dict["extra"]) + + content = executor.execute(cmd, encoding=lfEval("&encoding")) + + if "--current-file" in arguments_dict and "current_file" in arguments_dict: + return AsyncExecutor.Result(self.generateContent(content)) + + return content + + def getStlCategory(self): + return 'Git_log' + + @staticmethod + def generateOptions(arguments_dict): + options = "" + if "-n" in arguments_dict: + options += "-n %s " % arguments_dict["-n"][0] + + if "--skip" in arguments_dict: + options += "--skip %s " % arguments_dict["--skip"][0] + + if "--since" in arguments_dict: + options += "--since %s " % arguments_dict["--since"][0] + + if "--until" in arguments_dict: + options += "--until %s " % arguments_dict["--until"][0] + + if "--author" in arguments_dict: + options += "--author %s " % arguments_dict["--author"][0] + + if "--committer" in arguments_dict: + options += "--committer %s " % arguments_dict["--committer"][0] + + if "--no-merges" in arguments_dict: + options += "--no-merges " + + if "--all" in arguments_dict: + options += "--all " + + if "--graph" in arguments_dict: + options += "--graph " + + if "--reverse-order" in arguments_dict: + options += "--reverse " + + return options + + +class GitCommand(object): + def __init__(self, arguments_dict, source): + self._arguments = arguments_dict + self._source = source + self._cmd = "" + self._file_type = "" + self._file_type_cmd = "" + self._buffer_name = "" + self.buildCommandAndBufferName() + + def buildCommandAndBufferName(self): + pass + + def getCommand(self): + return self._cmd + + def getFileType(self): + return self._file_type + + def getFileTypeCommand(self): + return self._file_type_cmd + + def getBufferName(self): + return self._buffer_name + + def getArguments(self): + return self._arguments + + def getSource(self): + return self._source + + def getTitle(self): + return None + + +class GitDiffCommand(GitCommand): + def __init__(self, arguments_dict, source): + """ + source is a tuple like (b90f76fc1, bad07e644, R099, src/version.c, src/version2.c) + """ + super(GitDiffCommand, self).__init__(arguments_dict, source) + + def buildCommandAndBufferName(self): + self._cmd = "git diff --no-color" + extra_options = "" + if "--cached" in self._arguments: + extra_options += " --cached" + + if "extra" in self._arguments: + extra_options += " " + " ".join(self._arguments["extra"]) + + if self._source is not None: + if " -- " not in self._arguments["arg_line"]: + file_name = lfGetFilePath(self._source) + if " " in file_name: + file_name = file_name.replace(' ', r'\ ') + extra_options += " -- {}".format(file_name) + elif "--current-file" in self._arguments and "current_file" in self._arguments: + extra_options += " -- {}".format(self._arguments["current_file"]) + + self._cmd += extra_options + self._buffer_name = "LeaderF://git diff" + extra_options + self._file_type = "diff" + if lfEval("has('nvim')") == '1': + self._file_type_cmd = "setlocal filetype=diff" + else: + self._file_type_cmd = "silent! doautocmd filetypedetect BufNewFile *.diff" + + +class GitLogDiffCommand(GitCommand): + def __init__(self, arguments_dict, source): + """ + source is a tuple like (b90f76fc1, bad07e644, R099, src/version.c, src/version2.c) + """ + super(GitLogDiffCommand, self).__init__(arguments_dict, source) + + def buildCommandAndBufferName(self): + # fuzzy search in navigation panel + if not self._arguments["parent"].startswith("0000000"): + self._cmd = "git diff --follow --no-color {}..{} -- {}".format(self._arguments["parent"], + self._arguments["commit_id"], + lfGetFilePath(self._source) + ) + else: + self._cmd = "git show --pretty= --no-color {} -- {}".format(self._arguments["commit_id"], + lfGetFilePath(self._source) + ) + self._buffer_name = "LeaderF://" + self._cmd + self._file_type = "diff" + if lfEval("has('nvim')") == '1': + self._file_type_cmd = "setlocal filetype=diff" + else: + self._file_type_cmd = "silent! doautocmd filetypedetect BufNewFile *.diff" + + +class GitCatFileCommand(GitCommand): + def __init__(self, arguments_dict, source, commit_id): + """ + source is a tuple like (b90f76fc1, R099, src/version.c) + """ + self._commit_id = commit_id + super(GitCatFileCommand, self).__init__(arguments_dict, source) + + @staticmethod + def buildBufferName(commit_id, source): + """ + source is a tuple like (b90f76fc1, R099, src/version.c) + """ + if source[1].startswith("C"): + return "{}:{}:{}:{}".format(commit_id[:7], source[0][:9], "C", source[2]) + + return "{}:{}:{}".format(commit_id[:7], source[0][:9], source[2]) + + def buildCommandAndBufferName(self): + self._cmd = "git cat-file -p {}".format(self._source[0]) + if self._source[0].startswith("0000000"): + if self._source[1] == "M": + if os.name == 'nt': + self._cmd = "type {}".format(os.path.normpath(self._source[2])) + else: + self._cmd = "cat {}".format(self._source[2]) + else: + self._cmd = "" + + self._buffer_name = GitCatFileCommand.buildBufferName(self._commit_id, self._source) + self._file_type_cmd = "silent! doautocmd filetypedetect BufNewFile {}".format(self._source[2]) + + +class GitLogCommand(GitCommand): + def __init__(self, arguments_dict, source): + """ + source is a commit id + """ + super(GitLogCommand, self).__init__(arguments_dict, source) + + def buildCommandAndBufferName(self): + if "--directly" in self._arguments: + options = GitLogExplorer.generateOptions(self._arguments) + self._cmd = "git log {}".format(options) + + if "extra" in self._arguments: + self._cmd += " " + " ".join(self._arguments["extra"]) + + if "--current-file" in self._arguments and "current_file" in self._arguments: + self._cmd += " --follow -- {}".format(self._arguments["current_file"]) + + self._buffer_name = "LeaderF://" + self._cmd + elif "--current-line" in self._arguments and "current_file" in self._arguments: + self._buffer_name = "LeaderF://" + self._source + else: + sep = ' ' if os.name == 'nt' else '' + if "--find-copies-harder" in self._arguments: + find_copies_harder = " -C" + else: + find_copies_harder = "" + + self._cmd = ('git show {} -C{} --pretty=format:"commit %H%nparent %P%n' + 'Author: %an <%ae>%nAuthorDate: %ad%nCommitter: %cn <%ce>%nCommitDate:' + ' %cd{}%n%n%s%n%n%b%n%x2d%x2d%x2d" --stat=70 --stat-graph-width=10 --no-color' + ' && git log -1 -p --pretty=format:"%x20" --no-color {}' + ).format(self._source, find_copies_harder, sep, self._source) + + if (("--recall" in self._arguments or "--current-file" in self._arguments) + and "current_file" in self._arguments): + self._cmd = ('git show {} -C{} --pretty=format:"commit %H%nparent %P%n' + 'Author: %an <%ae>%nAuthorDate: %ad%nCommitter: %cn <%ce>%nCommitDate:' + ' %cd{}%n%n%s%n%n%b%n%x2d%x2d%x2d" --stat=70 --stat-graph-width=10 --no-color' + ' && git log -1 -p --follow --pretty=format:"%x20" --no-color {} -- {}' + ).format(self._source, find_copies_harder, sep, self._source, + self._arguments["orig_name"].get(self._source, + self._arguments["current_file"])) + + self._buffer_name = "LeaderF://" + self._source + + self._file_type = "git" + self._file_type_cmd = "setlocal filetype=git" + + +class GitDiffExplCommand(GitCommand): + def __init__(self, arguments_dict, source): + super(GitDiffExplCommand, self).__init__(arguments_dict, source) + + def buildCommandAndBufferName(self): + self._cmd = 'git diff --raw -C --numstat --shortstat --no-abbrev' + extra_options = "" + if "--cached" in self._arguments: + extra_options += " --cached" + + if "extra" in self._arguments: + extra_options += " " + " ".join(self._arguments["extra"]) + + self._cmd += extra_options + + self._buffer_name = "LeaderF://navigation/" + self._source + self._file_type_cmd = "" + + +class GitStagedCommand(GitCommand): + def __init__(self, arguments_dict, source): + super(GitStagedCommand, self).__init__(arguments_dict, source) + + def buildCommandAndBufferName(self): + self._cmd = 'git diff --cached --raw -C --numstat --shortstat --no-abbrev' + extra_options = "" + + if "extra" in self._arguments: + extra_options += " " + " ".join(self._arguments["extra"]) + + self._cmd += extra_options + + self._buffer_name = "LeaderF://navigation/" + self._source + self._file_type_cmd = "" + + def getTitle(self): + return "Staged Changes:" + + +class GitUnstagedCommand(GitCommand): + def __init__(self, arguments_dict, source): + super(GitUnstagedCommand, self).__init__(arguments_dict, source) + + def buildCommandAndBufferName(self): + self._cmd = 'git diff --raw -C --numstat --shortstat --no-abbrev' + extra_options = "" + + if "extra" in self._arguments: + extra_options += " " + " ".join(self._arguments["extra"]) + + self._cmd += extra_options + + self._buffer_name = "LeaderF://navigation/" + self._source + self._file_type_cmd = "" + + def getTitle(self): + return "Unstaged Changes:" + + +class GitLogExplCommand(GitCommand): + def __init__(self, arguments_dict, source): + """ + source is a commit id + """ + super(GitLogExplCommand, self).__init__(arguments_dict, source) + + def buildCommandAndBufferName(self): + if "--find-copies-harder" in self._arguments: + find_copies_harder = " -C" + else: + find_copies_harder = "" + + self._cmd = ('git show -m --raw -C{} --numstat --shortstat ' + '--pretty=format:"# %P" --no-abbrev {}').format(find_copies_harder, + self._source) + + self._buffer_name = "LeaderF://navigation/" + self._source + self._file_type_cmd = "" + + +class GitBlameCommand(GitCommand): + def __init__(self, arguments_dict, commit_id): + super(GitBlameCommand, self).__init__(arguments_dict, commit_id) + + @staticmethod + def buildCommand(arguments_dict, commit_id, file_name, use_contents=False): + extra_options = "" + if "-c" in arguments_dict: + extra_options += " -c" + + if "-w" in arguments_dict: + extra_options += " -w" + + if "--date" in arguments_dict: + extra_options += " --date={}".format(arguments_dict["--date"][0]) + + if use_contents and "--contents" in arguments_dict: + extra_options += " --contents {}".format(arguments_dict["--contents"][0]) + + return "git blame -f -n {} {} -- {}".format(extra_options, commit_id, file_name) + + def buildCommandAndBufferName(self): + commit_id = "" + if self._source is not None: + commit_id = self._source + + file_name = vim.current.buffer.name + if " " in file_name: + file_name = file_name.replace(' ', r'\ ') + file_name = PurePath(lfRelpath(file_name)).as_posix() + + self._cmd = GitBlameCommand.buildCommand(self._arguments, commit_id, file_name, True) + self._buffer_name = "LeaderF://git blame {} {}".format(commit_id, file_name) + self._file_type = "" + self._file_type_cmd = "" + + +class GitShowCommand(GitCommand): + def __init__(self, arguments_dict, commit_id, file_name): + self._commit_id = commit_id + self._file_name = file_name + super(GitShowCommand, self).__init__(arguments_dict, None) + + def buildCommandAndBufferName(self): + self._cmd = "git log -1 -p --follow {} -- {}".format(self._commit_id, self._file_name) + self._file_type = "git" + self._file_type_cmd = "setlocal filetype=git" + + +class GitCustomizeCommand(GitCommand): + def __init__(self, arguments_dict, cmd, buf_name, file_type, file_type_cmd): + super(GitCustomizeCommand, self).__init__(arguments_dict, None) + self._cmd = cmd + self._buffer_name = buf_name + self._file_type = file_type + self._file_type_cmd = file_type_cmd + + +class ParallelExecutor(object): + @staticmethod + def run(*cmds, format_line=None, directory=None, silent=False, error=None): + outputs = [[] for _ in range(len(cmds))] + stop_thread = False + + def readContent(content, output): + try: + for line in content: + output.append(line) + if stop_thread: + break + except Exception as e: + if silent == False: + traceback.print_exc() + traceback.print_stack() + elif isinstance(error, list): + error.append(str(e)) + + + executors = [AsyncExecutor() for _ in range(len(cmds))] + workers = [] + for i, (exe, cmd) in enumerate(zip(executors, cmds)): + if isinstance(format_line, list): + format_line_cb = format_line[i] + else: + format_line_cb = format_line + content = exe.execute(cmd, + encoding=lfEval("&encoding"), + format_line=format_line_cb, + cwd=directory) + worker = threading.Thread(target=readContent, args=(content, outputs[i])) + worker.daemon = True + worker.start() + workers.append(worker) + + for w in workers: + w.join(5) # I think 5s is enough for git cat-file + + stop_thread = True + + for e in executors: + e.killProcess() + + return outputs + + +class GitCommandView(object): + def __init__(self, owner, cmd): + self._owner = owner + self._cmd = cmd + self._executor = AsyncExecutor() + self._buffer = None + self._window_id = -1 + self._bufhidden = 'wipe' + self._format_line = None + self.init() + owner.register(self) + + def init(self): + self._content = [] + self._timer_id = None + self._reader_thread = None + self._offset_in_content = 0 + self._read_finished = 0 + self._stop_reader_thread = False + + def getBufferName(self): + return self._cmd.getBufferName() + + def getBufferNum(self): + if self._buffer is None: + return -1 + else: + return self._buffer.number + + def getWindowId(self): + self._window_id = int(lfEval("bufwinid({})".format(self._buffer.number))) + # window not exist in current tabpage + if self._window_id == -1: + ids = lfEval("win_findbuf({})".format(self._buffer.number)) + if len(ids) > 0: + self._window_id = int(ids[0]) + + return self._window_id + + def setWindowId(self, winid): + self._window_id = winid + + def getContent(self): + return self._content + + def setContent(self, content): + try: + self._buffer.options['modifiable'] = True + self._buffer[:] = content + finally: + self._buffer.options['modifiable'] = False + + def getSource(self): + return self._cmd.getSource() + + def start(self): + # start a timer and thread + self._timer_id = lfEval("timer_start(100, function('leaderf#Git#WriteBuffer', [%d]), {'repeat': -1})" % id(self)) + + self._reader_thread = threading.Thread(target=self._readContent, args=(lfEval("&encoding"),)) + self._reader_thread.daemon = True + self._reader_thread.start() + + def setOptions(self, winid, bufhidden): + lfCmd("call win_execute({}, 'setlocal nobuflisted')".format(winid)) + lfCmd("call win_execute({}, 'setlocal buftype=nofile')".format(winid)) + lfCmd("call win_execute({}, 'setlocal bufhidden={}')".format(winid, bufhidden)) + lfCmd("call win_execute({}, 'setlocal undolevels=-1')".format(winid)) + lfCmd("call win_execute({}, 'setlocal noswapfile')".format(winid)) + lfCmd("call win_execute({}, 'setlocal nospell')".format(winid)) + lfCmd("call win_execute({}, 'setlocal nomodifiable')".format(winid)) + if lfEval("getbufvar(winbufnr(%d), '&ft')" % winid) != self._cmd.getFileType(): + lfCmd("silent! call win_execute({}, '{}')".format(winid, self._cmd.getFileTypeCommand())) + + def defineMaps(self, winid): + pass + + def enableColor(self, winid): + pass + + def create(self, winid, bufhidden='wipe', buf_content=None, format_line=None): + self._bufhidden = bufhidden + self._format_line = format_line + + if self._buffer is not None: + self.cleanup() + lfCmd("call win_gotoid({})".format(self.getWindowId())) + + self.init() + + if self._buffer is None: + self.defineMaps(winid) + self.setOptions(winid, bufhidden) + lfCmd("augroup Lf_Git | augroup END") + lfCmd("call win_execute({}, 'autocmd! Lf_Git BufWipeout call leaderf#Git#Suicide({})')" + .format(winid, id(self))) + lfCmd("call win_execute({}, 'autocmd! Lf_Git BufHidden call leaderf#Git#Bufhidden({})')" + .format(winid, id(self))) + + self._buffer = vim.buffers[int(lfEval("winbufnr({})".format(winid)))] + self._window_id = winid + + self.enableColor(self.getWindowId()) + + if buf_content is not None: + # cache the content if buf_content is the result of ParallelExecutor.run() + self._content = buf_content + self._owner.readFinished(self) + + self._read_finished = 2 + + self._buffer.options['modifiable'] = True + self._buffer[:] = buf_content + self._buffer.options['modifiable'] = False + + self._owner.writeFinished(self.getWindowId()) + + return + + if self._cmd.getCommand() == "": + self._read_finished = 2 + self._owner.writeFinished(self.getWindowId()) + return + + self.start() + + def writeBuffer(self): + if self._read_finished == 2: + return + + if not self._buffer.valid: + self.stopTimer() + return + + self._buffer.options['modifiable'] = True + try: + cur_len = len(self._content) + if cur_len > self._offset_in_content: + if self._offset_in_content == 0: + self._buffer[:] = self._content[:cur_len] + else: + self._buffer.append(self._content[self._offset_in_content:cur_len]) + + self._offset_in_content = cur_len + lfCmd("redraw") + finally: + self._buffer.options['modifiable'] = False + + if self._read_finished == 1 and self._offset_in_content == len(self._content): + self._read_finished = 2 + self._owner.writeFinished(self.getWindowId()) + self.stopTimer() + + def _readContent(self, encoding): + try: + content = self._executor.execute(self._cmd.getCommand(), + encoding=encoding, + format_line=self._format_line, + cwd=self._owner.getProjectRoot() + ) + for line in content: + self._content.append(line) + if self._stop_reader_thread: + break + else: + self._read_finished = 1 + self._owner.readFinished(self) + except Exception: + traceback.print_exc() + traceback.print_stack() + self._read_finished = 1 + + def stopThread(self): + if self._reader_thread and self._reader_thread.is_alive(): + self._stop_reader_thread = True + self._reader_thread.join(0.01) + + def stopTimer(self): + if self._timer_id is not None: + lfCmd("call timer_stop(%s)" % self._timer_id) + self._timer_id = None + + def cleanup(self, wipe=True): + self.stopTimer() + self.stopThread() + # must do this at last + self._executor.killProcess() + + if self._bufhidden == "hide" and wipe == True and self._buffer.valid: + lfCmd("noautocmd bwipe! {}".format(self._buffer.number)) + + def suicide(self): + self._owner.deregister(self) + + def bufHidden(self): + self._owner.bufHidden(self) + + def valid(self): + return self._buffer is not None and self._buffer.valid + + +class GitBlameView(GitCommandView): + def __init__(self, owner, cmd): + super(GitBlameView, self).__init__(owner, cmd) + self._alternate_winid = None + self._alternate_buffer_num = None + self._alternate_win_options = {} + self._color_table = { + '0': '#000000', '1': '#800000', '2': '#008000', '3': '#808000', + '4': '#000080', '5': '#800080', '6': '#008080', '7': '#c0c0c0', + '8': '#808080', '9': '#ff0000', '10': '#00ff00', '11': '#ffff00', + '12': '#0000ff', '13': '#ff00ff', '14': '#00ffff', '15': '#ffffff', + '16': '#000000', '17': '#00005f', '18': '#000087', '19': '#0000af', + '20': '#0000d7', '21': '#0000ff', '22': '#005f00', '23': '#005f5f', + '24': '#005f87', '25': '#005faf', '26': '#005fd7', '27': '#005fff', + '28': '#008700', '29': '#00875f', '30': '#008787', '31': '#0087af', + '32': '#0087d7', '33': '#0087ff', '34': '#00af00', '35': '#00af5f', + '36': '#00af87', '37': '#00afaf', '38': '#00afd7', '39': '#00afff', + '40': '#00d700', '41': '#00d75f', '42': '#00d787', '43': '#00d7af', + '44': '#00d7d7', '45': '#00d7ff', '46': '#00ff00', '47': '#00ff5f', + '48': '#00ff87', '49': '#00ffaf', '50': '#00ffd7', '51': '#00ffff', + '52': '#5f0000', '53': '#5f005f', '54': '#5f0087', '55': '#5f00af', + '56': '#5f00d7', '57': '#5f00ff', '58': '#5f5f00', '59': '#5f5f5f', + '60': '#5f5f87', '61': '#5f5faf', '62': '#5f5fd7', '63': '#5f5fff', + '64': '#5f8700', '65': '#5f875f', '66': '#5f8787', '67': '#5f87af', + '68': '#5f87d7', '69': '#5f87ff', '70': '#5faf00', '71': '#5faf5f', + '72': '#5faf87', '73': '#5fafaf', '74': '#5fafd7', '75': '#5fafff', + '76': '#5fd700', '77': '#5fd75f', '78': '#5fd787', '79': '#5fd7af', + '80': '#5fd7d7', '81': '#5fd7ff', '82': '#5fff00', '83': '#5fff5f', + '84': '#5fff87', '85': '#5fffaf', '86': '#5fffd7', '87': '#5fffff', + '88': '#870000', '89': '#87005f', '90': '#870087', '91': '#8700af', + '92': '#8700d7', '93': '#8700ff', '94': '#875f00', '95': '#875f5f', + '96': '#875f87', '97': '#875faf', '98': '#875fd7', '99': '#875fff', + '100': '#878700', '101': '#87875f', '102': '#878787', '103': '#8787af', + '104': '#8787d7', '105': '#8787ff', '106': '#87af00', '107': '#87af5f', + '108': '#87af87', '109': '#87afaf', '110': '#87afd7', '111': '#87afff', + '112': '#87d700', '113': '#87d75f', '114': '#87d787', '115': '#87d7af', + '116': '#87d7d7', '117': '#87d7ff', '118': '#87ff00', '119': '#87ff5f', + '120': '#87ff87', '121': '#87ffaf', '122': '#87ffd7', '123': '#87ffff', + '124': '#af0000', '125': '#af005f', '126': '#af0087', '127': '#af00af', + '128': '#af00d7', '129': '#af00ff', '130': '#af5f00', '131': '#af5f5f', + '132': '#af5f87', '133': '#af5faf', '134': '#af5fd7', '135': '#af5fff', + '136': '#af8700', '137': '#af875f', '138': '#af8787', '139': '#af87af', + '140': '#af87d7', '141': '#af87ff', '142': '#afaf00', '143': '#afaf5f', + '144': '#afaf87', '145': '#afafaf', '146': '#afafd7', '147': '#afafff', + '148': '#afd700', '149': '#afd75f', '150': '#afd787', '151': '#afd7af', + '152': '#afd7d7', '153': '#afd7ff', '154': '#afff00', '155': '#afff5f', + '156': '#afff87', '157': '#afffaf', '158': '#afffd7', '159': '#afffff', + '160': '#d70000', '161': '#d7005f', '162': '#d70087', '163': '#d700af', + '164': '#d700d7', '165': '#d700ff', '166': '#d75f00', '167': '#d75f5f', + '168': '#d75f87', '169': '#d75faf', '170': '#d75fd7', '171': '#d75fff', + '172': '#d78700', '173': '#d7875f', '174': '#d78787', '175': '#d787af', + '176': '#d787d7', '177': '#d787ff', '178': '#d7af00', '179': '#d7af5f', + '180': '#d7af87', '181': '#d7afaf', '182': '#d7afd7', '183': '#d7afff', + '184': '#d7d700', '185': '#d7d75f', '186': '#d7d787', '187': '#d7d7af', + '188': '#d7d7d7', '189': '#d7d7ff', '190': '#d7ff00', '191': '#d7ff5f', + '192': '#d7ff87', '193': '#d7ffaf', '194': '#d7ffd7', '195': '#d7ffff', + '196': '#ff0000', '197': '#ff005f', '198': '#ff0087', '199': '#ff00af', + '200': '#ff00d7', '201': '#ff00ff', '202': '#ff5f00', '203': '#ff5f5f', + '204': '#ff5f87', '205': '#ff5faf', '206': '#ff5fd7', '207': '#ff5fff', + '208': '#ff8700', '209': '#ff875f', '210': '#ff8787', '211': '#ff87af', + '212': '#ff87d7', '213': '#ff87ff', '214': '#ffaf00', '215': '#ffaf5f', + '216': '#ffaf87', '217': '#ffafaf', '218': '#ffafd7', '219': '#ffafff', + '220': '#ffd700', '221': '#ffd75f', '222': '#ffd787', '223': '#ffd7af', + '224': '#ffd7d7', '225': '#ffd7ff', '226': '#ffff00', '227': '#ffff5f', + '228': '#ffff87', '229': '#ffffaf', '230': '#ffffd7', '231': '#ffffff', + '232': '#080808', '233': '#121212', '234': '#1c1c1c', '235': '#262626', + '236': '#303030', '237': '#3a3a3a', '238': '#444444', '239': '#4e4e4e', + '240': '#585858', '241': '#626262', '242': '#6c6c6c', '243': '#767676', + '244': '#808080', '245': '#8a8a8a', '246': '#949494', '247': '#9e9e9e', + '248': '#a8a8a8', '249': '#b2b2b2', '250': '#bcbcbc', '251': '#c6c6c6', + '252': '#d0d0d0', '253': '#dadada', '254': '#e4e4e4', '255': '#eeeeee' + } + + self._heat_colors = { + "dark": ['160', '196', '202', '208', '214', '220', '226', '190', '154', '118', '46', + '47', '48', '49', '50', '51', '45', '39', '33', '27', '21', '244', '242', '240' + ], + "light": ['160', '196', '202', '208', '214', '178', '142', '106', '70', '34', '35', + '36', '37', '38', '39', '33', '32', '27', '26', '21', '232' + ] + } + + self._date_dict = {} + self._heat_seconds = [] + + self.blame_stack = [] + # key is commit id, value is (blame_buffer, alternate_buffer_num) + self.blame_dict = {} + # key is alternate buffer name, value is (blame_buffer, alternate_buffer_num) + self.blame_buffer_dict = {} + + def setOptions(self, winid, bufhidden): + super(GitBlameView, self).setOptions(winid, bufhidden) + lfCmd("call win_execute({}, 'setlocal nowrap')".format(winid)) + lfCmd("call win_execute({}, 'setlocal winfixwidth')".format(winid)) + lfCmd("call win_execute({}, 'setlocal foldcolumn=0')".format(winid)) + lfCmd("call win_execute({}, 'setlocal number norelativenumber')".format(winid)) + lfCmd("call win_execute({}, 'setlocal nofoldenable')".format(winid)) + lfCmd("call win_execute({}, 'setlocal signcolumn=no')".format(winid)) + lfCmd("call win_execute({}, 'setlocal cursorline')".format(winid)) + lfCmd("call win_execute({}, 'setlocal scrolloff=0')".format(winid)) + + def saveAlternateWinOptions(self, winid, buffer_num): + self._alternate_winid = winid + self._alternate_buffer_num = buffer_num + + self._alternate_win_options = { + "foldenable": lfEval("getwinvar({}, '&foldenable')".format(winid)), + "scrollbind": lfEval("getwinvar({}, '&scrollbind')".format(winid)), + } + + def getAlternateWinid(self): + return self._alternate_winid + + def enableColor(self, winid): + if (lfEval("hlexists('Lf_hl_blame_255')") == '0' + or lfEval("exists('*hlget')") == '0' + or lfEval("hlget('Lf_hl_blame_255')[0]").get("cleared", False)): + for cterm_color, gui_color in self._color_table.items(): + lfCmd("hi def Lf_hl_blame_{} guifg={} guibg=NONE gui=NONE ctermfg={} ctermbg=NONE cterm=NONE" + .format(cterm_color, gui_color, cterm_color)) + + stl = "" + for i, cterm_color in enumerate(self._heat_colors[lfEval("&bg")]): + lfCmd("hi def Lf_hl_blame_heat_{} guifg={} guibg=NONE gui=NONE ctermfg={} ctermbg=NONE cterm=NONE" + .format(i, self._color_table[cterm_color], cterm_color)) + lfCmd("call leaderf#colorscheme#popup#link_two('Lf_hl_blame_stl_heat_{}', 'StatusLine', 'Lf_hl_blame_heat_{}', 1)" + .format(i, i)) + stl = "%#Lf_hl_blame_stl_heat_{}#>".format(i) + stl + + lfCmd("let g:Lf_GitStlHeatLine = '{}'".format(stl)) + + if lfEval("hlexists('Lf_hl_gitBlameDate')") == '0': + lfCmd("call leaderf#colorscheme#popup#load('{}', '{}')" + .format("git", lfEval("get(g:, 'Lf_PopupColorscheme', 'default')"))) + + lfCmd(r"""call win_execute(%d, 'syn match WarningMsg /\/')""" % winid) + + def highlightCommitId(self, commit_id): + n = int(commit_id.lstrip('^')[:2], 16) + lfCmd(r"syn match Lf_hl_blame_{} /^\^\?{}\x\+/".format(n, commit_id[:2])) + + def suicide(self): + super(GitBlameView, self).suicide() + + line_num = vim.current.window.cursor[0] + top_line = lfEval("line('w0')") + if len(self.blame_stack) > 0: + line_num = self.blame_stack[0][3] + top_line = self.blame_stack[0][4] + + lfCmd("call win_execute({}, 'buffer {} | norm! {}Gzt{}G0')".format(self._alternate_winid, + self._alternate_buffer_num, + top_line, + line_num)) + + if self._alternate_winid is not None: + for k, v in self._alternate_win_options.items(): + lfCmd("call setwinvar({}, '&{}', {})".format(self._alternate_winid, k, v)) + + for item in self.blame_dict.values(): + buffer_num = int(item[1]) + # buftype is not empty + if (lfEval("bufexists({})".format(buffer_num)) == "1" + and vim.buffers[buffer_num].options["buftype"]): + lfCmd("bwipe {}".format(buffer_num)) + self.blame_dict = {} + self.blame_stack = [] + self.blame_buffer_dict = {} + + def _helper(self, date_format): + if date_format == "iso": + # 6817817e (Yggdroot 2014-02-26 00:37:26 +0800) 1 autoload/leaderf/manager.py + pattern = r"\b\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [+-]\d{4}\b" + format_str = "%Y-%m-%d %H:%M:%S %z" + to_timestamp = lambda date: int(datetime.strptime(date, format_str).timestamp()) + elif date_format == "iso-strict": + # 6817817e (Yggdroot 2014-02-26T00:37:26+08:00) 1 autoload/leaderf/manager.py + pattern = r"\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}\b" + to_timestamp = lambda date: int(datetime.fromisoformat(date).timestamp()) + elif date_format == "short": + # 6817817e (Yggdroot 2014-02-26) 1 autoload/leaderf/manager.py + pattern = r"\b\d{4}-\d{2}-\d{2}\b" + to_timestamp = lambda date: int(datetime.fromisoformat(date).timestamp()) + else: + lfPrintError("Error. date_format = {}".format(date_format)) + + return (pattern, to_timestamp) + + def highlightHeatDate1(self, date_format, blame_list): + pattern, to_timestamp = self._helper(date_format) + self._date_dict = {} + for line in blame_list: + commit_id, rest = line.split(None, 1) + if commit_id not in self._date_dict: + self.highlightCommitId(commit_id) + match = re.search(pattern, rest) + if match: + date = match.group(0) + timestamp = to_timestamp(date) + else: + lfPrintError("Error. pattern '{}' can not be found in '{}'" + .format(pattern, rest)) + + self._date_dict[commit_id] = (date, timestamp) + + self._highlightHeatDate() + + def highlightHeatDate2(self, normal_blame_list, unix_blame_list): + """ + normal_blame_list: + ["6817817e\t( Yggdroot\t10 years ago \t1)#!/usr/bin/env python", + ... + ] + + unix_blame_list: + ["6817817e\t( Yggdroot\t1393346246\t1)#!/usr/bin/env python", + ... + ] + """ + self._date_dict = {} + for i, line in enumerate(normal_blame_list): + commit_id, rest = line.split('\t', 1) + if commit_id not in self._date_dict: + self.highlightCommitId(commit_id) + date = rest.split('\t')[1].strip() + timestamp = int(unix_blame_list[i].split('\t')[2]) + self._date_dict[commit_id] = (date, timestamp) + + self._highlightHeatDate() + + def _highlightHeatDate(self): + color_num = len(self._heat_colors[lfEval("&bg")]) + current_time = int(time.time()) + heat_seconds = sorted((current_time - timestamp + for date, timestamp in self._date_dict.values())) + heat_seconds_len = len(heat_seconds) + if heat_seconds_len > color_num: + step, remainder = divmod(heat_seconds_len, color_num) + if step > 0: + tmp = heat_seconds[step - 1 : heat_seconds_len - remainder : step] + if remainder > 0: + tmp[-1] = heat_seconds[-1] + heat_seconds = tmp + + self._heat_seconds = heat_seconds + self._highlight(current_time, self._date_dict) + + def highlightRestHeatDate1(self, date_format, blame_list): + pattern, to_timestamp = self._helper(date_format) + date_dict = {} + for line in blame_list: + commit_id, rest = line.split(None, 1) + if commit_id not in self._date_dict: + self.highlightCommitId(commit_id) + match = re.search(pattern, rest) + if match: + date = match.group(0) + timestamp = to_timestamp(date) + else: + lfPrintError("Error. pattern '{}' can not be found in '{}'" + .format(pattern, rest)) + + date_dict[commit_id] = (date, timestamp) + self._date_dict[commit_id] = date_dict[commit_id] + + current_time = int(time.time()) + self._highlight(current_time, date_dict) + + def highlightRestHeatDate2(self, normal_blame_list, unix_blame_list): + """ + normal_blame_list: + ["6817817e\t( Yggdroot\t10 years ago \t1)#!/usr/bin/env python", + ... + ] + + unix_blame_list: + ["6817817e\t( Yggdroot\t1393346246\t1)#!/usr/bin/env python", + ... + ] + """ + date_dict = {} + for i, line in enumerate(normal_blame_list): + commit_id, rest = line.split('\t', 1) + if commit_id not in self._date_dict: + self.highlightCommitId(commit_id) + date = rest.split('\t')[1].strip() + timestamp = int(unix_blame_list[i].split('\t')[2]) + date_dict[commit_id] = (date, timestamp) + self._date_dict[commit_id] = date_dict[commit_id] + + current_time = int(time.time()) + self._highlight(current_time, date_dict) + + def _highlight(self, current_time, date_dict): + date_set = set() + for date, timestamp in date_dict.values(): + if date not in date_set: + date_set.add(date) + index = Bisect.bisect_left(self._heat_seconds, current_time - timestamp) + lfCmd(r"syn match Lf_hl_blame_heat_{} /\<{}\>/".format(index, date)) + + def clearHeatSyntax(self): + for i in range(len(self._heat_seconds)): + lfCmd("silent! syn clear Lf_hl_blame_heat_{}".format(i)) + + +class LfOrderedDict(OrderedDict): + def last_key(self): + return next(reversed(self.keys())) + + def last_value(self): + return next(reversed(self.values())) + + def last_key_value(self): + return next(reversed(self.items())) + + def first_key(self): + return next(iter(self.keys())) + + def first_value(self): + return next(iter(self.values())) + + def first_key_value(self): + return next(iter(self.items())) + + +class FolderStatus(Enum): + CLOSED = 0 + OPEN = 1 + + +class TreeNode(object): + def __init__(self, status=FolderStatus.OPEN): + self.status = status + # key is the directory name, value is a TreeNode + self.dirs = LfOrderedDict() + # key is the file name, + # value is a tuple like (b90f76fc1, bad07e644, R099, src/version.c, src/version2.c) + self.files = LfOrderedDict() + + +class MetaInfo(object): + def __init__(self, level, is_dir, name, info, path): + """ + info is TreeNode if is_dir is true or source otherwise. + """ + self.level = level + self.is_dir = is_dir + self.name = name + self.info = info + self.path = path + self.has_num_stat = False + + +class KeyWrapper(object): + def __init__(self, iterable, key): + self._list = iterable + self._key = key + + def __getitem__(self, i): + if self._key is None: + return self._list[i] + + return self._key(self._list[i]) + + def __len__(self): + return len(self._list) + + +class Bisect(object): + @staticmethod + def bisect_left(a, x, lo=0, hi=None, *, key=None): + if hi is None: + hi = len(a) + + if sys.version_info >= (3, 10): + pos = bisect.bisect_left(a, x, lo, hi, key=key) + else: + pos = bisect.bisect_left(KeyWrapper(a, key), x, lo, hi) + return pos + + @staticmethod + def bisect_right(a, x, lo=0, hi=None, *, key=None): + if hi is None: + hi = len(a) + + if sys.version_info >= (3, 10): + pos = bisect.bisect_right(a, x, lo, hi, key=key) + else: + pos = bisect.bisect_right(KeyWrapper(a, key), x, lo, hi) + return pos + + +class TreeView(GitCommandView): + def __init__(self, owner, cmd, project_root, target_path, callback, next_tree_view=None): + super(TreeView, self).__init__(owner, cmd) + self._project_root = project_root + self._target_path = target_path + # the argument is source, source is a tuple like + # (b90f76fc1, bad07e644, R099, src/version.c, src/version2.c) + self._callback = callback + self._next_tree_view = next_tree_view + # key is the parent hash, value is a TreeNode + self._trees = LfOrderedDict() + # key is the parent hash, value is a list of MetaInfo + self._file_structures = {} + # to protect self._file_structures + self._lock = threading.Lock() + self._file_list = {} + self._cur_parent = None + self._short_stat = {} + self._num_stat = {} + self._first_source = {} + self._show_icon = lfEval("get(g:, 'Lf_ShowDevIcons', 1)") == "1" + folder_icons = lfEval("g:Lf_GitFolderIcons") + self._closed_folder_icon = folder_icons["closed"] + self._open_folder_icon = folder_icons["open"] + self._preopen_num = int(lfEval("get(g:, 'Lf_GitPreopenNum', 100)")) + self._add_icon = lfEval("get(g:, 'Lf_GitAddIcon', '')") #   + self._copy_icon = lfEval("get(g:, 'Lf_GitCopyIcon', '')") + self._del_icon = lfEval("get(g:, 'Lf_GitDelIcon', '')") #   + self._modification_icon = lfEval("get(g:, 'Lf_GitModifyIcon', '')") + self._rename_icon = lfEval("get(g:, 'Lf_GitRenameIcon', '')") + self._status_icons = { + "A": self._add_icon, + "C": self._copy_icon, + "D": self._del_icon, + "M": self._modification_icon, + "R": self._rename_icon, + } + self._match_ids = [] + self._init = False + + def startLine(self): + return self._owner.startLine(self) + + def enableColor(self, winid): + if lfEval("hlexists('Lf_hl_help')") == '0': + lfCmd("call leaderf#colorscheme#popup#load('{}', '{}')" + .format("git", lfEval("get(g:, 'Lf_PopupColorscheme', 'default')"))) + + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitHelp'', ''^".*'', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitFolder'', ''\S*[/\\]'', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitTitle'', ''.*:'', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitFilesNum'', ''(\d\+)'', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitFolderIcon'', ''^\s*\zs[{}{}]'', -100)')""" + .format(winid, self._closed_folder_icon, self._open_folder_icon)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitAddIcon'', ''^\s*\zs{}'', -100)')""" + .format(winid, self._add_icon)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitCopyIcon'', ''^\s*\zs{}'', -100)')""" + .format(winid, self._copy_icon)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitDelIcon'', ''^\s*\zs{}'', -100)')""" + .format(winid, self._del_icon)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitModifyIcon'', ''^\s*\zs{}'', -100)')""" + .format(winid, self._modification_icon)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitRenameIcon'', ''^\s*\zs{}'', -100)')""" + .format(winid, self._rename_icon)) + id = int(lfEval("matchid")) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitRenameIcon'', '' \zs=>\ze '', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitNumStatAdd'', ''\t\zs+\d\+'', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitNumStatDel'', ''\t+\d\+\s\+\zs-\d\+'', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitNumStatBinary'', ''\t\zs(Bin)'', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Identifier'', '''', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitSelectedOption'', ''\S\+ ◉\@='', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitDiffAddition'', ''\(\S\+ \)\@<=◉'', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitNonSelectedOption'', ''\S\+ ○\@='', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitDiffDeletion'', ''\(\S\+ \)\@<=○'', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitSelectedOption'', ''\( \)\@<=Ignore Whitespace 🗹\@='', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitDiffAddition'', ''\( Ignore Whitespace \)\@<=🗹 '', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitNonSelectedOption'', ''\( \)\@<=Ignore Whitespace 🗷\@='', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_gitDiffDeletion'', ''\( Ignore Whitespace \)\@<=🗷 '', -100)')""" + .format(winid)) + id = int(lfEval("matchid")) + self._match_ids.append(id) + + def defineMaps(self, winid): + lfCmd("call win_execute({}, 'call leaderf#Git#TreeViewMaps({})')" + .format(winid, id(self))) + + def getCurrentParent(self): + return self._cur_parent + + def getFileList(self): + return self._file_list.get(self._cur_parent, []) + + @staticmethod + def generateSource(line): + """ + :000000 100644 000000000 5b01d33aa A runtime/syntax/json5.vim + :100644 100644 671b269c0 ef52cddf4 M runtime/syntax/nix.vim + :100644 100644 69671c59c 084f8cdb4 M runtime/syntax/zsh.vim + :100644 100644 b90f76fc1 bad07e644 R099 src/version.c src/version2.c + :100644 000000 b5825eb19 000000000 D src/testdir/dumps + + ':100644 100644 72943a1 dbee026 R050\thello world.txt\thello world2.txt' + + return a tuple like (100644, (b90f76fc1, bad07e644, R099, src/version.c, src/version2.c)) + (100644, (69671c59c, 084f8cdb4, M, runtime/syntax/zsh.vim, "")) + """ + tmp = line.split(sep='\t') + file_names = (tmp[1], tmp[2] if len(tmp) == 3 else "") + blob_status = tmp[0].split() + return (blob_status[1], + (blob_status[2], blob_status[3], blob_status[4], + file_names[0], file_names[1]) + ) + + def buildFileStructure(self, parent, level, name, tree_node, path): + if len(tree_node.dirs) == 1 and len(tree_node.files) == 0: + if tree_node.status == FolderStatus.CLOSED: + self._file_structures[parent].append( + MetaInfo(level, True, name, tree_node, path) + ) + else: + dir_name, node = tree_node.dirs.last_key_value() + self.buildFileStructure(parent, level, "{}/{}".format(name, dir_name), + node, "{}{}/".format(path, dir_name) + ) + else: + self._file_structures[parent].append( + MetaInfo(level, True, name, tree_node, path) + ) + + if tree_node.status == FolderStatus.OPEN: + for dir_name, node in tree_node.dirs.items(): + self.buildFileStructure(parent, level + 1, dir_name, node, + "{}{}/".format(path, dir_name)) + + self.appendFiles(parent, level + 1, tree_node) + + def appendRemainingFiles(self, parent, tree_node): + if len(tree_node.dirs) == 0: + return + + dir_name, node = tree_node.dirs.last_key_value() + if len(node.dirs) > 1: + if node.status == FolderStatus.OPEN: + child_dir_name, child_node = node.dirs.last_key_value() + self.buildFileStructure(parent, 1, child_dir_name, child_node, + "{}/{}/".format(dir_name, child_dir_name)) + + self.appendFiles(parent, 1, node) + else: + self.buildFileStructure(parent, 0, dir_name, node, dir_name + "/") + + def appendFiles(self, parent, level, tree_node): + for k, v in tree_node.files.items(): + self._file_structures[parent].append( + MetaInfo(level, False, k, v, lfGetFilePath(v)) + ) + + def getLeftMostFile(self, tree_node): + for node in tree_node.dirs.values(): + result = self.getLeftMostFile(node) + if result is not None: + return result + + for i in tree_node.files.values(): + return i + + return None + + def buildTree(self, line): + """ + command output is something as follows: + + # 9d0ccb54c743424109751a82a742984699e365fe 63aa0c07bcd16ddac52d5275b9513712b780bc25 + :100644 100644 0cbabf4 d641678 M src/a.txt + 2 0 src/a.txt + 1 file changed, 2 insertions(+) + + # 9d0ccb54c743424109751a82a742984699e365fe 63aa0c07bcd16ddac52d5275b9513712b780bc25 + :100644 100644 acc5824 d641678 M src/a.txt + 3 0 src/a.txt + 1 file changed, 3 insertions(+) + """ + if line.startswith("#"): + size = len(self._trees) + parents = line.split() + if len(parents) == 1: # first commit + parent = "0000000" + else: + parent = parents[size + 1] + if self._cur_parent is None: + self._cur_parent = parent + self._trees[parent] = TreeNode() + self._file_structures[parent] = [] + self._file_list[parent] = [] + elif line.startswith(":"): + if self._cur_parent is None: + parent = "0000000" + self._cur_parent = parent + self._trees[parent] = TreeNode() + self._file_structures[parent] = [] + self._file_list[parent] = [] + + parent, tree_node = self._trees.last_key_value() + root_node = tree_node + mode, source = TreeView.generateSource(line) + if source[2] == "U": + return + file_path = lfGetFilePath(source) + icon = webDevIconsGetFileTypeSymbol(file_path) if self._show_icon else "" + self._file_list[parent].append("{:<4} {}{}{}" + .format(source[2], icon, source[3], + "" if source[4] == "" + else "\t=>\t" + source[4]) + ) + if mode == "160000": # gitlink + directories = file_path.split("/") + else: + *directories, file = file_path.split("/") + with self._lock: + for i, d in enumerate(directories, 0): + if i == 0: + level0_dir_name = d + + if d not in tree_node.dirs: + # not first directory + if len(tree_node.dirs) > 0: + if i == 1: + if len(tree_node.dirs) == 1: + self._file_structures[parent].append( + MetaInfo(0, True, level0_dir_name, + tree_node, level0_dir_name + "/") + ) + + if tree_node.status == FolderStatus.OPEN: + dir_name, node = tree_node.dirs.last_key_value() + self.buildFileStructure(parent, 1, dir_name, node, + "{}/{}/".format(level0_dir_name, + dir_name) + ) + elif i == 0: + self.appendRemainingFiles(parent, tree_node) + + if len(self._file_structures[parent]) >= self._preopen_num: + status = FolderStatus.CLOSED + else: + status = FolderStatus.OPEN + tree_node.dirs[d] = TreeNode(status) + + tree_node = tree_node.dirs[d] + + if self._target_path == file_path: + node = root_node + node.status = FolderStatus.OPEN + for d in directories: + node = node.dirs[d] + node.status = FolderStatus.OPEN + + if mode != "160000": + tree_node.files[file] = source + elif line.startswith(" "): + parent, tree_node = self._trees.last_key_value() + self._short_stat[parent] = line + self.appendRemainingFiles(parent, tree_node) + self.appendFiles(parent, 0, tree_node) + elif line == "": + pass + else: + parent = self._trees.last_key() + if parent not in self._num_stat: + self._num_stat[parent] = {} + + #'3\t1\tarch/{i386 => x86}/Makefile' + added, deleted, pathname = line.split("\t") + if "=>" in pathname: + if "{" in pathname: + pathname = re.sub(r'{.*?=> (.*?)}', r'\1', pathname) + else: + pathname = pathname.split(" => ")[1] + if added == "-" and deleted == "-": + self._num_stat[parent][pathname] = "(Bin)" + else: + self._num_stat[parent][pathname] = "+{:3} -{}".format(added, deleted) + + def metaInfoGenerator(self, meta_info, recursive, level): + meta_info.info.status = FolderStatus.OPEN + + tree_node = meta_info.info + if len(tree_node.dirs) == 1 and len(tree_node.files) == 0 and level != -1: + node = tree_node + while len(node.dirs) == 1 and len(node.files) == 0: + dir_name, node = node.dirs.last_key_value() + meta_info.name = "{}/{}".format(meta_info.name, dir_name) + meta_info.path = "{}{}/".format(meta_info.path, dir_name) + meta_info.info = node + if level == 0: + node.status = FolderStatus.OPEN + + if recursive == True or node.status == FolderStatus.OPEN: + yield from self.metaInfoGenerator(meta_info, recursive, level + 1) + + return + + for dir_name, node in tree_node.dirs.items(): + cur_path = "{}{}/".format(meta_info.path, dir_name) + info = MetaInfo(meta_info.level + 1, True, dir_name, node, cur_path) + yield info + if recursive == True or node.status == FolderStatus.OPEN: + yield from self.metaInfoGenerator(info, recursive, level + 1) + + for k, v in tree_node.files.items(): + yield MetaInfo(meta_info.level + 1, False, k, v, lfGetFilePath(v)) + + def expandOrCollapseFolder(self, recursive=False): + with self._lock: + if len(self._file_structures) == 0: + return None + + line_num = int(lfEval("getcurpos({})[1]".format(self.getWindowId()))) + index = line_num - self.startLine() + # the root + if index == -1 and recursive == True: + self.expandRoot(line_num) + return None + + structure = self._file_structures[self._cur_parent] + if index < 0 or index >= len(structure): + return None + + meta_info = structure[index] + if meta_info.is_dir: + if meta_info.info.status == FolderStatus.CLOSED: + self.expandFolder(line_num, index, meta_info, recursive) + elif recursive == True: + self.collapseFolder(line_num, index, meta_info, recursive) + self.expandFolder(line_num, index, meta_info, recursive) + else: + self.collapseFolder(line_num, index, meta_info, recursive) + return None + else: + return meta_info.info + + def collapseChildren(self): + with self._lock: + line_num = vim.current.window.cursor[0] + index = line_num - self.startLine() + structure = self._file_structures[self._cur_parent] + if index < -1 or index >= len(structure): + return + + # the root + if index == -1: + level = -1 + else: + meta_info = structure[index] + if not meta_info.is_dir: + return + + level = meta_info.level + + index += 1 + line_num += 1 + while index < len(structure) and structure[index].level > level and structure[index].is_dir: + if structure[index].info.status == FolderStatus.OPEN: + self.collapseFolder(line_num, index, structure[index], False) + index += 1 + line_num += 1 + + def expandRoot(self, line_num): + meta_info = MetaInfo(-1, True, "", self._trees[self._cur_parent], "") + orig_len = len(self._file_structures[self._cur_parent]) + self._file_structures[self._cur_parent] = list(self.metaInfoGenerator(meta_info, True, -1)) + self._buffer.options['modifiable'] = True + structure = self._file_structures[self._cur_parent] + try: + increment = len(structure) + self._buffer[line_num : line_num+orig_len] = [self.buildLine(info) for info in structure] + self._offset_in_content = increment + finally: + self._buffer.options['modifiable'] = False + + return increment + + def expandFolder(self, line_num, index, meta_info, recursive): + structure = self._file_structures[self._cur_parent] + size = len(structure) + structure[index + 1 : index + 1] = self.metaInfoGenerator(meta_info, recursive, 0) + self._buffer.options['modifiable'] = True + try: + increment = len(structure) - size + if index >= 0: + self._buffer[line_num - 1] = self.buildLine(structure[index]) + self._buffer.append([self.buildLine(info) + for info in structure[index + 1 : index + 1 + increment]], + line_num) + self._offset_in_content += increment + finally: + self._buffer.options['modifiable'] = False + + return increment + + def collapseFolder(self, line_num, index, meta_info, recursive): + meta_info.info.status = FolderStatus.CLOSED + # # Should all the status be set as CLOSED ? + # # No. + # if "/" in meta_info.name: + # prefix = meta_info.path[:len(meta_info.path) - len(meta_info.name) - 2] + # tree_node = self._trees[self._cur_parent] + # for d in prefix.split("/"): + # tree_node = tree_node.dirs[d] + + # for d in meta_info.name.split("/"): + # tree_node = tree_node.dirs[d] + # tree_node.status = FolderStatus.CLOSED + + structure = self._file_structures[self._cur_parent] + cur_node = meta_info.info + children_num = len(cur_node.dirs) + len(cur_node.files) + if (index + children_num + 1 == len(structure) + or not structure[index + children_num + 1].path.startswith(meta_info.path)): + decrement = children_num + else: + pos = Bisect.bisect_right(structure, False, lo=index + children_num + 1, + key=lambda info: not info.path.startswith(meta_info.path)) + decrement = pos - 1 - index + + del structure[index + 1 : index + 1 + decrement] + self._buffer.options['modifiable'] = True + try: + self._buffer[line_num - 1] = self.buildLine(structure[index]) + del self._buffer[line_num:line_num + decrement] + self._offset_in_content -= decrement + finally: + self._buffer.options['modifiable'] = False + + def inFileStructure(self, path): + *directories, file = path.split("/") + tree_node = self._trees[self._cur_parent] + for d in directories: + if d not in tree_node.dirs: + return False + tree_node = tree_node.dirs[d] + + return file in tree_node.files + + def locateFile(self, path): + with self._lock: + self._locateFile(PurePath(lfRelpath(path)).as_posix()) + + @staticmethod + def getDirName(path): + if path.endswith("/"): + return path + else: + path = os.path.dirname(path) + if path != "": + path += "/" + return path + + def _locateFile(self, path): + def getKey(info): + if info.path == path: + return 0 + else: + info_path_dir = TreeView.getDirName(info.path) + path_dir = TreeView.getDirName(path) + if ((info.path > path + and not (info_path_dir.startswith(path_dir) and info_path_dir != path_dir) + ) + or + (info.path < path and info.is_dir == False + and (path_dir.startswith(info_path_dir) and info_path_dir != path_dir) + ) + ): + return 1 + else: + return -1 + + structure = self._file_structures[self._cur_parent] + index = Bisect.bisect_left(structure, 0, key=getKey) + if index < len(structure) and structure[index].path == path: + lfCmd("call win_execute({}, 'norm! {}G0zz')" + .format(self.getWindowId(), index + self.startLine())) + else: + if not self.inFileStructure(path): + lfPrintError("File can't be found!") + return + + meta_info = structure[index-1] + prefix_len = len(meta_info.path) + tree_node = meta_info.info + *directories, file = path[prefix_len:].split("/") + node = tree_node + node.status = FolderStatus.OPEN + for d in directories: + node = node.dirs[d] + node.status = FolderStatus.OPEN + + line_num = index + self.startLine() - 1 + increment = self.expandFolder(line_num, index - 1, meta_info, False) + + index = Bisect.bisect_left(structure, 0, index, index + increment, key=getKey) + if index < len(structure) and structure[index].path == path: + lfCmd("call win_execute({}, 'norm! {}G0zz')" + .format(self.getWindowId(), index + self.startLine())) + else: + lfPrintError("BUG: File can't be found!") + + def buildLine(self, meta_info): + if meta_info.is_dir: + if meta_info.info.status == FolderStatus.CLOSED: + icon = self._closed_folder_icon + else: + icon = self._open_folder_icon + return "{}{} {}/".format(" " * meta_info.level, icon, meta_info.name) + else: + num_stat = self._num_stat.get(self._cur_parent, {}).get(meta_info.path, "") + if num_stat != "": + meta_info.has_num_stat = True + + icon = self._status_icons.get(meta_info.info[2][0], self._modification_icon) + + orig_name = "" + if meta_info.info[2][0] in ("R", "C"): + orig_name = "{} => ".format(PurePath(lfRelpath(meta_info.info[3], + os.path.dirname(meta_info.info[4]))).as_posix()) + + return "{}{} {}{}\t{}".format(" " * meta_info.level, + icon, + orig_name, + meta_info.name, + num_stat + ) + + def setOptions(self, winid, bufhidden): + super(TreeView, self).setOptions(winid, bufhidden) + if self._cmd.getTitle() is None: + lfCmd(r"""call win_execute({}, 'let &l:stl="%#Lf_hl_gitStlChangedNum# 0 %#Lf_hl_gitStlFileChanged#file changed, %#Lf_hl_gitStlAdd#0 (+), %#Lf_hl_gitStlDel#0 (-)"')""" + .format(winid)) + if lfEval("has('nvim')") == '1': + lfCmd("call nvim_win_set_option(%d, 'number', v:false)" % winid) + else: + lfCmd("call win_execute({}, 'setlocal nonumber')".format(winid)) + lfCmd("call win_execute({}, 'noautocmd setlocal sw=2 tabstop=4')".format(winid)) + lfCmd("call win_execute({}, 'setlocal signcolumn=no')".format(winid)) + lfCmd("call win_execute({}, 'setlocal foldmethod=indent')".format(winid)) + lfCmd("call win_execute({}, 'setlocal foldcolumn=1')".format(winid)) + lfCmd("call win_execute({}, 'setlocal conceallevel=0')".format(winid)) + lfCmd("call win_execute({}, 'setlocal winfixwidth')".format(winid)) + lfCmd("call win_execute({}, 'setlocal winfixheight')".format(winid)) + try: + lfCmd(r"call win_execute({}, 'setlocal list lcs=leadmultispace:¦\ ,tab:\ \ ')" + .format(winid)) + except vim.error: + lfCmd("call win_execute({}, 'setlocal nolist')".format(winid)) + lfCmd("augroup Lf_Git_Colorscheme | augroup END") + lfCmd("autocmd Lf_Git_Colorscheme ColorScheme * call leaderf#colorscheme#popup#load('Git', '{}')" + .format(lfEval("get(g:, 'Lf_PopupColorscheme', 'default')"))) + + def initBuffer(self): + if self._init == True: + return + + self._init = True + + self._buffer.append('') + title = self._cmd.getTitle() + if title is not None: + self._buffer.append(title) + + self._buffer.append(shrinkUser(self._project_root) + os.sep) + + def getCommand(self): + return self._cmd + + def getTitleHeight(self): + if self._cmd.getTitle() is None: + return 0 + else: + return 1 + + def getHeight(self): + if len(self._file_structures) == 0: + return 0 + + return len(self._file_structures[self._cur_parent]) + + def refreshNumStat(self): + self._buffer.options['modifiable'] = True + try: + init_line = self.startLine() - 1 + structure = self._file_structures[self._cur_parent] + for i, info in enumerate(structure, init_line): + if info.has_num_stat == True: + return + if not info.is_dir: + self._buffer[i] = self.buildLine(info) + finally: + self._buffer.options['modifiable'] = False + + def writeBuffer(self): + if self._cur_parent is None: + if self._read_finished == 1: + self.stopTimer() + if self._next_tree_view is not None: + self._next_tree_view() + return + + if self._read_finished == 2: + return + + if not self._buffer.valid: + self.stopTimer() + return + + with self._lock: + self._buffer.options['modifiable'] = True + try: + structure = self._file_structures[self._cur_parent] + cur_len = len(structure) + if cur_len > 0: + self.initBuffer() + + if cur_len > self._offset_in_content: + init_line = self.startLine() - 1 + cursor_line = init_line + + source = None + for info in structure[self._offset_in_content:cur_len]: + self._buffer.append(self.buildLine(info)) + if cursor_line == init_line and not info.is_dir: + if self._target_path is None or info.path == self._target_path: + cursor_line = len(self._buffer) + source = info.info + + if source is not None and self._callback(source, tree_view_id=id(self)) == True: + if lfEval("has('nvim')") == '1': + lfCmd("call nvim_win_set_option({}, 'cursorline', v:true)" + .format(self.getWindowId())) + else: + lfCmd("call win_execute({}, 'setlocal cursorline')" + .format(self.getWindowId())) + lfCmd("call win_execute({}, 'norm! {}G0zz')" + .format(self.getWindowId(), cursor_line)) + + if self._target_path is None: + lfCmd("call win_gotoid({})".format(self.getWindowId())) + + self._offset_in_content = cur_len + lfCmd("redraw") + finally: + self._buffer.options['modifiable'] = False + + if self._read_finished == 1 and self._offset_in_content == len(structure): + self.refreshNumStat() + if self._cmd.getTitle() is None: + shortstat = re.sub(r"( \d+)( files? changed)", + r"%#Lf_hl_gitStlChangedNum#\1%#Lf_hl_gitStlFileChanged#\2", + self._short_stat[self._cur_parent]) + shortstat = re.sub(r"(\d+) insertions?", r"%#Lf_hl_gitStlAdd#\1 ",shortstat) + shortstat = re.sub(r"(\d+) deletions?", r"%#Lf_hl_gitStlDel#\1 ", shortstat) + lfCmd(r"""call win_execute({}, 'let &l:stl="{}"')""" + .format(self.getWindowId(), shortstat)) + else: + num = self._short_stat[self._cur_parent].split(None, 1)[0] + self._setChangedFilesNum(num) + self._read_finished = 2 + self._owner.writeFinished(self.getWindowId()) + self.stopTimer() + if self._next_tree_view is not None: + self._next_tree_view() + + def _setChangedFilesNum(self, num): + self._buffer.options['modifiable'] = True + try: + title = self._buffer[self.startLine() - 3] + self._buffer[self.startLine() - 3] = title.replace(':', ' ({}):'.format(num)) + finally: + self._buffer.options['modifiable'] = False + + def _readContent(self, encoding): + try: + content = self._executor.execute(self._cmd.getCommand(), + encoding=encoding, + cwd=self._project_root + ) + for line in content: + self.buildTree(line) + if self._stop_reader_thread: + break + else: + self._read_finished = 1 + self._owner.readFinished(self) + except Exception: + traceback.print_exc() + traceback.print_stack() + self._read_finished = 1 + + def cleanup(self): + super(TreeView, self).cleanup() + self._match_ids = [] + + +class Panel(object): + def __init__(self): + self._project_root = None + + def getProjectRoot(self): + return self._project_root + + def register(self, view): + pass + + def deregister(self, view): + pass + + def bufHidden(self, view): + pass + + def cleanup(self): + pass + + def writeBuffer(self): + pass + + def readFinished(self, view): + pass + + def writeFinished(self, winid): + pass + + +class ResultPanel(Panel): + def __init__(self): + super(ResultPanel, self).__init__() + self._views = {} + self._sources = set() + + def register(self, view): + self._views[view.getBufferName()] = view + self._sources.add(view.getSource()) + + def deregister(self, view): + name = view.getBufferName() + if name in self._views: + self._sources.discard(self._views[name].getSource()) + self._views[name].cleanup() + del self._views[name] + + def getSources(self): + return self._sources + + def _createWindow(self, win_pos, buffer_name): + if win_pos == 'tab': + lfCmd("silent! keepa keepj hide edit {}".format(buffer_name)) + elif win_pos == 'top': + lfCmd("silent! noa keepa keepj abo sp {}".format(buffer_name)) + elif win_pos == 'bottom': + lfCmd("silent! noa keepa keepj bel sp {}".format(buffer_name)) + elif win_pos == 'left': + lfCmd("silent! noa keepa keepj abo vsp {}".format(buffer_name)) + elif win_pos == 'right': + lfCmd("silent! noa keepa keepj bel vsp {}".format(buffer_name)) + else: + lfCmd("silent! keepa keepj hide edit {}".format(buffer_name)) + + return int(lfEval("win_getid()")) + + def create(self, cmd, content=None): + buffer_name = cmd.getBufferName() + if buffer_name in self._views and self._views[buffer_name].valid(): + self._views[buffer_name].create(-1, buf_content=content) + else: + arguments = cmd.getArguments() + if arguments.get("mode") == 't': + win_pos = 'tab' + else: + win_pos = arguments.get("--position", ["top"])[0] + winid = self._createWindow(win_pos, buffer_name) + GitCommandView(self, cmd).create(winid, buf_content=content) + if cmd.getFileType() in ("diff", "git"): + key_map = lfEval("g:Lf_GitKeyMap") + lfCmd("call win_execute({}, 'nnoremap {} :call leaderf#Git#PreviousChange(1)')" + .format(winid, key_map["previous_change"])) + lfCmd("call win_execute({}, 'nnoremap {} :call leaderf#Git#NextChange(1)')" + .format(winid, key_map["next_change"])) + lfCmd("call win_execute({}, 'nnoremap {} :call leaderf#Git#EditFile(1)')" + .format(winid, key_map["edit_file"])) + + def writeBuffer(self): + for v in self._views.values(): + v.writeBuffer() + + +class PreviewPanel(Panel): + def __init__(self): + super(PreviewPanel, self).__init__() + self._view = None + self._buffer_contents = {} + self._preview_winid = 0 + + def register(self, view): + if self._view is not None: + self._view.cleanup() + self._view = view + + def deregister(self, view): + if self._view is view: + self._view.cleanup() + self._view = None + + def create(self, cmd, config, buf_content=None, project_root=None): + self._project_root = project_root + if lfEval("has('nvim')") == '1': + lfCmd("noautocmd let scratch_buffer = nvim_create_buf(0, 1)") + self._preview_winid = int(lfEval("nvim_open_win(scratch_buffer, 0, %s)" + % json.dumps(config))) + else: + lfCmd("noautocmd silent! let winid = popup_create([], %s)" % json.dumps(config)) + self._preview_winid = int(lfEval("winid")) + + GitCommandView(self, cmd).create(self._preview_winid, buf_content=buf_content) + + def createView(self, cmd): + if self._preview_winid > 0: + GitCommandView(self, cmd).create(self._preview_winid) + + def writeBuffer(self): + if self._view is not None: + self._view.writeBuffer() + + def getPreviewWinId(self): + return self._preview_winid + + def cleanup(self): + if self._view is not None: + # may never run here + self._view.cleanup() + self._view = None + self._buffer_contents = {} + self._preview_winid = 0 + + def readFinished(self, view): + self._buffer_contents[view.getSource()] = view.getContent() + + def getContent(self, source): + return self._buffer_contents.get(source) + + def setContent(self, content): + if self._view: + self._view.setContent(content) + + def getViewContent(self): + if self._view: + return self._view.getContent() + + return [] + + +class DiffViewPanel(Panel): + def __init__(self, bufhidden_callback=None, commit_id=""): + super(DiffViewPanel, self).__init__() + self._commit_id = commit_id + self._views = {} + self._hidden_views = {} + # key is current tabpage + self._buffer_names = {} + self._bufhidden_cb = bufhidden_callback + + def setCommitId(self, commit_id): + self._commit_id = commit_id + + def register(self, view): + self._views[view.getBufferName()] = view + + def deregister(self, view): + # :bw + name = view.getBufferName() + if name in self._views: + self._views[name].cleanup(wipe=False) + del self._views[name] + + if name in self._hidden_views: + self._hidden_views[name].cleanup(wipe=False) + del self._hidden_views[name] + + def bufHidden(self, view): + name = view.getBufferName() + if name in self._views: + del self._views[name] + self._hidden_views[name] = view + lfCmd("call win_execute({}, 'diffoff')".format(view.getWindowId())) + + if self._bufhidden_cb is not None: + self._bufhidden_cb() + + def bufShown(self, buffer_name, winid): + view = self._hidden_views[buffer_name] + view.setWindowId(winid) + del self._hidden_views[buffer_name] + self._views[buffer_name] = view + lfCmd("call win_execute({}, 'diffthis')".format(winid)) + + def cleanup(self): + for view in self._hidden_views.values(): + view.cleanup() + self._hidden_views = {} + + self._buffer_names = {} + + def writeFinished(self, winid): + lfCmd("call win_execute({}, 'diffthis')".format(winid)) + + def getValidWinIDs(self, win_ids, win_pos): + if win_ids == [-1, -1]: + if win_pos in ["top", "left"]: + lfCmd("wincmd w") + else: + lfCmd("wincmd W") + lfCmd("leftabove new") + win_ids[1] = int(lfEval("win_getid()")) + lfCmd("noautocmd leftabove vertical new") + win_ids[0] = int(lfEval("win_getid()")) + elif win_ids[0] == -1: + lfCmd("call win_gotoid({})".format(win_ids[1])) + lfCmd("noautocmd leftabove vertical new") + win_ids[0] = int(lfEval("win_getid()")) + elif win_ids[1] == -1: + lfCmd("call win_gotoid({})".format(win_ids[0])) + lfCmd("noautocmd rightbelow vertical new") + win_ids[1] = int(lfEval("win_getid()")) + + return win_ids + + def isAllHidden(self): + return len(self._views) == 0 + + def create(self, arguments_dict, source, **kwargs): + """ + source is a tuple like (b90f76fc1, bad07e644, R099, src/version.c, src/version2.c) + """ + self._project_root = kwargs.get("project_root", None) + file_path = lfGetFilePath(source) + sources = ((source[0], source[2], source[3]), + (source[1], source[2], file_path)) + buffer_names = (GitCatFileCommand.buildBufferName(self._commit_id, sources[0]), + GitCatFileCommand.buildBufferName(self._commit_id, sources[1])) + target_winid = None + if buffer_names[0] in self._views and buffer_names[1] in self._views: + win_ids = (self._views[buffer_names[0]].getWindowId(), + self._views[buffer_names[1]].getWindowId()) + lfCmd("call win_gotoid({})".format(win_ids[1])) + target_winid = win_ids[1] + elif buffer_names[0] in self._views: + lfCmd("call win_gotoid({})".format(self._views[buffer_names[0]].getWindowId())) + cmd = GitCatFileCommand(arguments_dict, sources[1], self._commit_id) + lfCmd("rightbelow vsp {}".format(cmd.getBufferName())) + if buffer_names[1] in self._hidden_views: + self.bufShown(buffer_names[1], int(lfEval("win_getid()"))) + else: + GitCommandView(self, cmd).create(int(lfEval("win_getid()")), bufhidden='hide') + target_winid = int(lfEval("win_getid()")) + lfCmd("call win_execute({}, 'setlocal cursorlineopt=number')".format(target_winid)) + lfCmd("call win_execute({}, 'setlocal cursorline')".format(target_winid)) + elif buffer_names[1] in self._views: + lfCmd("call win_gotoid({})".format(self._views[buffer_names[1]].getWindowId())) + cmd = GitCatFileCommand(arguments_dict, sources[0], self._commit_id) + lfCmd("leftabove vsp {}".format(cmd.getBufferName())) + if buffer_names[0] in self._hidden_views: + self.bufShown(buffer_names[0], int(lfEval("win_getid()"))) + else: + GitCommandView(self, cmd).create(int(lfEval("win_getid()")), bufhidden='hide') + lfCmd("call win_execute({}, 'setlocal cursorlineopt=number')" + .format(int(lfEval("win_getid()")))) + lfCmd("call win_execute({}, 'setlocal cursorline')".format(int(lfEval("win_getid()")))) + lfCmd("call win_gotoid({})".format(self._views[buffer_names[1]].getWindowId())) + target_winid = int(lfEval("win_getid()")) + else: + if kwargs.get("mode", '') == 't': + lfCmd("noautocmd tabnew | vsp") + tabmove() + win_ids = [int(lfEval("win_getid({})".format(w.number))) + for w in vim.current.tabpage.windows] + elif "winid" in kwargs: # --explorer create + win_ids = [kwargs["winid"], 0] + lfCmd("call win_gotoid({})".format(win_ids[0])) + lfCmd("noautocmd bel vsp") + win_ids[1] = int(lfEval("win_getid()")) + lfCmd("call win_gotoid({})".format(win_ids[0])) + elif vim.current.tabpage not in self._buffer_names: # Leaderf git diff -s + lfCmd("noautocmd tabnew | vsp") + tabmove() + win_ids = [int(lfEval("win_getid({})".format(w.number))) + for w in vim.current.tabpage.windows] + else: # open + buffer_names = self._buffer_names[vim.current.tabpage] + win_ids = [int(lfEval("bufwinid('{}')".format(escQuote(name)))) + for name in buffer_names] + win_pos = arguments_dict.get("--navigation-position", ["left"])[0] + win_ids = self.getValidWinIDs(win_ids, win_pos) + + target_winid = win_ids[1] + cat_file_cmds = [GitCatFileCommand(arguments_dict, s, self._commit_id) for s in sources] + outputs = [None, None] + if (cat_file_cmds[0].getBufferName() not in self._hidden_views + and cat_file_cmds[1].getBufferName() not in self._hidden_views): + outputs = ParallelExecutor.run(*[cmd.getCommand() for cmd in cat_file_cmds], + directory=self._project_root) + + if vim.current.tabpage not in self._buffer_names: + self._buffer_names[vim.current.tabpage] = [None, None] + + for i, (cmd, winid) in enumerate(zip(cat_file_cmds, win_ids)): + if (lfEval("bufname(winbufnr({}))".format(winid)) == "" + and int(lfEval("bufnr('{}')".format(escQuote(cmd.getBufferName())))) != -1): + lfCmd("call win_execute({}, 'setlocal bufhidden=wipe')".format(winid)) + + buffer_name = lfEval("bufname(winbufnr({}))".format(winid)) + lfCmd("call win_execute({}, 'diffoff | hide edit {}')" + .format(winid, cmd.getBufferName())) + lfCmd("call win_execute({}, 'setlocal cursorlineopt=number')".format(winid)) + lfCmd("call win_execute({}, 'setlocal cursorline')".format(winid)) + lfCmd("call win_execute({}, 'let b:lf_explorer_page_id = {}')" + .format(winid, kwargs.get("explorer_page_id", 0))) + lfCmd("call win_execute({}, 'let b:lf_tree_view_id = {}')" + .format(winid, kwargs.get("tree_view_id", 0))) + lfCmd("call win_execute({}, 'let b:lf_git_diff_win_pos = {}')".format(winid, i)) + lfCmd("call win_execute({}, 'let b:lf_git_diff_win_id = {}')".format(winid, win_ids[1])) + abs_file_path = os.path.join(self._project_root, lfGetFilePath(source)) + lfCmd("""call win_execute(%d, "let b:lf_git_buffer_name = '%s'")""" + % (winid, escQuote(abs_file_path))) + lfCmd("""call win_execute({}, 'let b:lf_diff_view_mode = "side-by-side"')""" + .format(winid)) + lfCmd("""call win_execute({}, "let b:lf_diff_view_source = {}")""" + .format(winid, str(list(source)))) + key_map = lfEval("g:Lf_GitKeyMap") + lfCmd("""call win_execute({}, 'nnoremap {} [c')""" + .format(winid, key_map["previous_change"])) + lfCmd("""call win_execute({}, 'nnoremap {} ]c')""" + .format(winid, key_map["next_change"])) + lfCmd("""call win_execute({}, 'nnoremap {} :call leaderf#Git#EditFile(2)')""" + .format(winid, key_map["edit_file"])) + + # if the buffer also in another tabpage, BufHidden is not triggerd + # should run this code + if buffer_name in self._views: + self.bufHidden(self._views[buffer_name]) + + self._buffer_names[vim.current.tabpage][i] = cmd.getBufferName() + if cmd.getBufferName() in self._hidden_views: + self.bufShown(cmd.getBufferName(), winid) + else: + GitCommandView(self, cmd).create(winid, bufhidden='hide', + buf_content=outputs[i]) + + lfCmd("call win_gotoid({})".format(win_ids[1])) + + if kwargs.get("line_num", None) is not None: + lfCmd("call win_execute({}, 'norm! {}G0zbzz')" + .format(target_winid, kwargs["line_num"])) + else: + lfCmd("call win_execute({}, 'norm! gg]c0')".format(target_winid)) + + # sometimes the two sides don't align. + lfCmd("call win_execute({}, 'norm! ztzz')".format(target_winid)) + + +class UnifiedDiffViewPanel(Panel): + def __init__(self, bufhidden_callback=None, commit_id=""): + super(UnifiedDiffViewPanel, self).__init__() + self._commit_id = commit_id + self._views = {} + self._hidden_views = {} + self._bufhidden_cb = bufhidden_callback + lfCmd("sign define Leaderf_diff_add linehl=Lf_hl_gitDiffAdd") + lfCmd("sign define Leaderf_diff_delete linehl=Lf_hl_gitDiffDelete") + lfCmd("sign define Leaderf_diff_change linehl=Lf_hl_gitDiffChange") + + def setCommitId(self, commit_id): + self._commit_id = commit_id + + def register(self, view): + self._views[view.getBufferName()] = view + + def deregister(self, view): + # :bw + name = view.getBufferName() + if name in self._views: + self._views[name].cleanup(wipe=False) + del self._views[name] + + if name in self._hidden_views: + self._hidden_views[name].cleanup(wipe=False) + del self._hidden_views[name] + + def bufHidden(self, view): + # window is closed if not equal + if int(lfEval("win_getid()")) == view.getWindowId(): + lfCmd("silent! call leaderf#Git#ClearMatches()") + + name = view.getBufferName() + if name in self._views: + del self._views[name] + self._hidden_views[name] = view + + if self._bufhidden_cb is not None: + self._bufhidden_cb() + + def bufShown(self, buffer_name, winid): + view = self._hidden_views[buffer_name] + view.setWindowId(winid) + del self._hidden_views[buffer_name] + self._views[buffer_name] = view + lfCmd("call setmatches(b:Leaderf_matches, {})".format(winid)) + + def cleanup(self): + for view in self._hidden_views.values(): + view.cleanup() + self._hidden_views = {} + + def isAllHidden(self): + return len(self._views) == 0 + + def signPlace(self, added_line_nums, deleted_line_nums, buffer_num): + lfCmd("call leaderf#Git#SignPlace({}, {}, {})" + .format(str(added_line_nums), str(deleted_line_nums), buffer_num)) + + def setLineNumberWin(self, line_num_content, buffer_num): + if lfEval("has('nvim')") == '1': + self.nvim_setLineNumberWin(line_num_content, buffer_num) + return + + hi_line_num = int(lfEval("get(g:, 'Lf_GitHightlightLineNumber', 1)")) + for i, line in enumerate(line_num_content, 1): + if line[-2] == '-': + if hi_line_num == 1: + property_type = "Lf_hl_gitDiffDelete" + else: + property_type = "Lf_hl_LineNr" + lfCmd("call prop_add(%d, 1, {'type': '%s', 'text': '%s', 'bufnr': %d})" + % (i, property_type, line[:-2], buffer_num)) + property_type = "Lf_hl_gitDiffDelete" + lfCmd("call prop_add(%d, 1, {'type': '%s', 'text': '%s', 'bufnr': %d})" + % (i, property_type, line[-2:], buffer_num)) + elif line[-2] == '+': + if hi_line_num == 1: + property_type = "Lf_hl_gitDiffAdd" + else: + property_type = "Lf_hl_LineNr" + lfCmd("call prop_add(%d, 1, {'type': '%s', 'text': '%s', 'bufnr': %d})" + % (i, property_type, line[:-2], buffer_num)) + property_type = "Lf_hl_gitDiffAdd" + lfCmd("call prop_add(%d, 1, {'type': '%s', 'text': '%s', 'bufnr': %d})" + % (i, property_type, line[-2:], buffer_num)) + else: + property_type = "Lf_hl_LineNr" + lfCmd("call prop_add(%d, 1, {'type': '%s', 'text': '%s', 'bufnr': %d})" + % (i, property_type, line, buffer_num)) + + def nvim_setLineNumberWin(self, line_num_content, buffer_num): + lfCmd("call leaderf#Git#SetLineNumberWin({}, {})".format(str(line_num_content), + buffer_num)) + + def highlight(self, winid, status, content, line_num, line): + i = 0 + while i < len(line): + if line[i] == status or line[i] == '^': + c = line[i] + beg = i + i += 1 + while i < len(line) and line[i] == c: + i += 1 + end = i + col = lfBytesLen(content[line_num-1][:beg]) + 1 + length = lfBytesLen(content[line_num - 1][beg : end]) + if c == '^': + hl_group = "Lf_hl_gitDiffText" + elif c == '-': + hl_group = "Lf_hl_gitDiffText" + else: + hl_group = "Lf_hl_gitDiffText" + lfCmd("""call win_execute({}, "let matchid = matchaddpos('{}', [{}], -100)")""" + .format(winid, hl_group, str([line_num, col, length]))) + else: + i += 1 + + def highlightOneline(self, winid, content, minus_beg, plus_beg): + sm = SequenceMatcher(None, content[minus_beg-1], content[plus_beg-1]) + opcodes = sm.get_opcodes() + if len(opcodes) == 1 and opcodes[0][0] == "replace": + return + + hl_group = "Lf_hl_gitDiffChange" + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''{}'', ''\%{}l'', -101)')""" + .format(winid, hl_group, minus_beg)) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''{}'', ''\%{}l'', -101)')""" + .format(winid, hl_group, plus_beg)) + + for tag, beg1, end1, beg2, end2 in sm.get_opcodes(): + if tag == "delete": + col = lfBytesLen(content[minus_beg-1][:beg1]) + 1 + length = lfBytesLen(content[minus_beg - 1][beg1 : end1]) + hl_group = "Lf_hl_gitDiffText" + lfCmd("""call win_execute({}, "let matchid = matchaddpos('{}', [{}], -100)")""" + .format(winid, hl_group, str([minus_beg, col, length]))) + elif tag == "insert": + col = lfBytesLen(content[plus_beg-1][:beg2]) + 1 + length = lfBytesLen(content[plus_beg - 1][beg2 : end2]) + hl_group = "Lf_hl_gitDiffText" + lfCmd("""call win_execute({}, "let matchid = matchaddpos('{}', [{}], -100)")""" + .format(winid, hl_group, str([plus_beg, col, length]))) + elif tag == "replace": + col = lfBytesLen(content[minus_beg-1][:beg1]) + 1 + length = lfBytesLen(content[minus_beg - 1][beg1 : end1]) + hl_group = "Lf_hl_gitDiffText" + lfCmd("""call win_execute({}, "let matchid = matchaddpos('{}', [{}], -100)")""" + .format(winid, hl_group, str([minus_beg, col, length]))) + + col = lfBytesLen(content[plus_beg-1][:beg2]) + 1 + length = lfBytesLen(content[plus_beg - 1][beg2 : end2]) + hl_group = "Lf_hl_gitDiffText" + lfCmd("""call win_execute({}, "let matchid = matchaddpos('{}', [{}], -100)")""" + .format(winid, hl_group, str([plus_beg, col, length]))) + + def highlightDiff(self, winid, content, minus_plus_lines): + for minus_beg, minus_end, plus_beg, plus_end in minus_plus_lines: + if minus_beg == minus_end and plus_beg == plus_end: + self.highlightOneline(winid, content, minus_beg, plus_beg) + continue + + minus_text = content[minus_beg - 1 : minus_end] + plus_text = content[plus_beg - 1 : plus_end] + minus_line_num = minus_beg - 1 + plus_line_num = plus_beg - 1 + status = ' ' + changed_line_num = 0 + for line in LfDiffer().compare(minus_text, plus_text): + if line.startswith('- '): + status = '-' + minus_line_num += 1 + elif line.startswith('+ '): + status = '+' + plus_line_num += 1 + elif line.startswith('? '): + if status == '-': + hl_group = "Lf_hl_gitDiffChange" + changed_line_num = plus_line_num + 1 + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''{}'', ''\%{}l'', -101)')""" + .format(winid, hl_group, minus_line_num)) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''{}'', ''\%{}l'', -101)')""" + .format(winid, hl_group, plus_line_num + 1)) + self.highlight(winid, status, content, minus_line_num, line[2:]) + elif status == '+': + hl_group = "Lf_hl_gitDiffChange" + if changed_line_num != plus_line_num: + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''{}'', ''\%{}l'', -101)')""" + .format(winid, hl_group, minus_line_num)) + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''{}'', ''\%{}l'', -101)')""" + .format(winid, hl_group, plus_line_num)) + self.highlight(winid, status, content, plus_line_num, line[2:]) + elif line.startswith(' '): + status = ' ' + minus_line_num += 1 + plus_line_num += 1 + + def setSomeOptions(self): + lfCmd("setlocal foldcolumn=1") + lfCmd("setlocal signcolumn=no") + lfCmd("setlocal nonumber") + lfCmd("setlocal conceallevel=0") + lfCmd("setlocal nowrap") + lfCmd("setlocal foldmethod=expr") + lfCmd("setlocal foldexpr=leaderf#Git#FoldExpr()") + lfCmd("setlocal foldlevel=0") + + def create(self, arguments_dict, source, **kwargs): + """ + source is a tuple like (b90f76fc1, bad07e644, R099, src/version.c, src/version2.c) + """ + self._project_root = kwargs.get("project_root", None) + ignore_whitespace = kwargs.get("ignore_whitespace", False) + diff_algorithm = kwargs.get("diff_algorithm", "myers") + algo_dict = { + "myers": 0, + "minimal": 2, + "patience": 4, + "histogram": 6 + } + if source[1].startswith("0000000"): + commit_id = hex(int(self._commit_id, 16) + int(source[0][-7:], 16))[2:] + else: + commit_id = self._commit_id + uid = algo_dict[diff_algorithm] + int(ignore_whitespace) + buf_name = "LeaderF://{}:{}:{}".format(commit_id, + uid, + lfGetFilePath(source)) + if buf_name in self._views: + winid = self._views[buf_name].getWindowId() + lfCmd("call win_gotoid({})".format(winid)) + else: + if kwargs.get("mode", '') == 't': + lfCmd("noautocmd tabnew") + tabmove() + winid = int(lfEval("win_getid()")) + elif "winid" in kwargs: # --explorer + winid = kwargs["winid"] + else: + win_pos = arguments_dict.get("--navigation-position", ["left"])[0] + if win_pos in ["top", "left"]: + lfCmd("wincmd w") + else: + lfCmd("wincmd W") + winid = int(lfEval("win_getid()")) + + if buf_name not in self._hidden_views: + fold_ranges = [] + minus_plus_lines = [] + line_num_dict = {} + change_start_lines = [] + delimiter = lfEval("get(g:, 'Lf_GitDelimiter', '│')") + if source[0].startswith("0000000"): + git_cmd = "git show {}".format(source[1]) + outputs = ParallelExecutor.run(git_cmd, directory=self._project_root) + line_num_width = len(str(len(outputs[0]))) + content = outputs[0] + line_num_content = ["{:>{}} +{}".format(i, line_num_width, delimiter) + for i in range(1, len(content) + 1)] + deleted_line_nums = [] + added_line_nums = range(1, len(outputs[0]) + 1) + elif source[1].startswith("0000000") and source[2] != 'M': + git_cmd = "git show {}".format(source[0]) + outputs = ParallelExecutor.run(git_cmd, directory=self._project_root) + line_num_width = len(str(len(outputs[0]))) + content = outputs[0] + line_num_content = ["{:>{}} -{}".format(i, line_num_width, delimiter) + for i in range(1, len(content) + 1)] + deleted_line_nums = range(1, len(outputs[0]) + 1) + added_line_nums = [] + else: + if source[1].startswith("0000000"): + extra_options = "--diff-algorithm=" + diff_algorithm + if "--cached" in arguments_dict: + extra_options += " --cached" + + if "extra" in arguments_dict: + extra_options += " " + " ".join(arguments_dict["extra"]) + + if ignore_whitespace == True: + extra_options += " -w" + + git_cmd = "git diff -U999999 --no-color {} -- {}".format(extra_options, + source[3]) + else: + extra_options = "--diff-algorithm=" + diff_algorithm + if ignore_whitespace == True: + extra_options += " -w" + + git_cmd = "git diff -U999999 --no-color {} {} {}".format(extra_options, + source[0], + source[1]) + + outputs = ParallelExecutor.run(git_cmd, directory=self._project_root) + start = 0 + for i, line in enumerate(outputs[0], 1): + if line.startswith("@@"): + start = i + break + + line_num_width = len(str(len(outputs[0]) - start)) + + content = [] + line_num_content = [] + orig_line_num = 0 + line_num = 0 + + minus_beg = 0 + minus_end = 0 + plus_beg = 0 + plus_end = 0 + + change_start = 0 + deleted_line_nums = [] + added_line_nums = [] + context_num = int(lfEval("get(g:, 'Lf_GitContextNum', 6)")) + if context_num < 0: + context_num = 6 + beg = 1 + for i, line in enumerate(islice(outputs[0], start, None), 1): + content.append(line[1:]) + if line.startswith("-"): + # for fold + if beg != 0: + end = i - context_num - 1 + if end > beg: + fold_ranges.append([beg, end]) + beg = 0 + + # for highlight + if plus_beg != 0: + plus_end = i - 1 + minus_plus_lines.append((minus_beg, minus_end, plus_beg, plus_end)) + minus_beg = 0 + plus_beg = 0 + + if minus_beg == 0: + minus_beg = i + + if change_start == 0: + change_start = i + + deleted_line_nums.append(i) + orig_line_num += 1 + line_num_content.append("{:>{}} {:{}} -{}".format(orig_line_num, + line_num_width, + " ", + line_num_width, + delimiter)) + elif line.startswith("+"): + # for fold + if beg != 0: + end = i - context_num - 1 + if end > beg: + fold_ranges.append([beg, end]) + beg = 0 + + # for highlight + if minus_beg != 0 and plus_beg == 0: + minus_end = i - 1 + plus_beg = i + + if change_start == 0: + change_start = i + + added_line_nums.append(i) + line_num += 1 + line_num_dict[line_num] = i + line_num_content.append("{:{}} {:>{}} +{}".format(" ", + line_num_width, + line_num, + line_num_width, + delimiter)) + else: + # for fold + if beg == 0: + beg = i + context_num + + # for highlight + if plus_beg != 0: + plus_end = i - 1 + minus_plus_lines.append((minus_beg, minus_end, plus_beg, plus_end)) + plus_beg = 0 + + minus_beg = 0 + + if change_start != 0: + change_start_lines.append(change_start) + change_start = 0 + + orig_line_num += 1 + line_num += 1 + line_num_dict[line_num] = i + line_num_content.append("{:>{}} {:>{}} {}".format(orig_line_num, + line_num_width, + line_num, + line_num_width, + delimiter)) + else: + # for fold + end = len(outputs[0]) - start + if beg != 0 and end > beg: + fold_ranges.append([beg, end]) + + # for highlight + if plus_beg != 0: + plus_end = end + minus_plus_lines.append((minus_beg, minus_end, plus_beg, plus_end)) + + if change_start != 0: + change_start_lines.append(change_start) + + lfCmd("call win_gotoid({})".format(winid)) + if not vim.current.buffer.name: # buffer name is empty + lfCmd("setlocal bufhidden=wipe") + lfCmd("silent hide edit {}".format(buf_name)) + abs_file_path = os.path.join(self._project_root, lfGetFilePath(source)) + lfCmd("let b:lf_git_buffer_name = '%s'" % escQuote(abs_file_path)) + lfCmd("let b:lf_git_line_num_content = {}".format(str(line_num_content))) + lfCmd("augroup Lf_Git_Log | augroup END") + lfCmd("autocmd! Lf_Git_Log BufWinEnter call leaderf#Git#SetMatches()") + ranges = (range(sublist[0], sublist[1] + 1) for sublist in fold_ranges) + fold_ranges_dict = {i: 0 for i in itertools.chain.from_iterable(ranges)} + lfCmd("let b:Leaderf_fold_ranges_dict = {}".format(str(fold_ranges_dict))) + lfCmd("silent! IndentLinesDisable") + self.setSomeOptions() + + cmd = GitCustomizeCommand(arguments_dict, "", buf_name, "", "") + view = GitCommandView(self, cmd) + view.line_num_dict = line_num_dict + view.change_start_lines = change_start_lines + view.create(winid, bufhidden='hide', buf_content=content) + + buffer_num = int(lfEval("winbufnr({})".format(winid))) + self.signPlace(added_line_nums, deleted_line_nums, buffer_num) + + self.setLineNumberWin(line_num_content, buffer_num) + self.highlightDiff(winid, content, minus_plus_lines) + lfCmd("let b:Leaderf_matches = getmatches()") + lfCmd("let b:lf_change_start_lines = {}".format(str(change_start_lines))) + lfCmd("let b:lf_explorer_page_id = {}".format(kwargs.get("explorer_page_id", 0))) + lfCmd("let b:lf_tree_view_id = {}".format(kwargs.get("tree_view_id", 0))) + lfCmd("let b:lf_diff_view_mode = 'unified'") + lfCmd("let b:lf_diff_view_source = {}".format(str(list(source)))) + key_map = lfEval("g:Lf_GitKeyMap") + lfCmd("nnoremap {} :call leaderf#Git#PreviousChange(0)" + .format(key_map["previous_change"])) + lfCmd("nnoremap {} :call leaderf#Git#NextChange(0)" + .format(key_map["next_change"])) + lfCmd("nnoremap {} :call leaderf#Git#EditFile(0)" + .format(key_map["edit_file"])) + else: + lfCmd("call win_gotoid({})".format(winid)) + if not vim.current.buffer.name: # buffer name is empty + lfCmd("setlocal bufhidden=wipe") + lfCmd("silent hide edit {}".format(buf_name)) + self.bufShown(buf_name, winid) + self.setSomeOptions() + + target_line_num = kwargs.get("line_num", None) + if target_line_num is not None: + target_line_num = int(target_line_num) + line_num = self._views[buf_name].line_num_dict.get(target_line_num, target_line_num) + lfCmd("call win_execute({}, 'norm! {}G0zbzz')".format(winid, line_num)) + else: + change_start_lines = self._views[buf_name].change_start_lines + if len(change_start_lines) == 0: + first_change = 1 + else: + first_change = change_start_lines[0] + lfCmd("call win_execute({}, 'norm! {}G0zbzz')".format(winid, first_change)) + + +class NavigationPanel(Panel): + def __init__(self, owner, project_root, commit_id, bufhidden_callback=None): + super(NavigationPanel, self).__init__() + self._owner = owner + self._project_root = project_root + self._commit_id = commit_id + self._tree_view = [] + self._bufhidden_cb = bufhidden_callback + self._is_hidden = False + self._arguments = {} + self._diff_view_mode = None + self._ignore_whitespace = False + self._diff_algorithm = 'myers' + self._git_diff_manager = None + self._buffer = None + self._head = [ + '" Press for help', + ' Side-by-side ◉ Unified ○', + ' Ignore Whitespace 🗷 ', + ' Myers ◉ Minimal ○ Patience ○ Histogram ○', + ] + + def startLine(self, tree_view): + n = len(self._head) + 1 + for view in self._tree_view: + if view.getHeight() > 0: + n += view.getTitleHeight() + 2 + + if tree_view is view: + return n + else: + n += view.getHeight() + + return n + + def getTreeView(self): + line_num = int(lfEval("getcurpos({})[1]".format(self.getWindowId()))) + n = len(self._head) + 1 + for view in self._tree_view: + if view.getHeight() > 0: + n += view.getTitleHeight() + view.getHeight() + 2 + if line_num < n: + return view + + def getDiffViewMode(self): + return self._diff_view_mode + + def getIgnoreWhitespace(self): + return self._ignore_whitespace + + def getDiffAlgorithm(self): + return self._diff_algorithm + + def register(self, view): + self._tree_view.append(view) + + def bufHidden(self, view): + self._is_hidden = True + if self._bufhidden_cb is not None: + self._bufhidden_cb() + + def isHidden(self): + return self._is_hidden + + def cleanup(self): + for view in self._tree_view: + if view is not None: + view.cleanup() + self._tree_view = [] + + def create(self, arguments_dict, command, winid, project_root, target_path, callback): + if "-u" in arguments_dict: + self._diff_view_mode = "unified" + elif "-s" in arguments_dict: + self._diff_view_mode = "side-by-side" + else: + self._diff_view_mode = lfEval("get(g:, 'Lf_GitDiffViewMode', 'unified')") + + self._buffer = vim.buffers[int(lfEval("winbufnr({})".format(winid)))] + self._buffer[:] = self._head + self.setDiffViewMode(self._diff_view_mode) + + flag = [False] + def wrapper(cb, flag, *args, **kwargs): + if flag[0] == False: + flag[0] = True + cb(*args, **kwargs) + return True + else: + return False + + if isinstance(command, list): + def createTreeView(cmds): + if len(cmds) > 0: + TreeView(self, cmds[0], + project_root, + target_path, + partial(wrapper, callback, flag), + partial(createTreeView, cmds[1:]) + ).create(winid, bufhidden="hide") + + self._arguments = command[0].getArguments() + createTreeView(command) + else: + self._arguments = command.getArguments() + TreeView(self, command, + project_root, + target_path, + partial(wrapper, callback, flag), + ).create(winid, bufhidden="hide") + + lfCmd("call win_execute({}, 'let b:lf_navigation_matches = getmatches()')".format(winid)) + + self.defineMaps(winid) + + def defineMaps(self, winid): + lfCmd("call win_execute({}, 'call leaderf#Git#NavigationPanelMaps({})')" + .format(winid, id(self))) + + def setDiffViewMode(self, mode): + self._buffer.options['modifiable'] = True + if mode == 'side-by-side': + self._buffer[1] = ' Side-by-side ◉ Unified ○' + + diffopt = lfEval("&diffopt") + if "iwhiteall" in diffopt: + self._buffer[2] = ' Ignore Whitespace 🗹 ' + else: + self._buffer[2] = ' Ignore Whitespace 🗷 ' + + if "algorithm:" in diffopt: + algo = re.sub(r".*algorithm:(\w+).*", r"\1", diffopt) + self.setDiffAlgorithm(algo) + else: + self.setDiffAlgorithm("myers") + else: + self._buffer[1] = ' Side-by-side ○ Unified ◉' + self._buffer.options['modifiable'] = False + + def setIgnoreWhitespace(self, diff_view_mode, ignore): + self._buffer.options['modifiable'] = True + if diff_view_mode == 'side-by-side': + if "iwhiteall" in lfEval("&diffopt"): + self._buffer[2] = ' Ignore Whitespace 🗹 ' + else: + self._buffer[2] = ' Ignore Whitespace 🗷 ' + else: + if ignore == True: + self._buffer[2] = ' Ignore Whitespace 🗹 ' + else: + self._buffer[2] = ' Ignore Whitespace 🗷 ' + self._buffer.options['modifiable'] = False + + def setDiffAlgorithm(self, algorithm): + self._buffer.options['modifiable'] = True + if algorithm == 'myers': + self._buffer[3] = ' Myers ◉ Minimal ○ Patience ○ Histogram ○' + elif algorithm == 'minimal': + self._buffer[3] = ' Myers ○ Minimal ◉ Patience ○ Histogram ○' + elif algorithm == 'patience': + self._buffer[3] = ' Myers ○ Minimal ○ Patience ◉ Histogram ○' + elif algorithm == 'histogram': + self._buffer[3] = ' Myers ○ Minimal ○ Patience ○ Histogram ◉' + self._buffer.options['modifiable'] = False + + def selectOption(self): + mouse_pos = lfEval("getmousepos()") + column = int(mouse_pos["column"]) + if mouse_pos["line"] == '2': + if column >= 5 and column <= 18: + mode = 'side-by-side' + elif column >= 22 and column <= 30: + mode = 'unified' + else: + mode = None + + if mode is not None and mode != self._diff_view_mode: + self.toggleDiffViewMode() + elif mouse_pos["line"] == '3': + if column >= 5 and column <= 23: + self.toggleIgnoreWhitespace() + elif mouse_pos["line"] == '4': + if column >= 5 and column <= 11: + diff_algorithm = 'myers' + elif column >= 15 and column <= 23: + diff_algorithm = 'minimal' + elif column >= 27 and column <= 36: + diff_algorithm = 'patience' + elif column >= 40 and column <= 50: + diff_algorithm = 'histogram' + else: + diff_algorithm = self._diff_algorithm + + if self._diff_algorithm != diff_algorithm: + self._diff_algorithm = diff_algorithm + self.selectDiffAlgorithm() + + def selectDiffAlgorithm(self): + self.setDiffAlgorithm(self._diff_algorithm) + if self._diff_view_mode == 'side-by-side': + lfCmd("set diffopt+=internal") + diffopt = lfEval("&diffopt") + if "algorithm:" in diffopt: + diffopt = re.sub(r"(?<=algorithm:)\w+", self._diff_algorithm, diffopt) + lfCmd("let &diffopt = '{}'".format(diffopt)) + else: + lfCmd("set diffopt+=algorithm:{}".format(self._diff_algorithm)) + else: + self.openDiffView(False, preview=True, diff_view_source=True) + + def toggleDiffViewMode(self): + if self._diff_view_mode == 'side-by-side': + self._diff_view_mode = 'unified' + self._ignore_whitespace = "iwhiteall" in lfEval("&diffopt") + else: + self._diff_view_mode = 'side-by-side' + if self._ignore_whitespace == True: + lfCmd("set diffopt+=iwhiteall") + else: + lfCmd("set diffopt-=iwhiteall") + + self.setDiffViewMode(self._diff_view_mode) + + if self._diff_view_mode == 'side-by-side': + diffopt = lfEval("&diffopt") + if "algorithm:" in diffopt: + algo = re.sub(r".*algorithm:(\w+).*", r"\1", diffopt) + self._diff_algorithm = algo + else: + self._diff_algorithm = "myers" + + self.openDiffView(False, preview=True, diff_view_source=True) + + def toggleIgnoreWhitespace(self): + if self._diff_view_mode == 'side-by-side': + if "iwhiteall" in lfEval("&diffopt"): + lfCmd("set diffopt-=iwhiteall") + else: + lfCmd("set diffopt+=iwhiteall") + self.setIgnoreWhitespace(self._diff_view_mode, self._ignore_whitespace) + else: + self._ignore_whitespace = not self._ignore_whitespace + self.setIgnoreWhitespace(self._diff_view_mode, self._ignore_whitespace) + self.openDiffView(False, preview=True, diff_view_source=True) + + def openDiffView(self, recursive, **kwargs): + return self._owner.openDiffView(recursive, **kwargs) + + def open(self): + navigation_winid = self.getWindowId() + if navigation_winid != -1: + lfCmd("call win_gotoid({})".format(navigation_winid)) + return + + tree_view_id = int(lfEval("b:lf_tree_view_id")) + buffer_name = self._buffer.name + current_file_path = vim.current.buffer.name.rsplit(':', 1)[1] + win_pos = self._arguments.get("--navigation-position", ["left"])[0] + if win_pos == 'top': + height = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelHeight', &lines * 0.3)"))) + lfCmd("silent! noa keepa keepj topleft {}sp {}".format(height, buffer_name)) + elif win_pos == 'bottom': + height = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelHeight', &lines * 0.3)"))) + lfCmd("silent! noa keepa keepj botright {}sp {}".format(height, buffer_name)) + elif win_pos == 'left': + width = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelWidth', 44)"))) + lfCmd("silent! noa keepa keepj topleft {}vsp {}".format(width, buffer_name)) + elif win_pos == 'right': + width = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelWidth', 44)"))) + lfCmd("silent! noa keepa keepj botright {}vsp {}".format(width, buffer_name)) + else: # left + width = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelWidth', 44)"))) + lfCmd("silent! noa keepa keepj topleft {}vsp {}".format(width, buffer_name)) + + lfCmd("call setmatches(b:lf_navigation_matches)") + lfCmd("setlocal winfixwidth | wincmd =") + self._is_hidden = False + if len(self._tree_view) == 1: + self.getTreeView().locateFile(current_file_path) + else: + ctypes.cast(tree_view_id, ctypes.py_object).value.locateFile(current_file_path) + + def getWindowId(self): + return int(lfEval("bufwinid('{}')".format(escQuote(self._buffer.name)))) + + def collapseChildren(self): + tree_view = self.getTreeView() + if tree_view is not None: + tree_view.collapseChildren() + + def locateFile(self, path, line_num=None, preview=True): + self.getTreeView().locateFile(path) + self.openDiffView(False, line_num=line_num, preview=preview) + + @ensureWorkingDirectory + def fuzzySearch(self, recall=False): + if self._git_diff_manager is None: + self._git_diff_manager = GitDiffExplManager() + + tree_view = self.getTreeView() + if tree_view is None: + return + + kwargs = {} + kwargs["arguments"] = { + "owner": self._owner._owner, + "commit_id": self._commit_id, + "command": tree_view.getCommand(), + "parent": tree_view.getCurrentParent(), + "content": tree_view.getFileList(), + "accept": self.locateFile + } + + if recall == True: + kwargs["arguments"]["--recall"] = [] + + self._git_diff_manager.startExplorer("popup", **kwargs) + + @ensureWorkingDirectory + def showCommitMessage(self): + cmd = "git show {} -s --decorate --pretty=fuller".format(self._commit_id) + lfCmd("""call leaderf#Git#ShowCommitMessage(systemlist('{}'))""".format(cmd)) + + +class BlamePanel(Panel): + def __init__(self, owner): + super(BlamePanel, self).__init__() + self._owner = owner + self._views = {} + + def register(self, view): + self._views[view.getBufferName()] = view + + def deregister(self, view): + name = view.getBufferName() + if name in self._views: + self._views[name].cleanup() + del self._views[name] + if len(self._views) == 0: + self._owner.discardPanel(self.getProjectRoot()) + + def getBlameView(self, buffer_name): + return self._views[buffer_name] + + def getAlternateWinid(self, buffer_name): + return self._views[buffer_name].getAlternateWinid() + + def getBlameStack(self, buffer_name): + return self._views[buffer_name].blame_stack + + def getBlameDict(self, buffer_name): + return self._views[buffer_name].blame_dict + + def getBlameBufferDict(self, buffer_name): + return self._views[buffer_name].blame_buffer_dict + + @staticmethod + def formatLine(arguments_dict, line_num_width, line): + if line.startswith("0000000"): + line = line.replace("External file (--contents)", "Not Committed Yet ") + + date_format = arguments_dict.get("--date", ["iso"])[0] + if date_format in ["iso", "iso-strict", "short"]: + # 6817817e autoload/leaderf/manager.py 1 (Yggdroot 2014-02-26 00:37:26 +0800 1) #!/usr/bin/env python + return re.sub(r'(^\^?\w+)\s+(.*?)\s+(\d+)\s+(\(.*?\d\d)\s+\d+\).*', + r'\g<1> \g<4>)\t\g<3> \g<2>', line, 1) + elif date_format == "relative": + # c5c6d072 autoload/leaderf/python/leaderf/manager.py 63 (Yggdroot 4 years, 6 months ago 66) def catchException(func): + line = re.sub(r'(^.*?\s\d+\)).*', r'\g<1>', line, 1) + return re.sub(r'(^\^?\w+)\s+(.*?)\s+(\d+)\s+(\(.*)', + r'\g<1> \g<4>)\t\g<3> \g<2>', + line[:-(line_num_width + 1)], 1) + elif date_format == "local": + # 6817817e autoload/leaderf/manager.py 1 (Yggdroot Wed Feb 26 00:37:26 2014 1) #!/usr/bin/env python + line = re.sub(r'(^.*?\s\d+\)).*', r'\g<1>', line, 1) + return re.sub(r'(^\^?\w+)\s+(.*?)\s+(\d+)\s+(\(.*)', + r'\g<1> \g<4>)\t\g<3> \g<2>', + line[:-(line_num_width + 7)], 1) + elif date_format in ("rfc", "default"): + # 6817817e autoload/leaderf/manager.py 1 (Yggdroot Wed, 26 Feb 2014 00:37:26 +0800 1) #!/usr/bin/env python + # 6817817e autoload/leaderf/manager.py 1 (Yggdroot Wed Feb 26 00:37:26 2014 +0800 1) #!/usr/bin/env python + line = re.sub(r'(^.*?\s\d+\)).*', r'\g<1>', line, 1) + return re.sub(r'(^\^?\w+)\s+(.*?)\s+(\d+)\s+(\(.*)', + r'\g<1> \g<4>)\t\g<3> \g<2>', + line[:-(line_num_width + 1)], 1) + else: + return line + + def create(self, arguments_dict, cmd, project_root=None): + self._project_root = project_root + buffer_name = cmd.getBufferName() + line_num_width = len(str(len(vim.current.buffer))) + 1 + + date_format = arguments_dict.get("--date", ["iso"])[0] + error = [] + if date_format in ["iso", "iso-strict", "short"]: + outputs = ParallelExecutor.run(cmd.getCommand(), + format_line=partial(BlamePanel.formatLine, + arguments_dict, + line_num_width), + directory=self._project_root, + silent=True, + error=error) + else: + arguments_dict2 = arguments_dict.copy() + arguments_dict2["-c"] = [] + cmd2 = GitBlameCommand(arguments_dict2, None) + + arguments_dict3 = arguments_dict2.copy() + arguments_dict3["--date"] = ["unix"] + cmd3 = GitBlameCommand(arguments_dict3, None) + + outputs = ParallelExecutor.run(cmd.getCommand(), + cmd2.getCommand(), + cmd3.getCommand(), + format_line=[partial(BlamePanel.formatLine, + arguments_dict, + line_num_width), + None, + None], + directory=self._project_root, + silent=True, + error=error) + if len(error) > 0: + lfPrintError(error[0]) + return + + line_num_width = max(line_num_width, int(lfEval('&numberwidth'))) + if len(outputs[0]) > 0: + if buffer_name in self._views and self._views[buffer_name].valid(): + top_line = lfEval("line('w0')") + cursor_line = lfEval("line('.')") + line_width = outputs[0][0].rfind('\t') + self._views[buffer_name].create(-1, buf_content=outputs[0]) + lfCmd("vertical resize {}".format(line_width + line_num_width)) + lfCmd("noautocmd norm! {}Gzt{}G0".format(top_line, cursor_line)) + if date_format != lfEval("b:lf_blame_date_format"): + lfCmd("let b:lf_blame_date_format = '{}'".format(date_format)) + blame_view = self._views[buffer_name] + blame_view.clearHeatSyntax() + if date_format in ["iso", "iso-strict", "short"]: + blame_view.highlightHeatDate1(date_format, outputs[0]) + else: + blame_view.highlightHeatDate2(outputs[1], outputs[2]) + else: + winid = int(lfEval("win_getid()")) + blame_view = GitBlameView(self, cmd) + blame_view.saveAlternateWinOptions(winid, vim.current.buffer.number) + lfCmd("setlocal nofoldenable") + top_line = lfEval("line('w0')") + cursor_line = lfEval("line('.')") + line_width = outputs[0][0].rfind('\t') + lfCmd("silent! noa keepa keepj abo {}vsp {}".format(line_width + line_num_width, + buffer_name)) + blame_winid = int(lfEval("win_getid()")) + blame_view.create(blame_winid, buf_content=outputs[0]) + if date_format in ["iso", "iso-strict", "short"]: + blame_view.highlightHeatDate1(date_format, outputs[0]) + else: + blame_view.highlightHeatDate2(outputs[1], outputs[2]) + self._owner.defineMaps(blame_winid) + lfCmd("let b:lf_blame_project_root = '{}'".format(self._project_root)) + lfCmd("let b:lf_blame_date_format = '{}'".format(date_format)) + lfCmd("let b:lf_blame_file_name = '{}'".format(escQuote(arguments_dict["blamed_file_name"]))) + lfCmd("noautocmd norm! {}Gzt{}G0".format(top_line, cursor_line)) + lfCmd("call win_execute({}, 'setlocal scrollbind')".format(winid)) + lfCmd("setlocal scrollbind") + lfCmd(r"""call win_execute({}, 'let &l:stl=" Press for help.%=".g:Lf_GitStlHeatLine')""" + .format(blame_winid)) + else: + lfPrintError("No need to blame!") + + +class ExplorerPage(object): + def __init__(self, project_root, commit_id, owner): + self._project_root = project_root + self._navigation_panel = NavigationPanel(self, project_root, commit_id, self.afterBufhidden) + self._diff_view_panel = DiffViewPanel(self.afterBufhidden, commit_id) + self._unified_diff_view_panel = UnifiedDiffViewPanel(self.afterBufhidden, commit_id) + self.commit_id = commit_id + self._owner = owner + self._arguments = {} + self.tabpage = None + + def openNavigationPanel(self): + self._navigation_panel.open() + + def _createWindow(self, win_pos, buffer_name): + if win_pos == 'top': + height = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelHeight', &lines * 0.3)"))) + lfCmd("silent! noa keepa keepj abo {}sp {}".format(height, buffer_name)) + elif win_pos == 'bottom': + height = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelHeight', &lines * 0.3)"))) + lfCmd("silent! noa keepa keepj bel {}sp {}".format(height, buffer_name)) + elif win_pos == 'left': + width = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelWidth', 44)"))) + lfCmd("silent! noa keepa keepj abo {}vsp {}".format(width, buffer_name)) + elif win_pos == 'right': + width = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelWidth', 44)"))) + lfCmd("silent! noa keepa keepj bel {}vsp {}".format(width, buffer_name)) + else: # left + width = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelWidth', 44)"))) + lfCmd("silent! noa keepa keepj abo {}vsp {}".format(width, buffer_name)) + + return int(lfEval("win_getid()")) + + def splitWindow(self, win_pos): + if win_pos == 'top': + height = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelHeight', &lines * 0.3)"))) + height = int(lfEval("&lines")) - height - 4 + lfCmd("silent! noa keepa keepj bel {}sp".format(height)) + elif win_pos == 'bottom': + height = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelHeight', &lines * 0.3)"))) + height = int(lfEval("&lines")) - height - 4 + lfCmd("silent! noa keepa keepj abo {}sp".format(height)) + elif win_pos == 'left': + width = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelWidth', 44)"))) + width = int(lfEval("&columns")) - width - 1 + lfCmd("silent! noa keepa keepj bel {}vsp".format(width)) + elif win_pos == 'right': + width = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelWidth', 44)"))) + width = int(lfEval("&columns")) - width - 1 + lfCmd("silent! noa keepa keepj abo {}vsp".format(width)) + else: # left + width = int(float(lfEval("get(g:, 'Lf_GitNavigationPanelWidth', 44)"))) + width = int(lfEval("&columns")) - width - 1 + lfCmd("silent! noa keepa keepj bel {}vsp".format(width)) + + return int(lfEval("win_getid()")) + + def getDiffViewPanel(self): + if self._navigation_panel.getDiffViewMode() == "side-by-side": + return self._diff_view_panel + else: + return self._unified_diff_view_panel + + def create(self, arguments_dict, command, target_path=None, line_num=None): + self._arguments = arguments_dict + lfCmd("noautocmd tabnew | setlocal bufhidden=wipe") + + self.tabpage = vim.current.tabpage + diff_view_winid = int(lfEval("win_getid()")) + win_pos = arguments_dict.get("--navigation-position", ["left"])[0] + + if isinstance(command, list): + buffer_name = command[0].getBufferName() + winid = self._createWindow(win_pos, buffer_name) + else: + buffer_name = command.getBufferName() + winid = self._createWindow(win_pos, buffer_name) + + def createDiffViewPanel(get_diff_view_panel, arguments_dict, source, **kwargs): + return get_diff_view_panel().create(arguments_dict, source, **kwargs) + + callback = partial(createDiffViewPanel, + self.getDiffViewPanel, + arguments_dict, + winid=diff_view_winid, + line_num=line_num, + project_root=self._project_root, + explorer_page_id=id(self)) + + self._navigation_panel.create(arguments_dict, + command, + winid, + self._project_root, + target_path, + callback) + + def afterBufhidden(self): + if (self._navigation_panel.isHidden() and self._diff_view_panel.isAllHidden() + and self._unified_diff_view_panel.isAllHidden()): + lfCmd("call timer_start(1, function('leaderf#Git#Cleanup', [{}]))".format(id(self))) + + def cleanup(self): + self._navigation_panel.cleanup() + self._diff_view_panel.cleanup() + self._unified_diff_view_panel.cleanup() + self._owner.cleanupExplorerPage(self) + + def getExistingSource(self): + for w in vim.current.tabpage.windows: + source = lfEval("getbufvar({}, 'lf_diff_view_source', 0)".format(w.buffer.number)) + if source != '0': + return source + + return None + + def makeOnly(self): + diff_view_mode = self._navigation_panel.getDiffViewMode() + for w in vim.current.tabpage.windows: + if (lfEval("getbufvar({}, 'lf_diff_view_mode', '{}')".format(w.buffer.number, + diff_view_mode)) + != diff_view_mode): + lfCmd("only") + break + + def openDiffView(self, recursive, **kwargs): + kwargs["project_root"] = self._project_root + kwargs["explorer_page_id"] = id(self) + kwargs["ignore_whitespace"] = self._navigation_panel.getIgnoreWhitespace() + kwargs["diff_algorithm"] = self._navigation_panel.getDiffAlgorithm() + if "diff_view_source" in kwargs: + source = self.getExistingSource() + else: + tree_view = self._navigation_panel.getTreeView() + if tree_view is None: + return + source = tree_view.expandOrCollapseFolder(recursive) + kwargs["tree_view_id"] = id(tree_view) + + if source is not None: + self.makeOnly() + + if kwargs.get("mode", '') == 't': + tabpage_count = len(vim.tabpages) + self.getDiffViewPanel().create(self._arguments, source, **kwargs) + if len(vim.tabpages) > tabpage_count: + tabmove() + elif len(vim.current.tabpage.windows) == 1: + win_pos = self._arguments.get("--navigation-position", ["left"])[0] + diff_view_winid = self.splitWindow(win_pos) + kwargs["winid"] = diff_view_winid + self.getDiffViewPanel().create(self._arguments, source, **kwargs) + else: + self.getDiffViewPanel().create(self._arguments, source, **kwargs) + + if kwargs.get("preview", False) == True: + lfCmd("call win_gotoid({})".format(self._navigation_panel.getWindowId())) + + def locateFile(self, path, line_num=None, preview=True): + self._navigation_panel.locateFile(path, line_num, preview) + + +#***************************************************** +# GitExplManager +#***************************************************** +class GitExplManager(Manager): + def __init__(self): + super(GitExplManager, self).__init__() + self._show_icon = lfEval("get(g:, 'Lf_ShowDevIcons', 1)") == "1" + self._result_panel = ResultPanel() + self._preview_panel = PreviewPanel() + self._git_diff_manager = None + self._git_log_manager = None + self._git_blame_manager = None + self._git_status_manager = None + self._selected_content = None + self._project_root = None + self._subcommand = "" + + def _getExplClass(self): + return GitExplorer + + def _defineMaps(self): + lfCmd("call leaderf#Git#Maps({})".format(id(self))) + if type(self) is GitExplManager: + lfCmd("call leaderf#Git#SpecificMaps({})".format(id(self))) + + def _createHelp(self): + help = [] + help.append('" //o : execute command under cursor') + help.append('" i/ : switch to input mode') + if type(self) is GitExplManager: + help.append('" e : edit command under cursor') + help.append('" p : preview the help information') + help.append('" q : quit') + help.append('" : toggle this help') + help.append('" : close the preview window or quit') + help.append('" ---------------------------------------------------------') + return help + + def _workInIdle(self, content=None, bang=False): + self._result_panel.writeBuffer() + self._preview_panel.writeBuffer() + + super(GitExplManager, self)._workInIdle(content, bang) + + def _beforeExit(self): + super(GitExplManager, self)._beforeExit() + self._preview_panel.cleanup() + + def getExplManager(self, subcommand): + if subcommand == "diff": + if self._git_diff_manager is None: + self._git_diff_manager = GitDiffExplManager() + return self._git_diff_manager + elif subcommand == "log": + if self._git_log_manager is None: + self._git_log_manager = GitLogExplManager() + return self._git_log_manager + elif subcommand == "blame": + if self._git_blame_manager is None: + self._git_blame_manager = GitBlameExplManager() + return self._git_blame_manager + elif subcommand == "status": + if self._git_status_manager is None: + self._git_status_manager = GitStatusExplManager() + return self._git_status_manager + else: + return super(GitExplManager, self) + + def getWorkingDirectory(self, orig_cwd): + wd_mode = lfEval("get(g:, 'Lf_GitWorkingDirectoryMode', 'f')") + if wd_mode == 'f': + cur_buf_name = lfDecode(vim.current.buffer.name) + if cur_buf_name: + return nearestAncestor([".git"], os.path.dirname(cur_buf_name)) + + return nearestAncestor([".git"], orig_cwd) + + def checkWorkingDirectory(self, *args, **kwargs): + self._orig_cwd = lfGetCwd() + self._project_root = self.getWorkingDirectory(self._orig_cwd) + + if self._project_root: # there exists a root marker in nearest ancestor path + lfChdir(self._project_root) + else: + if kwargs.get('autocmd', None) == None: + lfPrintError("Not a git repository (or any of the parent directories): .git") + return False + + return True + + def startExplorer(self, win_pos, *args, **kwargs): + arguments_dict = kwargs.get("arguments", {}) + if "--recall" in arguments_dict: + self._arguments.update(arguments_dict) + subcommand = self._subcommand + else: + self.setArguments(arguments_dict) + + arg_list = self._arguments.get("arg_line", 'git').split() + arg_list = [item for item in arg_list if not item.startswith('-')] + if len(arg_list) == 1: + subcommand = "" + else: + subcommand = arg_list[1] + if "autocmd" not in arguments_dict: + self._subcommand = subcommand + + self.getExplManager(subcommand).startExplorer(win_pos, *args, **kwargs) + + def accept(self, mode=''): + source = self.getSource(self._getInstance().currentLine) + self._selected_content = self._preview_panel.getContent(source) + + return super(GitExplManager, self).accept(mode) + + def _accept(self, file, mode, *args, **kwargs): + self._acceptSelection(file, *args, **kwargs) + + def _acceptSelection(self, *args, **kwargs): + if len(args) == 0 or args[0] == '': + return + + line = args[0] + cmd = line + try: + lfCmd(cmd) + except vim.error: + lfPrintTraceback() + + def _bangEnter(self): + super(GitExplManager, self)._bangEnter() + + if lfEval("exists('*timer_start')") == '0': + lfCmd("echohl Error | redraw | echo ' E117: Unknown function: timer_start' | echohl NONE") + return + + self._callback(bang=True) + if self._read_finished < 2: + self._timer_id = lfEval("timer_start(10, function('leaderf#Git#TimerCallback', [%d]), {'repeat': -1})" % id(self)) + + def getSource(self, line): + commands = lfEval("leaderf#Git#Commands()") + for cmd in commands: + if line in cmd: + return cmd[line] + + return None + + def _previewInPopup(self, *args, **kwargs): + if len(args) == 0 or args[0] == '': + return + + line = args[0] + source = self.getSource(line) + + self._createPopupPreview("", source, 0) + + def _createPreviewWindow(self, config, source, line_num, jump_cmd): + if lfEval("has('nvim')") == '1': + lfCmd("noautocmd let scratch_buffer = nvim_create_buf(0, 1)") + lfCmd("noautocmd call setbufline(scratch_buffer, 1, '{}')".format(escQuote(source))) + lfCmd("noautocmd call nvim_buf_set_option(scratch_buffer, 'bufhidden', 'wipe')") + lfCmd("noautocmd call nvim_buf_set_option(scratch_buffer, 'undolevels', -1)") + + self._preview_winid = int(lfEval("nvim_open_win(scratch_buffer, 0, {})" + .format(json.dumps(config)))) + else: + lfCmd("noautocmd let winid = popup_create('{}', {})" + .format(escQuote(source), json.dumps(config))) + self._preview_winid = int(lfEval("winid")) + + self._setWinOptions(self._preview_winid) + + def createGitCommand(self, arguments_dict, source): + pass + + def _useExistingWindow(self, title, source, line_num, jump_cmd): + self.setOptionsForCursor() + + if lfEval("has('nvim')") == '1': + lfCmd("""call win_execute({}, "call nvim_buf_set_lines(0, 0, -1, v:false, ['{}'])")""" + .format(self._preview_winid, escQuote(source))) + else: + lfCmd("noautocmd call popup_settext({}, '{}')" + .format(self._preview_winid, escQuote(source))) + + def _cmdExtension(self, cmd): + if type(self) is GitExplManager: + if equal(cmd, ''): + self.editCommand() + return True + + def editCommand(self): + instance = self._getInstance() + line = instance.currentLine + instance.exitBuffer() + lfCmd("call feedkeys(':%s', 'n')" % escQuote(line)) + + +class GitDiffExplManager(GitExplManager): + def __init__(self): + super(GitDiffExplManager, self).__init__() + self._diff_view_panel = DiffViewPanel(self.afterBufhidden) + self._pages = set() + + def _getExplorer(self): + if self._explorer is None: + self._explorer = GitDiffExplorer() + return self._explorer + + def _getDigest(self, line, mode): + if mode == 0: + return line[5:] + elif mode == 1: + return getBasename(line) + else: + return getDirname(line[5:]) + + def _getDigestStartPos(self, line, mode): + if mode == 0 or mode == 2: + return 5 + else: + return lfBytesLen(getDirname(line)) + + def afterBufhidden(self): + if self._diff_view_panel.isAllHidden(): + lfCmd("call timer_start(1, function('leaderf#Git#Cleanup', [{}]))".format(id(self))) + + def getSource(self, line): + """ + return a tuple like (b90f76fc1, bad07e644, R099, src/version.c, src/version2.c) + """ + if line == '': + return None + + file_name2 = "" + if "\t=>\t" in line: + # 'R050 hello world.txt\t=>\thello world2.txt' + # 'R050  hello world.txt\t=>\thello world2.txt' + tmp = line.split("\t=>\t") + file_name1 = tmp[0].split(None, 2 if self._show_icon else 1)[-1] + file_name2 = tmp[1] + else: + # 'M  runtime/syntax/nix.vim' + file_name1 = line.split()[-1] + + return self._getExplorer().getSourceInfo().get((file_name1, file_name2), + ("", "", "", file_name1, file_name2)) + + def _createPreviewWindow(self, config, source, line_num, jump_cmd): + self._preview_panel.create(self.createGitCommand(self._arguments, source), + config, + project_root=self._project_root) + self._preview_winid = self._preview_panel.getPreviewWinId() + self._setWinOptions(self._preview_winid) + + def getPreviewCommand(self, arguments_dict, source): + arguments_dict.update(self._arguments) + return GitDiffCommand(arguments_dict, source) + + def createGitCommand(self, arguments_dict, source): + if "owner" in arguments_dict: + return arguments_dict["owner"].getPreviewCommand(arguments_dict, source) + else: + return GitDiffCommand(arguments_dict, source) + + def _useExistingWindow(self, title, source, line_num, jump_cmd): + self.setOptionsForCursor() + + content = self._preview_panel.getContent(source) + if content is None: + self._preview_panel.createView(self.createGitCommand(self._arguments, source)) + else: + self._preview_panel.setContent(content) + + def vsplitDiff(self): + if "--cached" not in self._arguments: + if "extra" in self._arguments: + cmd = "git diff {} --raw -- {}".format(" ".join(self._arguments["extra"]), + self._arguments["current_file"]) + + outputs = ParallelExecutor.run(cmd, directory=self._project_root) + if len(outputs[0]) == 0: + lfPrintError("No diffs!") + return + + blob = outputs[0][0].split()[2] + cmd = "git cat-file -p {}".format(blob) + file_name = "LeaderF://{}:{}".format(blob, self._arguments["current_file"]) + else: + cmd = "git show :{}".format(self._arguments["current_file"]) + file_name = "LeaderF://:{}".format(self._arguments["current_file"]) + + win_ids = [int(lfEval("win_getid()")), 0] + lfCmd("keepa keepj abo vsp {}".format(file_name)) + win_ids[1] = int(lfEval("win_getid()")) + lfCmd("augroup Lf_Git_Diff | augroup END") + lfCmd("autocmd! Lf_Git_Diff BufWipeout call leaderf#Git#DiffOff({})" + .format(win_ids)) + lfCmd("call win_execute({}, 'autocmd! Lf_Git_Diff BufHidden,BufWipeout call leaderf#Git#DiffOff({})')" + .format(win_ids[0], win_ids)) + lfCmd("setlocal nobuflisted") + lfCmd("setlocal buftype=nofile") + lfCmd("setlocal bufhidden=wipe") + lfCmd("setlocal undolevels=-1") + lfCmd("setlocal noswapfile") + lfCmd("setlocal nospell") + + outputs = ParallelExecutor.run(cmd, directory=self._project_root) + vim.current.buffer[:] = outputs[0] + lfCmd("setlocal nomodifiable") + + for winid in win_ids: + lfCmd("call win_execute({}, 'diffthis')".format(winid)) + else: + if "extra" in self._arguments: + extra = " ".join(self._arguments["extra"]) + else: + extra = "" + + cmd = "git diff {} --cached --raw -- {}".format(extra, + self._arguments["current_file"]) + outputs = ParallelExecutor.run(cmd, directory=self._project_root) + if len(outputs[0]) > 0: + _, source = TreeView.generateSource(outputs[0][0]) + self._diff_view_panel.create(self._arguments, source, **{"mode": 't'}) + else: + lfPrintError("No diffs!") + + def startExplorer(self, win_pos, *args, **kwargs): + arguments_dict = kwargs.get("arguments", {}) + if "--recall" not in arguments_dict and self.checkWorkingDirectory() == False: + return + + if "--recall" not in arguments_dict: + self.setArguments(arguments_dict) + if ("--current-file" in arguments_dict + and vim.current.buffer.name + and not vim.current.buffer.options['bt'] + ): + file_name = vim.current.buffer.name + if " " in file_name: + file_name = file_name.replace(' ', r'\ ') + self._arguments["current_file"] = PurePath(lfRelpath(file_name)).as_posix() + if "-s" in self._arguments: + self.vsplitDiff() + else: + self._accept(self._arguments["current_file"], "") + + self._restoreOrigCwd() + return + + if "--recall" in arguments_dict: + super(GitExplManager, self).startExplorer(win_pos, *args, **kwargs) + elif "--directly" in self._arguments: + self._result_panel.create(self.createGitCommand(self._arguments, None)) + self._restoreOrigCwd() + elif "--explorer" in self._arguments: + uid = str(hex(int(time.time())))[-7:] + page = ExplorerPage(self._project_root, uid, self) + page.create(arguments_dict, GitDiffExplCommand(arguments_dict, uid)) + self._pages.add(page) + self._restoreOrigCwd() + else: + super(GitExplManager, self).startExplorer(win_pos, *args, **kwargs) + + def _afterEnter(self): + super(GitExplManager, self)._afterEnter() + + if lfEval("get(g:, 'Lf_ShowDevIcons', 1)") == '1': + winid = self._getInstance().getPopupWinId() if self._getInstance().getWinPos() == 'popup' else None + icon_pattern = r'^\S*\s*\zs__icon__' + self._match_ids.extend(matchaddDevIconsExtension(icon_pattern, winid)) + self._match_ids.extend(matchaddDevIconsExact(icon_pattern, winid)) + self._match_ids.extend(matchaddDevIconsDefault(icon_pattern, winid)) + + if self._getInstance().getWinPos() == 'popup': + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_gitDiffModification'', ''^[MRT]\S*'')')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_gitDiffAddition'', ''^[AC]\S*'')')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_gitDiffDeletion'', ''^[DU]'')')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + else: + id = int(lfEval(r'''matchadd('Lf_hl_gitDiffModification', '^[MRT]\S*')''')) + self._match_ids.append(id) + id = int(lfEval(r'''matchadd('Lf_hl_gitDiffAddition', '^[AC]\S*')''')) + self._match_ids.append(id) + id = int(lfEval(r'''matchadd('Lf_hl_gitDiffDeletion', '^[DU]')''')) + self._match_ids.append(id) + + def _accept(self, file, mode, *args, **kwargs): + if "-s" in self._arguments: + kwargs["mode"] = mode + self._acceptSelection(file, *args, **kwargs) + else: + super(GitExplManager, self)._accept(file, mode, *args, **kwargs) + + def _acceptSelection(self, *args, **kwargs): + if len(args) == 0 or args[0] == '': + return + + line = args[0] + source = self.getSource(line) + + if "accept" in self._arguments: + self._arguments["accept"](lfGetFilePath(source)) + elif "-s" in self._arguments: + kwargs["project_root"] = self._project_root + self._diff_view_panel.create(self._arguments, source, **kwargs) + else: + if kwargs.get("mode", '') == 't' and source not in self._result_panel.getSources(): + self._arguments["mode"] = 't' + lfCmd("tabnew") + else: + self._arguments["mode"] = '' + + tabpage_count = len(vim.tabpages) + + self._result_panel.create(self.createGitCommand(self._arguments, source), + self._selected_content) + + if kwargs.get("mode", '') == 't' and len(vim.tabpages) > tabpage_count: + tabmove() + + def cleanup(self): + self._diff_view_panel.cleanup() + + def cleanupExplorerPage(self, page): + self._pages.discard(page) + + +class GitLogExplManager(GitExplManager): + def __init__(self): + super(GitLogExplManager, self).__init__() + lfCmd("augroup Lf_Git_Log | augroup END") + lfCmd("autocmd! Lf_Git_Log FileType git call leaderf#Git#DefineSyntax()") + self._diff_view_panel = None + # key is commit id, value is ExplorerPage + self._pages = {} + + def _getExplorer(self): + if self._explorer is None: + self._explorer = GitLogExplorer() + return self._explorer + + def _getDigest(self, line, mode): + return line.lstrip(r"*\|_/ ") + + def _getDigestStartPos(self, line, mode): + return len(line) - len(line.lstrip(r"*\|_/ ")) + + def afterBufhidden(self): + if self._diff_view_panel.isAllHidden(): + lfCmd("call timer_start(1, function('leaderf#Git#Cleanup', [{}]))".format(id(self))) + + def getSource(self, line): + """ + return the hash + """ + line = line.lstrip(r"*\|_/ ") + if line == '': + return None + + return line.split(None, 1)[0] + + def _createPreviewWindow(self, config, source, line_num, jump_cmd): + if source is None: + return + + if "--current-line" in self._arguments and len(self._getExplorer().patches) > 0: + self._preview_panel.create(self.createGitCommand(self._arguments, source), + config, + buf_content=self._getExplorer().patches[source], + project_root=self._project_root) + else: + self._preview_panel.create(self.createGitCommand(self._arguments, source), + config, + project_root=self._project_root) + self._preview_winid = self._preview_panel.getPreviewWinId() + self._setWinOptions(self._preview_winid) + + def getPreviewCommand(self, arguments_dict, source): + return GitLogDiffCommand(arguments_dict, source) + + def createGitCommand(self, arguments_dict, commit_id): + return GitLogCommand(arguments_dict, commit_id) + + def _useExistingWindow(self, title, source, line_num, jump_cmd): + if source is None: + return + + self.setOptionsForCursor() + + content = self._preview_panel.getContent(source) + if content is None: + if "--current-line" in self._arguments and len(self._getExplorer().patches) > 0: + self._preview_panel.setContent(self._getExplorer().patches[source]) + else: + self._preview_panel.createView(self.createGitCommand(self._arguments, source)) + else: + self._preview_panel.setContent(content) + + def startExplorer(self, win_pos, *args, **kwargs): + arguments_dict = kwargs.get("arguments", {}) + if "--recall" not in arguments_dict and self.checkWorkingDirectory() == False: + return + + if "--recall" not in arguments_dict: + self.setArguments(arguments_dict) + if ("--current-file" in arguments_dict + and vim.current.buffer.name + and not vim.current.buffer.options['bt'] + ): + file_name = vim.current.buffer.name + if " " in file_name: + file_name = file_name.replace(' ', r'\ ') + self._arguments["current_file"] = PurePath(lfRelpath(file_name)).as_posix() + self._arguments["orig_name"] = self._getExplorer().orig_name + elif ("--current-line" in arguments_dict + and vim.current.buffer.name + and not vim.current.buffer.options['bt'] + ): + file_name = vim.current.buffer.name + if " " in file_name: + file_name = file_name.replace(' ', r'\ ') + self._arguments["current_file"] = PurePath(lfRelpath(file_name)).as_posix() + self._arguments["current_line_num"] = vim.current.window.cursor[0] + + if "--recall" in arguments_dict: + super(GitExplManager, self).startExplorer(win_pos, *args, **kwargs) + elif "--directly" in self._arguments: + self._result_panel.create(self.createGitCommand(self._arguments, None)) + self._restoreOrigCwd() + else: + super(GitExplManager, self).startExplorer(win_pos, *args, **kwargs) + + def _afterEnter(self): + super(GitExplManager, self)._afterEnter() + + if self._getInstance().getWinPos() == 'popup': + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_gitGraph1'', ''^|'')')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_gitGraph2'', ''^[*\|_/ ]\{2}\zs|'')')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_gitGraph3'', ''^[*\|_/ ]\{4}\zs|'')')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_gitGraph4'', ''\(^[*\|_/ ]\{6,}\)\@<=|'')')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_gitGraphSlash'', ''\(^[*\|_/ ]\{-}\)\@<=[\/]'')')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_gitHash'', ''\(^[*\|_/ ]*\)\@<=[0-9A-Fa-f]\+'')')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_gitRefNames'', ''^[*\|_/ ]*[0-9A-Fa-f]\+\s*\zs(.\{-})'')')""" + % self._getInstance().getPopupWinId()) + id = int(lfEval("matchid")) + else: + id = int(lfEval(r'''matchadd('Lf_hl_gitGraph1', '^|')''')) + self._match_ids.append(id) + id = int(lfEval(r'''matchadd('Lf_hl_gitGraph2', '^[*\|_/ ]\{2}\zs|')''')) + self._match_ids.append(id) + id = int(lfEval(r'''matchadd('Lf_hl_gitGraph3', '^[*\|_/ ]\{4}\zs|')''')) + self._match_ids.append(id) + id = int(lfEval(r'''matchadd('Lf_hl_gitGraph4', '\(^[*\|_/ ]\{6,}\)\@<=|')''')) + self._match_ids.append(id) + id = int(lfEval(r'''matchadd('Lf_hl_gitGraphSlash', '\(^[*\|_/ ]\{-}\)\@<=[\/]')''')) + self._match_ids.append(id) + id = int(lfEval(r'''matchadd('Lf_hl_gitHash', '\(^[*\|_/ ]*\)\@<=[0-9A-Fa-f]\+')''')) + self._match_ids.append(id) + id = int(lfEval(r'''matchadd('Lf_hl_gitRefNames', '^[*\|_/ ]*[0-9A-Fa-f]\+\s*\zs(.\{-})')''')) + self._match_ids.append(id) + + def _accept(self, file, mode, *args, **kwargs): + super(GitExplManager, self)._accept(file, mode, *args, **kwargs) + + def _createExplorerPage(self, commit_id, target_path=None, line_num=None): + if commit_id in self._pages: + vim.current.tabpage = self._pages[commit_id].tabpage + else: + self._pages[commit_id] = ExplorerPage(self._project_root, commit_id, self) + self._pages[commit_id].create(self._arguments, + GitLogExplCommand(self._arguments, commit_id), + target_path=target_path, + line_num=line_num) + + def _getPathAndLineNum(self, commit_id): + patch = self._getExplorer().patches[commit_id] + file_path = patch[0].rsplit(None, 1)[1][2:] + line_num = 1 + count = 0 + found = False + for line in patch: + if line.startswith("@@"): + found = True + line_numbers = line.split("+", 1)[1].split(None, 1)[0] + if "," in line_numbers: + line_num, _ = line_numbers.split(",") + line_num = int(line_num) + else: + # @@ -1886 +1893 @@ + line_num = int(line_numbers) + elif found: + if line.startswith("-"): + pass + elif line.startswith("+"): + line_num += count + break + else: + count += 1 + + return (file_path, line_num) + + def _acceptSelection(self, *args, **kwargs): + if len(args) == 0 or args[0] == '': + return + + line = args[0] + commit_id = self.getSource(line) + if commit_id is None: + return + + if "--current-file" in self._arguments and "current_file" in self._arguments: + file_name = self._getExplorer().orig_name[commit_id] + if "--explorer" in self._arguments: + self._createExplorerPage(commit_id, file_name) + else: + if self._diff_view_panel is None: + self._diff_view_panel = DiffViewPanel(self.afterBufhidden) + + self._diff_view_panel.setCommitId(commit_id) + cmd = "git log -1 --follow --pretty= --no-color --raw {} -- {}".format(commit_id, + file_name) + outputs = ParallelExecutor.run(cmd, directory=self._project_root) + if len(outputs[0]) > 0: + _, source = TreeView.generateSource(outputs[0][0]) + kwargs["project_root"] = self._project_root + self._diff_view_panel.create(self._arguments, source, **kwargs) + elif "--current-line" in self._arguments and len(self._getExplorer().patches) > 0: + if "--explorer" in self._arguments: + file_path, line_num = self._getPathAndLineNum(commit_id) + self._createExplorerPage(commit_id, file_path, line_num) + else: + if kwargs.get("mode", '') == 't' and commit_id not in self._result_panel.getSources(): + self._arguments["mode"] = 't' + lfCmd("tabnew") + + tabpage_count = len(vim.tabpages) + + self._result_panel.create(self.createGitCommand(self._arguments, commit_id), + self._getExplorer().patches[commit_id]) + + if kwargs.get("mode", '') == 't' and len(vim.tabpages) > tabpage_count: + tabmove() + elif "--explorer" in self._arguments: + self._createExplorerPage(commit_id) + else: + if kwargs.get("mode", '') == 't' and commit_id not in self._result_panel.getSources(): + self._arguments["mode"] = 't' + lfCmd("tabnew") + + tabpage_count = len(vim.tabpages) + + self._result_panel.create(self.createGitCommand(self._arguments, commit_id), + self._selected_content) + + if kwargs.get("mode", '') == 't' and len(vim.tabpages) > tabpage_count: + tabmove() + + def cleanup(self): + if self._diff_view_panel is not None: + self._diff_view_panel.cleanup() + + def cleanupExplorerPage(self, page): + del self._pages[page.commit_id] + + +class GitBlameExplManager(GitExplManager): + def __init__(self): + super(GitBlameExplManager, self).__init__() + self._blame_panels = {} + # key is commit_id, value is ExplorerPage + self._pages = {} + # key is buffer number + self._blame_infos = {} + # key is buffer number + self._initial_changedtick = {} + lfCmd("let g:lf_blame_manager_id = {}".format(id(self))) + if lfEval("hlexists('Lf_hl_gitInlineBlame')") == '0': + lfCmd("call leaderf#colorscheme#popup#load('{}', '{}')" + .format("git", lfEval("get(g:, 'Lf_PopupColorscheme', 'default')"))) + self._blame_timer_id = None + + def discardPanel(self, project_root): + del self._blame_panels[project_root] + + def createGitCommand(self, arguments_dict, commit_id): + return GitBlameCommand(arguments_dict, commit_id) + + def getPreviewCommand(self, arguments_dict, source): + return GitLogDiffCommand(arguments_dict, source) + + def defineMaps(self, winid): + lfCmd("call win_execute({}, 'call leaderf#Git#BlameMaps({})')" + .format(winid, id(self))) + + def setOptions(self, winid): + lfCmd("call win_execute({}, 'setlocal nobuflisted')".format(winid)) + lfCmd("call win_execute({}, 'setlocal buftype=nofile')".format(winid)) + lfCmd("call win_execute({}, 'setlocal bufhidden=hide')".format(winid)) + lfCmd("call win_execute({}, 'setlocal undolevels=-1')".format(winid)) + lfCmd("call win_execute({}, 'setlocal noswapfile')".format(winid)) + lfCmd("call win_execute({}, 'setlocal nospell')".format(winid)) + + def getLineNumber(self, commit_id, file_name, line_num, text, project_root): + cmd = 'git log -1 -p --pretty= -U0 --follow {} -- {}'.format(commit_id, file_name) + outputs = ParallelExecutor.run(cmd, directory=project_root) + found = False + for i, line in enumerate(outputs[0]): + # @@ -2,11 +2,21 @@ + if line.startswith("@@"): + line_numbers = line.split("+", 1)[1].split(None, 1)[0] + if "," in line_numbers: + start, count = line_numbers.split(",") + start = int(start) + count = int(count) + else: + # @@ -1886 +1893 @@ + start = int(line_numbers) + count = 1 + + if start + count > line_num: + found = True + orig_line_numbers = line.split(None, 2)[1].lstrip("-") + if "," in orig_line_numbers: + orig_start, orig_count = orig_line_numbers.split(",") + orig_start = int(orig_start) + orig_count = int(orig_count) + else: + orig_start = int(orig_line_numbers) + orig_count = 1 + + if orig_count == 1 or orig_count == 0: + return orig_start + elif orig_count == count: + return orig_start + line_num - start + else: + ratio = 0 + index = i + 1 + for j, line in enumerate(outputs[0][index: index + orig_count], index): + r = SequenceMatcher(None, text, line).ratio() + if r > ratio: + ratio = r + index = j + + return orig_start + index - i - 1 + + return line_num + + def blamePrevious(self): + if vim.current.line == "": + return + + if vim.current.line.startswith('^'): + lfPrintError("First commit!") + return + + commit_id = vim.current.line.lstrip('^').split(None, 1)[0] + if commit_id.startswith('0000000'): + lfPrintError("Not Committed Yet!") + return + + line_num, file_name = vim.current.line.rsplit('\t', 1)[1].split(None, 1) + line_num = int(line_num) + project_root = lfEval("b:lf_blame_project_root") + blame_panel = self._blame_panels[project_root] + blame_buffer_name = vim.current.buffer.name + alternate_winid = blame_panel.getAlternateWinid(blame_buffer_name) + blame_winid = lfEval("win_getid()") + + alternate_buffer_num = int(lfEval("winbufnr({})".format(alternate_winid))) + text = vim.buffers[alternate_buffer_num][vim.current.window.cursor[0] - 1] + line_num = self.getLineNumber(commit_id, file_name, line_num, text, project_root) + top_line_delta = vim.current.window.cursor[0] - int(lfEval("line('w0')")) + + if commit_id not in blame_panel.getBlameDict(blame_buffer_name): + cmd = 'git log -2 --pretty="%H" --name-status --follow {} -- {}'.format(commit_id, + file_name) + # output is as below: + + # a7cdd68e0f9e891e6f5def7b2b657d07d92a3675 + # + # R064 tui.py src/tui.py + # 5a0cd5103deba164a6fb33a5a3f67fb3a5dcf378 + # + # M tui.py + outputs = ParallelExecutor.run(cmd, directory=project_root) + name_stat = outputs[0][2] + if name_stat.startswith("A") or name_stat.startswith("C"): + lfPrintError("First commit of current file!") + return + else: + if name_stat.startswith("R"): + orig_name = name_stat.split()[1] + else: + orig_name = file_name + + parent_commit_id = outputs[0][3] + + blame_win_width = vim.current.window.width + blame_panel.getBlameStack(blame_buffer_name).append( + ( + vim.current.buffer[:], + lfEval("winbufnr({})".format(alternate_winid)), + blame_win_width, + vim.current.window.cursor[0], + int(lfEval("line('w0')")) + ) + ) + + alternate_buffer_name = "LeaderF://{}:{}".format(parent_commit_id[:7], orig_name) + blame_buffer_dict = blame_panel.getBlameBufferDict(blame_buffer_name) + + lfCmd("noautocmd call win_gotoid({})".format(alternate_winid)) + if alternate_buffer_name in blame_buffer_dict: + blame_buffer, alternate_buffer_num = blame_buffer_dict[alternate_buffer_name] + lfCmd("noautocmd buffer {}".format(alternate_buffer_num)) + lfCmd("noautocmd norm! {}Gzt{}G0".format(line_num-top_line_delta, line_num)) + top_line = lfEval("line('w0')") + + lfCmd("noautocmd call win_gotoid({})".format(blame_winid)) + else: + date_format = self._arguments.get("--date", ["iso"])[0] + if date_format in ["iso", "iso-strict", "short"]: + cmd = [GitBlameCommand.buildCommand(self._arguments, parent_commit_id, orig_name), + "git show {}:{}".format(parent_commit_id, orig_name), + ] + else: + arguments_dict2 = self._arguments.copy() + arguments_dict2["-c"] = [] + + arguments_dict3 = arguments_dict2.copy() + arguments_dict3["--date"] = ["unix"] + + cmd = [GitBlameCommand.buildCommand(self._arguments, parent_commit_id, orig_name), + "git show {}:{}".format(parent_commit_id, orig_name), + GitBlameCommand.buildCommand(arguments_dict2, parent_commit_id, orig_name), + GitBlameCommand.buildCommand(arguments_dict3, parent_commit_id, orig_name), + ] + + outputs = ParallelExecutor.run(*cmd, directory=project_root) + line_num_width = len(str(len(outputs[1]))) + 1 + blame_buffer = [BlamePanel.formatLine(self._arguments, line_num_width, line) + for line in outputs[0] + ] + + lfCmd("noautocmd enew") + self.setOptions(alternate_winid) + + vim.current.buffer[:] = outputs[1] + vim.current.buffer.name = alternate_buffer_name + lfCmd("doautocmd filetypedetect BufNewFile {}".format(orig_name)) + lfCmd("setlocal nomodifiable") + alternate_buffer_num = vim.current.buffer.number + + lfCmd("noautocmd norm! {}Gzt{}G0".format(line_num-top_line_delta, line_num)) + top_line = lfEval("line('w0')") + + lfCmd("noautocmd call win_gotoid({})".format(blame_winid)) + blame_view = blame_panel.getBlameView(blame_buffer_name) + if date_format in ["iso", "iso-strict", "short"]: + blame_view.highlightRestHeatDate1(date_format, outputs[0]) + else: + blame_view.highlightRestHeatDate2(outputs[2], outputs[3]) + + # here we are in the blame window + lfCmd("setlocal modifiable") + vim.current.buffer[:] = blame_buffer + lfCmd("setlocal nomodifiable") + if len(blame_buffer) > 0: + line_width = blame_buffer[0].rfind('\t') + line_num_width = max(len(str(len(vim.current.buffer))) + 1, + int(lfEval('&numberwidth'))) + lfCmd("vertical resize {}".format(line_width + line_num_width)) + lfCmd("noautocmd norm! {}Gzt{}G0".format(top_line, line_num)) + lfCmd("call win_execute({}, 'setlocal scrollbind')".format(alternate_winid)) + + blame_win_width = vim.current.window.width + blame_panel.getBlameDict(blame_buffer_name)[commit_id] = ( + blame_buffer, + alternate_buffer_num, + blame_win_width + ) + blame_buffer_dict[alternate_buffer_name] = (blame_buffer, alternate_buffer_num) + else: + blame_panel.getBlameStack(blame_buffer_name).append( + ( + vim.current.buffer[:], + lfEval("winbufnr({})".format(alternate_winid)), + vim.current.window.width, + vim.current.window.cursor[0], + int(lfEval("line('w0')")) + ) + ) + (blame_buffer, + alternate_buffer_num, + blame_win_width + ) = blame_panel.getBlameDict(blame_buffer_name)[commit_id] + lfCmd("noautocmd call win_gotoid({})".format(alternate_winid)) + lfCmd("noautocmd buffer {}".format(alternate_buffer_num)) + lfCmd("noautocmd norm! {}Gzt{}G0".format(line_num-top_line_delta, line_num)) + top_line = lfEval("line('w0')") + + lfCmd("noautocmd call win_gotoid({})".format(blame_winid)) + lfCmd("setlocal modifiable") + vim.current.buffer[:] = blame_buffer + lfCmd("setlocal nomodifiable") + lfCmd("vertical resize {}".format(blame_win_width)) + lfCmd("noautocmd norm! {}Gzt{}G0".format(top_line, line_num)) + + if lfEval("exists('b:lf_preview_winid') && winbufnr(b:lf_preview_winid) != -1") == '1': + if lfEval("has('nvim')") == '1': + lfCmd("call nvim_win_close(b:lf_preview_winid, 1)") + else: + lfCmd("call popup_close(b:lf_preview_winid)") + self.preview() + + def blameNext(self): + project_root = lfEval("b:lf_blame_project_root") + blame_panel = self._blame_panels[project_root] + blame_stack = blame_panel.getBlameStack(vim.current.buffer.name) + if len(blame_stack) == 0: + return + + blame_buffer, alternate_buffer_num, blame_win_width, cursor_line, top_line = blame_stack.pop() + blame_winid = lfEval("win_getid()") + alternate_winid = blame_panel.getAlternateWinid(vim.current.buffer.name) + + lfCmd("noautocmd call win_gotoid({})".format(alternate_winid)) + lfCmd("noautocmd buffer {}".format(alternate_buffer_num)) + lfCmd("noautocmd norm! {}Gzt{}G0".format(top_line, cursor_line)) + + lfCmd("noautocmd call win_gotoid({})".format(blame_winid)) + lfCmd("setlocal modifiable") + vim.current.buffer[:] = blame_buffer + lfCmd("setlocal nomodifiable") + lfCmd("vertical resize {}".format(blame_win_width)) + lfCmd("noautocmd norm! {}Gzt{}G0".format(top_line, cursor_line)) + + if lfEval("exists('b:lf_preview_winid') && winbufnr(b:lf_preview_winid) != -1") == '1': + if lfEval("has('nvim')") == '1': + lfCmd("call nvim_win_close(b:lf_preview_winid, 1)") + else: + lfCmd("call popup_close(b:lf_preview_winid)") + self.preview() + + def showCommitMessage(self): + if vim.current.line == "": + return + + commit_id = vim.current.line.lstrip('^').split(None, 1)[0] + if commit_id.startswith('0000000'): + lfPrintError("Not Committed Yet!") + return + + project_root = lfEval("b:lf_blame_project_root") + cmd = "cd {} && git show {} -s --decorate --pretty=fuller".format(project_root, commit_id) + lfCmd("""call leaderf#Git#ShowCommitMessage(systemlist('{}'))""".format(cmd)) + + def open(self): + if vim.current.line == "": + return + + commit_id = vim.current.line.lstrip('^').split(None, 1)[0] + if commit_id.startswith('0000000'): + lfPrintError("Not Committed Yet!") + return + + line_num, file_name = vim.current.line.rsplit('\t', 1)[1].split(None, 1) + + if commit_id in self._pages: + vim.current.tabpage = self._pages[commit_id].tabpage + self._pages[commit_id].locateFile(file_name, line_num, False) + else: + project_root = lfEval("b:lf_blame_project_root") + self._pages[commit_id] = ExplorerPage(project_root, commit_id, self) + self._pages[commit_id].create(self._arguments, + GitLogExplCommand(self._arguments, commit_id), + target_path=file_name, + line_num=line_num) + + def generateConfig(self, project_root): + maxheight = int(lfEval("get(g:, 'Lf_GitBlamePreviewHeight', {})" + .format(vim.current.window.height // 3))) + if maxheight < 5: + maxheight = 5 + + blame_panel = self._blame_panels[project_root] + alternate_winid = blame_panel.getAlternateWinid(vim.current.buffer.name) + screenpos = lfEval("screenpos({}, {}, {})".format(alternate_winid, + vim.current.window.cursor[0], + 1)) + col = int(screenpos["col"]) + maxwidth = int(lfEval("&columns")) - col + + if lfEval("has('nvim')") == '1': + row = int(screenpos["row"]) + + popup_borders = lfEval("g:Lf_PopupBorders") + borderchars = [ + [popup_borders[4], "Lf_hl_popupBorder"], + [popup_borders[0], "Lf_hl_popupBorder"], + [popup_borders[5], "Lf_hl_popupBorder"], + [popup_borders[1], "Lf_hl_popupBorder"], + [popup_borders[6], "Lf_hl_popupBorder"], + [popup_borders[2], "Lf_hl_popupBorder"], + [popup_borders[7], "Lf_hl_popupBorder"], + [popup_borders[3], "Lf_hl_popupBorder"] + ] + + if row > maxheight + 2: + anchor = "SW" + row -= 1 + else: + anchor = "NW" + + config = { + "title": " Preview ", + "title_pos": "center", + "relative": "editor", + "anchor": anchor, + "row": row, + "col": col - 1, + "width": maxwidth, + "height": maxheight, + "zindex": 20482, + "noautocmd": 1, + "border": borderchars, + "style": "minimal", + } + else: + config = { + "title": " Preview ", + "maxwidth": maxwidth, + "minwidth": maxwidth, + "maxheight": maxheight, + "minheight": maxheight, + "zindex": 20482, + "pos": "botleft", + "line": "cursor-1", + "col": col, + "scrollbar": 0, + "padding": [0, 0, 0, 0], + "border": [1, 1, 1, 1], + "borderchars": lfEval("g:Lf_PopupBorders"), + "borderhighlight": ["Lf_hl_popupBorder"], + "filter": "leaderf#Git#PreviewFilter", + "mapping": 0, + } + + return config + + def preview(self): + if vim.current.line == "": + return + + commit_id = vim.current.line.lstrip('^').split(None, 1)[0] + + line_num, file_name = vim.current.line.rsplit('\t', 1)[1].split(None, 1) + line_num = int(line_num) + + if lfEval("has('nvim')") == '1': + lfCmd("let b:lf_blame_preview_cursorline = line('.')") + lfCmd("let b:lf_blame_winid = win_getid()") + if lfEval("exists('b:lf_preview_winid') && winbufnr(b:lf_preview_winid) != -1") == '1': + lfCmd("call nvim_win_close(b:lf_preview_winid, 1)") + + project_root = lfEval("b:lf_blame_project_root") + if commit_id.startswith('0000000'): + cmd = GitCustomizeCommand(self._arguments, "git diff @ -- {}".format(file_name), + None, "git", "setlocal filetype=git") + else: + cmd = GitShowCommand(self._arguments, commit_id, file_name) + outputs = ParallelExecutor.run(cmd.getCommand(), directory=project_root) + self._preview_panel.create(cmd, self.generateConfig(project_root), outputs[0]) + preview_winid = self._preview_panel.getPreviewWinId() + self._setWinOptions(preview_winid) + lfCmd("let b:lf_preview_winid = {}".format(preview_winid)) + if lfEval("has('nvim')") == '1': + lfCmd("call nvim_win_set_option(%d, 'number', v:false)" % preview_winid) + else: + lfCmd("call win_execute({}, 'setlocal nonumber')".format(preview_winid)) + lfCmd("call win_execute({}, 'let b:lf_blame_manager_id = {}')".format(preview_winid, + id(self))) + + self.gotoLine(preview_winid, line_num) + lfCmd("call win_execute({}, 'setlocal filetype=diff')".format(preview_winid)) + + def quit(self): + if lfEval("has('nvim')") == '1': + if lfEval("exists('b:lf_preview_winid') && winbufnr(b:lf_preview_winid) != -1") == '1': + lfCmd("call nvim_win_close(b:lf_preview_winid, 1)") + + lfCmd("bwipe") + + def gotoLine(self, winid, line_num): + found = False + current_line = 0 + view_content = self._preview_panel.getViewContent() + for i, line in enumerate(view_content, 1): + if found: + if not line.startswith("-"): + current_line += 1 + if current_line == line_num: + lfCmd("call win_execute({}, 'norm! {}Gzz')".format(winid, i)) + break + # @@ -2,11 +2,21 @@ + elif line.startswith("@@"): + line_numbers = line.split("+", 1)[1].split(None, 1)[0] + if "," in line_numbers: + start, count = line_numbers.split(",") + start = int(start) + count = int(count) + else: + # @@ -1886 +1893 @@ + start = int(line_numbers) + count = 1 + + if start + count > line_num: + found = True + current_line = start - 1 + + def startInlineBlame(self, tmp_file_name): + if lfEval("exists('b:lf_blame_changedtick')") == "1": + return + + lfCmd("let b:lf_blame_changedtick = b:changedtick") + self._initial_changedtick[vim.current.buffer.number] = vim.current.buffer.vars["changedtick"] + + file_name = vim.current.buffer.name + if " " in file_name: + file_name = file_name.replace(' ', r'\ ') + + if tmp_file_name is None: + git_cmd = (r'git blame --line-porcelain -- {} | grep "^author \|^author-time\|^summary"' + .format(file_name)) + else: + git_cmd = (r'git blame --line-porcelain --contents {} -- {} | grep "^author \|^author-time\|^summary"' + .format(tmp_file_name, file_name)) + + self._blame_info_list = [] + self._read_finished = 0 + + reader_thread = threading.Thread(target=self._readBlameInfo, args=(git_cmd, lfEval("&encoding"))) + reader_thread.daemon = True + reader_thread.start() + + if self._blame_timer_id is not None: + lfCmd("call timer_stop(%s)" % self._blame_timer_id) + self._blame_timer_id = lfEval("timer_start(50, function('leaderf#Git#InlineBlame', [%d]), {'repeat': -1})" % id(self)) + + lfCmd("let g:Lf_git_inline_blame_enabled = 1") + + def _readBlameInfo(self, git_cmd, encoding): + try: + executor = AsyncExecutor() + content = executor.execute(git_cmd, + encoding=encoding, + cwd=self._project_root + ) + self._blame_info_list = list(content) + self._read_finished = 1 + except Exception: + # traceback.print_exc() + # traceback.print_stack() + self._read_finished = 1 + + def inlineBlame(self): + if self._read_finished == 0: + return + + if self._blame_timer_id is not None: + lfCmd("call timer_stop(%s)" % self._blame_timer_id) + self._blame_timer_id = None + + lfCmd("augroup Lf_Git_Blame | augroup END") + lfCmd("autocmd! Lf_Git_Blame BufRead * silent call leaderf#Git#StartInlineBlame()") + lfCmd("autocmd! Lf_Git_Blame BufWinEnter * silent call leaderf#Git#StartInlineBlame()") + + lfCmd("let b:lf_blame_line_number = line('.')") + + if len(self._blame_info_list) == 0: + return + + blame_list = iter(self._blame_info_list) + i = 0 + self._blame_infos[vim.current.buffer.number] = {} + blame_infos = self._blame_infos[vim.current.buffer.number] + if lfEval("has('nvim')") == '1': + lfCmd("let ns_id = nvim_create_namespace('LeaderF_Git_Blame_0')") + for i, (author, author_time, summary) in enumerate(itertools.zip_longest(blame_list, + blame_list, + blame_list)): + author = author.split(None, 1)[1].replace("External file (--contents)", "Not Committed Yet") + author_time = int(author_time.split(None, 1)[1]) + summary = summary.split(None, 1)[1] + mark_id = i + 1 + blame_infos[mark_id] = (vim.current.buffer[i], author, author_time, summary) + lfCmd("call nvim_buf_set_extmark(0, ns_id, %d, 0, {'id': %d})" % (i, mark_id)) + else: + for i, (author, author_time, summary) in enumerate(itertools.zip_longest(blame_list, + blame_list, + blame_list)): + author = author.split(None, 1)[1].replace("External file (--contents)", "Not Committed Yet") + author_time = int(author_time.split(None, 1)[1]) + summary = summary.split(None, 1)[1] + prop_id = i + 1 + blame_infos[prop_id] = (vim.current.buffer[i], author, author_time, summary) + lfCmd('call prop_add(%d, 1, {"type": "Lf_hl_gitTransparent", "length": 0, "id": %d})' + % (i+1, prop_id)) + + line_number = vim.current.window.cursor[0] + _, author, author_time, summary = blame_infos[line_number] + author_time = self.formated_time(author_time) + blame_info = "{} • {} • {}".format(author, author_time, summary) + if lfEval("has('nvim')") == '1': + lfCmd("let ns_id = nvim_create_namespace('LeaderF_Git_Blame_1')") + lfCmd("call nvim_buf_set_extmark(0, ns_id, line('.') - 1, 0, {'id': 1, 'virt_text': [[' %s', 'Lf_hl_gitInlineBlame']]})" + % (escQuote(blame_info))) + else: + lfCmd("call prop_add(line('.'), 0, {'type': 'Lf_hl_gitInlineBlame', 'text': ' %s'})" + % (escQuote(blame_info))) + + lfCmd("autocmd! Lf_Git_Blame CursorMoved call leaderf#Git#UpdateInlineBlame({})" + .format(id(self))) + lfCmd("autocmd! Lf_Git_Blame InsertEnter call leaderf#Git#HideInlineBlame({})" + .format(id(self))) + lfCmd("autocmd! Lf_Git_Blame InsertLeave call leaderf#Git#ShowInlineBlame({})" + .format(id(self))) + + def formated_time(self, timestamp): + time_format = lfEval("get(g:, 'Lf_GitBlameTimeFormat', '')") + if time_format == "": + return self.relative_time(timestamp) + else: + return datetime.fromtimestamp(timestamp).strftime(time_format) + + def relative_time(self, timestamp): + def format_time_unit(value, unit): + if value == 1: + return "{} {}".format(value, unit) + else: + return "{} {}s".format(value, unit) + + current_time = datetime.now() + past_time = datetime.fromtimestamp(timestamp) + + delta = current_time - past_time + + years = delta.days // 365 + months = (delta.days % 365) // 30 + if years >= 1: + if months == 0: + return "{} ago".format(format_time_unit(years, "year")) + else: + return "{}, {} ago".format(format_time_unit(years, "year"), + format_time_unit(months, "month")) + elif months >= 1: + return "{} ago".format(format_time_unit(months, "month")) + else: + days = delta.days + if days >= 7: + weeks = days // 7 + return "{} ago".format(format_time_unit(weeks, "week")) + elif days >= 1: + return "{} ago".format(format_time_unit(days, "day")) + else: + hours = delta.seconds // 3600 + if hours >= 1: + return "{} ago".format(format_time_unit(hours, "hour")) + else: + minutes = delta.seconds // 60 + if minutes >= 1: + return "{} ago".format(format_time_unit(minutes, "minute")) + else: + return "{} ago".format(format_time_unit(delta.seconds, "second")) + + def updateInlineBlame(self): + if (lfEval("b:lf_blame_line_number == line('.')") == '1' + and lfEval("b:lf_blame_changedtick == b:changedtick") == '1'): + return + + self.showInlineBlame() + + def showInlineBlame(self): + if lfEval("has('nvim')") == '1': + self.nvim_showInlineBlame() + return + + lfCmd("let b:lf_blame_line_number = line('.')") + lfCmd("let b:lf_blame_changedtick = b:changedtick") + + lfCmd("call prop_remove({'type': 'Lf_hl_gitInlineBlame'})") + prop_list = lfEval("prop_list(line('.'), {'types':['Lf_hl_gitTransparent']})") + if len(prop_list) > 0: + prop_id = int(prop_list[0]["id"]) + line, author, author_time, summary = self._blame_infos[vim.current.buffer.number][prop_id] + if (vim.current.buffer.vars["changedtick"] == self._initial_changedtick[vim.current.buffer.number] + or vim.current.line == line): + author_time = self.formated_time(author_time) + blame_info = "{} • {} • {}".format(author, author_time, summary) + lfCmd("call prop_add(line('.'), 0, {'type': 'Lf_hl_gitInlineBlame', 'text': ' %s'})" + % (escQuote(blame_info))) + else: + lfCmd("call prop_add(line('.'), 0, {'type': 'Lf_hl_gitInlineBlame', 'text': ' Not Committed Yet'})") + else: + lfCmd("call prop_add(line('.'), 0, {'type': 'Lf_hl_gitInlineBlame', 'text': ' Not Committed Yet'})") + + def nvim_showInlineBlame(self): + lfCmd("let b:lf_blame_line_number = line('.')") + lfCmd("let b:lf_blame_changedtick = b:changedtick") + + lfCmd("let ns_id_1 = nvim_create_namespace('LeaderF_Git_Blame_1')") + lfCmd("call nvim_buf_del_extmark(0, ns_id_1, 1)") + + lfCmd("let ns_id_0 = nvim_create_namespace('LeaderF_Git_Blame_0')") + mark_list = lfEval("nvim_buf_get_extmarks(0, ns_id_0, [line('.')-1, 0], [line('.')-1, -1], {})") + if len(mark_list) > 0: + mark_id = int(mark_list[0][0]) + line, author, author_time, summary = self._blame_infos[vim.current.buffer.number][mark_id] + if (vim.current.buffer.vars["changedtick"] == self._initial_changedtick[vim.current.buffer.number] + or vim.current.line == line): + author_time = self.formated_time(author_time) + blame_info = "{} • {} • {}".format(author, author_time, summary) + if lfEval("has('nvim')") == '1': + lfCmd("call nvim_buf_set_extmark(0, ns_id_1, line('.') - 1, 0, {'id': 1, 'virt_text': [[' %s', 'Lf_hl_gitInlineBlame']]})" + % (escQuote(blame_info))) + else: + lfCmd("call prop_add(line('.'), 0, {'type': 'Lf_hl_gitInlineBlame', 'text': ' %s'})" + % (escQuote(blame_info))) + else: + if lfEval("has('nvim')") == '1': + lfCmd("call nvim_buf_set_extmark(0, ns_id_1, line('.') - 1, 0, {'id': 1, 'virt_text': [[ ' Not Committed Yet', 'Lf_hl_gitInlineBlame']]})") + else: + lfCmd("call prop_add(line('.'), 0, {'type': 'Lf_hl_gitInlineBlame', 'text': ' Not Committed Yet'})") + else: + if lfEval("has('nvim')") == '1': + lfCmd("call nvim_buf_set_extmark(0, ns_id_1, line('.') - 1, 0, {'id': 1, 'virt_text': [[ ' Not Committed Yet', 'Lf_hl_gitInlineBlame']]})") + else: + lfCmd("call prop_add(line('.'), 0, {'type': 'Lf_hl_gitInlineBlame', 'text': ' Not Committed Yet'})") + + def hideInlineBlame(self): + if lfEval("has('nvim')") == '1': + lfCmd("let ns_id_1 = nvim_create_namespace('LeaderF_Git_Blame_1')") + lfCmd("call nvim_buf_del_extmark(0, ns_id_1, 1)") + else: + lfCmd("call prop_remove({'type': 'Lf_hl_gitInlineBlame'})") + + def disableInlineBlame(self): + lfCmd("let g:Lf_git_inline_blame_enabled = 0") + lfCmd("augroup Lf_Git_Blame | au! | augroup END") + buffers = {b.number for b in vim.buffers} + if lfEval("has('nvim')") == '1': + for buffer_num in self._blame_infos: + if buffer_num not in buffers: + continue + + lfCmd("call leaderf#Git#RemoveExtmarks(%d)" % buffer_num) + del vim.buffers[buffer_num].vars["lf_blame_changedtick"] + else: + for buffer_num in self._blame_infos: + if buffer_num not in buffers: + continue + + lfCmd("call prop_remove({'type': 'Lf_hl_gitInlineBlame', 'bufnr': %d})" + % buffer_num) + lfCmd("call prop_remove({'type': 'Lf_hl_gitTransparent', 'bufnr': %d})" + % buffer_num) + del vim.buffers[buffer_num].vars["lf_blame_changedtick"] + + self._blame_infos = {} + self._initial_changedtick = {} + + def startExplorer(self, win_pos, *args, **kwargs): + arguments_dict = kwargs.get("arguments", {}) + if self.checkWorkingDirectory(**arguments_dict) == False: + return + + self.setArguments(arguments_dict) + + if lfEval("exists('b:lf_blame_file_name')") == "1": + buf_winid = int(lfEval("bufwinid(b:lf_blame_file_name)")) + if buf_winid != -1: + lfCmd("call win_gotoid({})".format(buf_winid)) + else: + lfCmd("bel vsp {}".format(lfEval("b:lf_blame_file_name"))) + + if vim.current.buffer.name and not vim.current.buffer.options['bt']: + if not vim.current.buffer.name.startswith(self._project_root): + lfPrintError("fatal: '{}' is outside repository at '{}'" + .format(lfRelpath(vim.current.buffer.name), self._project_root)) + else: + self._arguments["blamed_file_name"] = vim.current.buffer.name + tmp_file_name = None + if vim.current.buffer.options["modified"]: + if sys.version_info >= (3, 0): + tmp_file = partial(tempfile.NamedTemporaryFile, + encoding=lfEval("&encoding")) + else: + tmp_file = tempfile.NamedTemporaryFile + + with tmp_file(mode='w+', delete=False) as f: + for line in vim.current.buffer: + f.write(line + '\n') + tmp_file_name = f.name + self._arguments["--contents"] = [tmp_file_name] + + if "--inline" in self._arguments: + self.startInlineBlame(tmp_file_name) + else: + if self._project_root not in self._blame_panels: + self._blame_panels[self._project_root] = BlamePanel(self) + + self._blame_panels[self._project_root].create(arguments_dict, + self.createGitCommand(self._arguments, + None), + project_root=self._project_root) + if tmp_file_name is not None: + os.remove(tmp_file_name) + else: + if arguments_dict.get('autocmd', None) == None: + lfPrintError("fatal: no such path '{}' in HEAD".format(vim.current.buffer.name)) + + self._restoreOrigCwd() + + def cleanupExplorerPage(self, page): + del self._pages[page.commit_id] + + +class GitStatusExplManager(GitExplManager): + def __init__(self): + super(GitStatusExplManager, self).__init__() + self._pages = set() + + def startExplorer(self, win_pos, *args, **kwargs): + arguments_dict = kwargs.get("arguments", {}) + if "--recall" not in arguments_dict and self.checkWorkingDirectory() == False: + return + + if "--recall" in arguments_dict: + super(GitExplManager, self).startExplorer(win_pos, *args, **kwargs) + else: + self.setArguments(arguments_dict) + + uid = str(hex(int(time.time())))[-7:] + page = ExplorerPage(self._project_root, uid, self) + command = [ + GitStagedCommand(arguments_dict, uid), + GitUnstagedCommand(arguments_dict, uid), + ] + page.create(arguments_dict, command) + self._pages.add(page) + self._restoreOrigCwd() + + def getPreviewCommand(self, arguments_dict, source): + arguments_dict.update(self._arguments) + if isinstance(arguments_dict["command"], GitStagedCommand): + arguments_dict["--cached"] = [] + return GitDiffCommand(arguments_dict, source) + + def cleanupExplorerPage(self, page): + self._pages.discard(page) + + +#***************************************************** +# gitExplManager is a singleton +#***************************************************** +gitExplManager = GitExplManager() + +__all__ = ['gitExplManager'] diff --git a/autoload/leaderf/python/leaderf/gtagsExpl.py b/autoload/leaderf/python/leaderf/gtagsExpl.py index 6ef9b881..9ecc6bc4 100644 --- a/autoload/leaderf/python/leaderf/gtagsExpl.py +++ b/autoload/leaderf/python/leaderf/gtagsExpl.py @@ -30,7 +30,7 @@ def __init__(self): self._cd_option = '' self._root_markers = lfEval("g:Lf_RootMarkers") self._db_location = os.path.join(lfEval("g:Lf_CacheDirectory"), - '.LfCache', + 'LeaderF', 'gtags') self._store_in_project = lfEval("get(g:, 'Lf_GtagsStoreInProject', 0)") == '1' self._store_in_rootmarker = lfEval("get(g:, 'Lf_GtagsStoreInRootMarker', 0)") == '1' @@ -44,6 +44,8 @@ def __init__(self): self._last_command = "" self._content = [] self._with_gutentags = lfEval("get(g:, 'Lf_GtagsGutentags', 0)") != '0' + self._global = lfEval("get(g:, 'Lf_Global', 'global')") + self._gtags = lfEval("get(g:, 'Lf_Gtags', 'gtags')") self._is_debug = False self._cmd = '' @@ -119,7 +121,10 @@ def getContent(self, *args, **kwargs): auto_jump = False self._last_result_format = self._result_format - self._result_format = None + self._result_format = 'ctags-mod' + if "--result" in arguments_dict: + self._result_format = arguments_dict["--result"][0] + if "-d" in arguments_dict: pattern = arguments_dict["-d"][0] pattern_option = "-d -e %s " % pattern @@ -171,12 +176,13 @@ def getContent(self, *args, **kwargs): env["GTAGSDBPATH"] = dbpath if pattern_option is None: # '--all' or empty means the whole project - cmd = 'global -P | global -L- -f {}--gtagslabel={} {}--color=never --result={}'.format( + cmd = '{} -P | {} -L- -f {}--gtagslabel={} {}--color=never --result={}'.format( + self._global, self._global, '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", self._gtagslabel, path_style, self._result_format) else: - cmd = 'global {}--gtagslabel={} {} {}--color=never --result={}'.format( - '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", + cmd = '{} {}--gtagslabel={} {} {}--color=never --result={}'.format( + self._global, '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", self._gtagslabel, pattern_option, path_style, self._result_format) if not self._isDBModified(os.path.join(dbpath, 'GTAGS')) and self._content \ @@ -241,9 +247,10 @@ def getContent(self, *args, **kwargs): env = os.environ env["GTAGSROOT"] = root env["GTAGSDBPATH"] = dbpath - cmd = 'global {}--gtagslabel={} {} {}{}{}{}--color=never --result=ctags-mod'.format( - '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", - self._gtagslabel, pattern_option, path_style, scope, literal, ignorecase) + cmd = '{} {}--gtagslabel={} {} {}{}{}{}--color=never --result={}'.format( + self._global, '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", + self._gtagslabel, pattern_option, path_style, scope, literal, + ignorecase, self._result_format) executor = AsyncExecutor() self._executor.append(executor) @@ -263,9 +270,10 @@ def getContent(self, *args, **kwargs): if path_style == "--path-style abslib ": path_style = "--path-style absolute " - cmd = 'global {}--gtagslabel={} {} {}{}{}{}--color=never --result=ctags-mod -q'.format( - '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", - self._gtagslabel, pattern_option, path_style, scope, literal, ignorecase) + cmd = '{} {}--gtagslabel={} {} {}{}{}{}--color=never --result={} -q'.format( + self._global, '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", + self._gtagslabel, pattern_option, path_style, scope, literal, + ignorecase, self._result_format) executor = AsyncExecutor() self._executor.append(executor) @@ -273,7 +281,9 @@ def getContent(self, *args, **kwargs): if auto_jump: first_two = list(itertools.islice(content, 2)) - if len(first_two) == 1: + if len(first_two) == 0: + return [] + elif len(first_two) == 1: return first_two else: return content.join_left(first_two) @@ -284,8 +294,20 @@ def translateRegex(self, regex, is_perl=False): """ copied from RgExplorer """ + + def replace(text, pattern, repl): + r""" + only replace pattern with even number of \ preceding it + """ + result = '' + for s in re.split(r'((?:\\\\)+)', text): + result += re.sub(pattern, repl, s) + + return result + vim_regex = regex + vim_regex = vim_regex.replace(r"\\", "\\") vim_regex = re.sub(r'([%@&])', r'\\\1', vim_regex) # non-greedy pattern @@ -311,12 +333,12 @@ def translateRegex(self, regex, is_perl=False): vim_regex = re.sub(r'\(\?>(.+?)\)', r'(\1)@>', vim_regex) # this won't hurt although they are not the same - vim_regex = vim_regex.replace(r'\A', r'^') - vim_regex = vim_regex.replace(r'\z', r'$') - vim_regex = vim_regex.replace(r'\B', r'') + vim_regex = replace(vim_regex, r'\\A', r'^') + vim_regex = replace(vim_regex, r'\\z', r'$') + vim_regex = replace(vim_regex, r'\\B', r'') # word boundary - vim_regex = re.sub(r'\\b', r'(<|>)', vim_regex) + vim_regex = replace(vim_regex, r'\\b', r'(<|>)') # case-insensitive vim_regex = vim_regex.replace(r'(?i)', r'\c') @@ -331,19 +353,19 @@ def translateRegex(self, regex, is_perl=False): # \a bell (\x07) # \f form feed (\x0C) # \v vertical tab (\x0B) - vim_regex = vim_regex.replace(r'\a', r'%x07') - vim_regex = vim_regex.replace(r'\f', r'%x0C') - vim_regex = vim_regex.replace(r'\v', r'%x0B') + vim_regex = replace(vim_regex, r'\\a', r'%x07') + vim_regex = replace(vim_regex, r'\\f', r'%x0C') + vim_regex = replace(vim_regex, r'\\v', r'%x0B') # \123 octal character code (up to three digits) (when enabled) # \x7F hex character code (exactly two digits) - vim_regex = re.sub(r'\\(x[0-9A-Fa-f][0-9A-Fa-f])', r'%\1', vim_regex) + vim_regex = replace(vim_regex, r'\\(x[0-9A-Fa-f][0-9A-Fa-f])', r'%\1') # \x{10FFFF} any hex character code corresponding to a Unicode code point # \u007F hex character code (exactly four digits) # \u{7F} any hex character code corresponding to a Unicode code point # \U0000007F hex character code (exactly eight digits) # \U{7F} any hex character code corresponding to a Unicode code point - vim_regex = re.sub(r'\\([uU])', r'%\1', vim_regex) + vim_regex = replace(vim_regex, r'\\([uU])', r'%\1') vim_regex = re.sub(r'\[\[:ascii:\]\]', r'[\\x00-\\x7F]', vim_regex) vim_regex = re.sub(r'\[\[:word:\]\]', r'[0-9A-Za-z_]', vim_regex) @@ -365,36 +387,11 @@ def translateRegex(self, regex, is_perl=False): return r'\v' + vim_regex - def _nearestAncestor(self, markers, path): - """ - return the nearest ancestor path(including itself) of `path` that contains - one of files or directories in `markers`. - `markers` is a list of file or directory names. - """ - if os.name == 'nt': - # e.g. C:\\ - root = os.path.splitdrive(os.path.abspath(path))[0] + os.sep - else: - root = '/' - - path = os.path.abspath(path) - while path != root: - for name in markers: - if os.path.exists(os.path.join(path, name)): - return path - path = os.path.abspath(os.path.join(path, "..")) - - for name in markers: - if os.path.exists(os.path.join(path, name)): - return path - - return "" - def _isVersionControl(self, filename): if self._project_root and filename.startswith(self._project_root): return True - ancestor = self._nearestAncestor(self._root_markers, os.path.dirname(filename)) + ancestor = nearestAncestor(self._root_markers, os.path.dirname(filename)) if ancestor: self._project_root = ancestor return True @@ -432,12 +429,12 @@ def _root_dbpath(self, filename): if self._project_root and filename.startswith(self._project_root): root = self._project_root else: - ancestor = self._nearestAncestor(self._root_markers, os.path.dirname(filename)) + ancestor = nearestAncestor(self._root_markers, os.path.dirname(filename)) if ancestor: self._project_root = ancestor root = self._project_root else: - ancestor = self._nearestAncestor(self._root_markers, lfGetCwd()) + ancestor = nearestAncestor(self._root_markers, lfGetCwd()) if ancestor: self._project_root = ancestor root = self._project_root @@ -502,8 +499,8 @@ def _update(self, filename, single_update, auto): self._updateLibGtags(root, dbpath) if single_update: if exists: - cmd = 'cd {}"{}" && gtags {}{}{}{}--gtagslabel {} --single-update "{}" "{}"'.format(self._cd_option, root, - self._accept_dotfiles, self._skip_unreadable, self._skip_symlink, + cmd = 'cd {}"{}" && {} {}{}{}{}--gtagslabel {} --single-update "{}" "{}"'.format(self._cd_option, root, + self._gtags, self._accept_dotfiles, self._skip_unreadable, self._skip_symlink, '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", self._gtagslabel, filename, dbpath) env = os.environ @@ -540,8 +537,8 @@ def _updateLibGtags(self, root, dbpath): libdbpath = self._generateDbpath(path) if not os.path.exists(libdbpath): os.makedirs(libdbpath) - cmd = 'cd {}"{}" && gtags -i {}{}{}{}--gtagslabel {} "{}"'.format(self._cd_option, path, - self._accept_dotfiles, self._skip_unreadable, self._skip_symlink, + cmd = 'cd {}"{}" && {} -i {}{}{}{}--gtagslabel {} "{}"'.format(self._cd_option, path, + self._gtags, self._accept_dotfiles, self._skip_unreadable, self._skip_symlink, '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", self._gtagslabel, libdbpath) @@ -626,6 +623,22 @@ def _exists(self, path, dir): return False + def _gtagsFilesExists(self, path): + """ + return True if `gtags.files` exists in project root + otherwise return False + """ + root_markers = lfEval("g:Lf_RootMarkers") + project_root = nearestAncestor(root_markers, path) + if project_root == "": + return False + + cur_path = os.path.join(path, "gtags.files") + if os.path.exists(cur_path): + return True + + return False + def _buildCmd(self, dir, **kwargs): """ this function comes from FileExplorer @@ -637,6 +650,13 @@ def _buildCmd(self, dir, **kwargs): if self._Lf_ExternalCommand: return self._Lf_ExternalCommand.replace('"%s"', '%s') % dir.join('""') + if self._gtagsFilesExists(dir): + if os.name == 'nt': + cmd = 'type gtags.files' + else: + cmd = 'cat gtags.files' + return cmd + arguments_dict = kwargs.get("arguments", {}) if self._Lf_UseVersionControlTool: if self._exists(dir, ".git"): @@ -831,18 +851,18 @@ def _executeCmd(self, root, dbpath): cmd = self._file_list_cmd(root) if cmd: if os.name == 'nt': - cmd = 'cd {}"{}" && ( {} ) | gtags -i {}{}{}{}--gtagslabel {} -f- "{}"'.format(self._cd_option, root, cmd, - self._accept_dotfiles, self._skip_unreadable, self._skip_symlink, + cmd = 'cd {}"{}" && ( {} ) | {} -i {}{}{}{}--gtagslabel {} -f- "{}"'.format(self._cd_option, root, cmd, + self._gtags, self._accept_dotfiles, self._skip_unreadable, self._skip_symlink, '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", self._gtagslabel, dbpath) else: - cmd = 'cd {}"{}" && {{ {}; }} | gtags -i {}{}{}{}--gtagslabel {} -f- "{}"'.format(self._cd_option, root, cmd, - self._accept_dotfiles, self._skip_unreadable, self._skip_symlink, + cmd = 'cd {}"{}" && {{ {}; }} | {} -i {}{}{}{}--gtagslabel {} -f- "{}"'.format(self._cd_option, root, cmd, + self._gtags, self._accept_dotfiles, self._skip_unreadable, self._skip_symlink, '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", self._gtagslabel, dbpath) else: - cmd = 'cd {}"{}" && gtags -i {}{}{}{}--gtagslabel {} "{}"'.format(self._cd_option, root, - self._accept_dotfiles, self._skip_unreadable, self._skip_symlink, + cmd = 'cd {}"{}" && {} -i {}{}{}{}--gtagslabel {} "{}"'.format(self._cd_option, root, + self._gtags, self._accept_dotfiles, self._skip_unreadable, self._skip_symlink, '--gtagsconf %s ' % self._gtagsconf if self._gtagsconf else "", self._gtagslabel, dbpath) @@ -854,7 +874,7 @@ def _executeCmd(self, root, dbpath): def print_log(args): print(args) - if error: + if proc.returncode != 0: if self._has_nvim: vim.async_call(print_log, cmd) vim.async_call(print_log, error) @@ -903,6 +923,7 @@ class GtagsExplManager(Manager): def __init__(self): super(GtagsExplManager, self).__init__() self._match_path = False + self._preview_match_ids = [] def _getExplClass(self): return GtagsExplorer @@ -949,8 +970,9 @@ def _acceptSelection(self, *args, **kwargs): self._cursorline_dict[vim.current.window] = vim.current.window.options["cursorline"] lfCmd("setlocal cursorline") - except vim.error: - lfPrintTraceback() + except vim.error as e: # E37 + if 'E325' not in str(e).split(':'): + lfPrintTraceback() def updateGtags(self, filename, single_update, auto=True): self._getExplorer().updateGtags(filename, single_update, auto) @@ -959,6 +981,14 @@ def setArguments(self, arguments): self._arguments = arguments self._match_path = "--match-path" in arguments + def autoJump(self, content): + if "--auto-jump" in self._arguments and isinstance(content, list) and len(content) == 1: + mode = self._arguments["--auto-jump"][0] if len(self._arguments["--auto-jump"]) else "" + self._accept(content[0], mode) + return True + + return False + def _getDigest(self, line, mode): """ specify what part in the line to be processed and highlighted @@ -1087,24 +1117,24 @@ def _afterEnter(self): pass else: if self._getExplorer().getResultFormat() is None: - id = int(lfEval("""matchadd('Lf_hl_gtagsFileName', '^.\{-}\ze\t')""")) + id = int(lfEval(r"""matchadd('Lf_hl_gtagsFileName', '^.\{-}\ze\t')""")) self._match_ids.append(id) - id = int(lfEval("""matchadd('Lf_hl_gtagsLineNumber', '\t\zs\d\+\ze\t')""")) + id = int(lfEval(r"""matchadd('Lf_hl_gtagsLineNumber', '\t\zs\d\+\ze\t')""")) self._match_ids.append(id) elif self._getExplorer().getResultFormat() == "ctags": - id = int(lfEval("""matchadd('Lf_hl_gtagsFileName', '\t\zs.\{-}\ze\t')""")) + id = int(lfEval(r"""matchadd('Lf_hl_gtagsFileName', '\t\zs.\{-}\ze\t')""")) self._match_ids.append(id) - id = int(lfEval("""matchadd('Lf_hl_gtagsLineNumber', '\t\zs\d\+$')""")) + id = int(lfEval(r"""matchadd('Lf_hl_gtagsLineNumber', '\t\zs\d\+$')""")) self._match_ids.append(id) elif self._getExplorer().getResultFormat() == "ctags-x": - id = int(lfEval("""matchadd('Lf_hl_gtagsFileName', '^\S\+\s\+\d\+\s\+\zs\S\+')""")) + id = int(lfEval(r"""matchadd('Lf_hl_gtagsFileName', '^\S\+\s\+\d\+\s\+\zs\S\+')""")) self._match_ids.append(id) - id = int(lfEval("""matchadd('Lf_hl_gtagsLineNumber', '^\S\+\s\+\zs\d\+')""")) + id = int(lfEval(r"""matchadd('Lf_hl_gtagsLineNumber', '^\S\+\s\+\zs\d\+')""")) self._match_ids.append(id) else: # ctags-mod - id = int(lfEval("""matchadd('Lf_hl_gtagsFileName', '^.\{-}\ze\t')""")) + id = int(lfEval(r"""matchadd('Lf_hl_gtagsFileName', '^.\{-}\ze\t')""")) self._match_ids.append(id) - id = int(lfEval("""matchadd('Lf_hl_gtagsLineNumber', '\t\zs\d\+\ze\t')""")) + id = int(lfEval(r"""matchadd('Lf_hl_gtagsLineNumber', '\t\zs\d\+\ze\t')""")) self._match_ids.append(id) try: for i in self._getExplorer().getPatternRegex(): @@ -1122,6 +1152,7 @@ def _beforeExit(self): if k.valid: k.options["cursorline"] = v self._cursorline_dict.clear() + self._clearPreviewHighlights() def _bangEnter(self): super(GtagsExplManager, self)._bangEnter() @@ -1192,16 +1223,42 @@ def startExplorer(self, win_pos, *args, **kwargs): else: path = lfGetCwd() root_markers = lfEval("g:Lf_RootMarkers") - project_root = self._getExplorer()._nearestAncestor(root_markers, path) + project_root = nearestAncestor(root_markers, path) if project_root == "" and path != lfGetCwd(): - project_root = self._getExplorer()._nearestAncestor(root_markers, lfGetCwd()) + project_root = nearestAncestor(root_markers, lfGetCwd()) if project_root: chdir(project_root) super(GtagsExplManager, self).startExplorer(win_pos, *args, **kwargs) + def _clearPreviewHighlights(self): + for i in self._preview_match_ids: + lfCmd("silent! call matchdelete(%d, %d)" % (i, self._preview_winid)) + + def _highlightInPreview(self): + if lfEval("has('nvim')") != '1': + try: + for i in self._getExplorer().getPatternRegex(): + lfCmd("""call win_execute(%d, "let matchid = matchadd('Lf_hl_gtagsHighlight', '%s', 9)")""" + % (self._preview_winid, re.sub(r'\\(?!")', r'\\\\', escQuote(i)))) + id = int(lfEval("matchid")) + self._preview_match_ids.append(id) + except vim.error: + pass + else: + cur_winid = lfEval("win_getid()") + lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) + if lfEval("win_getid()") != cur_winid: + try: + for i in self._getExplorer().getPatternRegex(): + id = int(lfEval("matchadd('Lf_hl_gtagsHighlight', '%s', 9)" % escQuote(i))) + self._preview_match_ids.append(id) + except vim.error: + pass + lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) + def _previewInPopup(self, *args, **kwargs): - if len(args) == 0: + if len(args) == 0 or args[0] == '': return line = args[0] @@ -1223,6 +1280,7 @@ def _previewInPopup(self, *args, **kwargs): else: source = file self._createPopupPreview("", source, line_num) + self._highlightInPreview() #***************************************************** diff --git a/autoload/leaderf/python/leaderf/helpExpl.py b/autoload/leaderf/python/leaderf/helpExpl.py index c8c88bc8..d88fd326 100644 --- a/autoload/leaderf/python/leaderf/helpExpl.py +++ b/autoload/leaderf/python/leaderf/helpExpl.py @@ -16,6 +16,7 @@ class HelpExplorer(Explorer): def __init__(self): self._content = [] + self._file_ids = {} def getContent(self, *args, **kwargs): if self._content: @@ -26,6 +27,7 @@ def getContent(self, *args, **kwargs): def getFreshContent(self, *args, **kwargs): self._content = [] lfCmd("silent! helptags ALL") + file_id = 0 for dir in lfEval("&rtp").split(','): tags_file = os.path.join(dir, "doc", "tags") try: @@ -33,10 +35,13 @@ def getFreshContent(self, *args, **kwargs): lines = f.readlines() for line in lines: tag, file = line.split()[:2] - self._content.append("{:<40} {}".format(tag, file)) + self._content.append("{:<40} {} {}".format(tag, file, file_id)) except IOError: pass + self._file_ids[file_id] = os.path.dirname(tags_file) + file_id += 1 + return self._content def getStlCategory(self): @@ -120,12 +125,12 @@ def _createHelp(self): def _afterEnter(self): super(HelpExplManager, self)._afterEnter() if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_helpTagfile'', '' \zs.*$'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_helpTagfile'', '' \zs.*$'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) else: - id = int(lfEval('''matchadd('Lf_hl_helpTagfile', ' \zs.*$')''')) + id = int(lfEval(r'''matchadd('Lf_hl_helpTagfile', ' \zs.*$')''')) self._match_ids.append(id) def _beforeExit(self): @@ -134,6 +139,16 @@ def _beforeExit(self): def _supportsRefine(self): return True + def _previewInPopup(self, *args, **kwargs): + if len(args) == 0 or args[0] == '': + return + + line = args[0] + tagname, tagfile, file_id = line.split(None, 2) + tagfile = os.path.join(self._getExplorer()._file_ids[int(file_id)], tagfile) + jump_cmd = r"let pos = search('\m\*%s\*', 'w') | exec 'norm! '.pos.'G'" % escQuote(tagname) + self._createPopupPreview("", tagfile, 0, jump_cmd) + #***************************************************** # helpExplManager is a singleton diff --git a/autoload/leaderf/python/leaderf/instance.py b/autoload/leaderf/python/leaderf/instance.py index c13c5374..9fe9996f 100644 --- a/autoload/leaderf/python/leaderf/instance.py +++ b/autoload/leaderf/python/leaderf/instance.py @@ -210,6 +210,20 @@ def statusline_win(self, statusline_win): def getWinIdList(self): return [win.id for win in self._popup_wins.values() if win is not None] + @property + def tabpage(self): + if self._popup_wins["input_win"] is None: + return None + else: + return self._popup_wins["input_win"].tabpage + + def valid(self): + for win in self._popup_wins.values(): + if win is None or not win.valid: + return False + + return True + #***************************************************** # LfInstance #***************************************************** @@ -234,7 +248,7 @@ def __init__(self, manager, category, cli, self._tabpage_object = None self._window_object = None self._buffer_object = None - self._buffer_name = lfEval("expand('$VIMRUNTIME/')") + category + '/LeaderF' + self._buffer_name = 'LeaderF://' + category + '/LeaderF' self._input_buffer_number = -1 self._stl_buffer_number = -1 self._win_height = float(lfEval("g:Lf_WindowHeight")) @@ -259,6 +273,12 @@ def __init__(self, manager, category, cli, self._win_pos = None self._stl_buf_namespace = None self._auto_resize = lfEval("get(g:, 'Lf_AutoResize', 0)") == '1' + self._window_id = 0 + self._float_win_view = None + self._popup_cursor_line = 1 + self._auto_adjust_height = lfEval("get(g:, 'Lf_PopupAutoAdjustHeight', 1)") == '1' + self._preview_position = None + self._initial_maxwidth = 0 def _initStlVar(self): if int(lfEval("!exists('g:Lf_{}_StlCategory')".format(self._category))): @@ -303,7 +323,10 @@ def _setAttributes(self): lfCmd("setlocal nofoldenable") lfCmd("setlocal foldmethod=manual") lfCmd("setlocal shiftwidth=4") - lfCmd("setlocal cursorline") + if lfEval("has('nvim')") == '1': + lfCmd("call nvim_win_set_option(win_getid(), 'cursorline', v:true)") + else: + lfCmd("setlocal cursorline") if lfEval("exists('+cursorlineopt')") == '1': lfCmd("setlocal cursorlineopt=both") if lfEval("has('nvim')") == '1': @@ -337,36 +360,161 @@ def _setStatusline(self): .format(self.buffer.number, self._stl)) lfCmd("augroup END") - def _createPopupWindow(self, clear): - # `type(self._window_object) != type(vim.current.window)` is necessary, error occurs if - # `Leaderf file --popup` after `Leaderf file` without it. - if self._window_object is not None and type(self._window_object) != type(vim.current.window)\ - and isinstance(self._window_object, PopupWindow): # type is PopupWindow - if self._window_object.tabpage == vim.current.tabpage and lfEval("get(g:, 'Lf_Popup_VimResized', 0)") == '0' \ - and "--popup-width" not in self._arguments and "--popup-height" not in self._arguments: - if self._popup_winid > 0 and self._window_object.valid: # invalid if cleared by popup_clear() - # clear the buffer first to avoid a flash - if clear and lfEval("g:Lf_RememberLastSearch") == '0' \ - and "--append" not in self._arguments \ - and "--recall" not in self._arguments: - self.buffer.options['modifiable'] = True - del self._buffer_object[:] - self.refreshPopupStatusline() - - self._popup_instance.show() - return + def _nvim_set_options(self, buf_number, winid): + lfCmd("call nvim_buf_set_option(%d, 'buflisted', v:false)" % buf_number) + lfCmd("call nvim_buf_set_option(%d, 'buftype', 'nofile')" % buf_number) + lfCmd("call nvim_buf_set_option(%d, 'bufhidden', 'hide')" % buf_number) + lfCmd("call nvim_buf_set_option(%d, 'undolevels', -1)" % buf_number) + lfCmd("call nvim_buf_set_option(%d, 'swapfile', v:false)" % buf_number) + + lfCmd("call nvim_win_set_option(%d, 'list', v:false)" % winid) + lfCmd("call nvim_win_set_option(%d, 'number', v:false)" % winid) + lfCmd("call nvim_win_set_option(%d, 'relativenumber', v:false)" % winid) + lfCmd("call nvim_win_set_option(%d, 'spell', v:false)" % winid) + lfCmd("call nvim_win_set_option(%d, 'foldenable', v:false)" % winid) + lfCmd("call nvim_win_set_option(%d, 'foldmethod', 'manual')" % winid) + try: + lfCmd("call nvim_win_set_option(%d, 'foldcolumn', 0)" % winid) + except vim.error: + lfCmd("call nvim_win_set_option(%d, 'foldcolumn', '0')" % winid) + lfCmd("call nvim_win_set_option(%d, 'signcolumn', 'no')" % winid) + lfCmd("call nvim_win_set_option(%d, 'cursorline', v:false)" % winid) + if lfEval("exists('+cursorlineopt')") == '1': + lfCmd("call nvim_win_set_option(%d, 'cursorlineopt', 'both')" % winid) + lfCmd("call nvim_win_set_option(%d, 'colorcolumn', '')" % winid) + + def _vim_set_options(self, winid): + lfCmd("call win_execute(%d, 'setlocal nobuflisted')" % winid) + lfCmd("call win_execute(%d, 'setlocal buftype=nofile')" % winid) + lfCmd("call win_execute(%d, 'setlocal bufhidden=hide')" % winid) + lfCmd("call win_execute(%d, 'setlocal undolevels=-1')" % winid) + lfCmd("call win_execute(%d, 'setlocal noswapfile')" % winid) + lfCmd("call win_execute(%d, 'setlocal nolist')" % winid) + lfCmd("call win_execute(%d, 'setlocal nonumber norelativenumber')" % winid) + lfCmd("call win_execute(%d, 'setlocal nospell')" % winid) + lfCmd("call win_execute(%d, 'setlocal nofoldenable')" % winid) + lfCmd("call win_execute(%d, 'setlocal foldmethod=manual')" % winid) + lfCmd("call win_execute(%d, 'setlocal shiftwidth=4')" % winid) + lfCmd("call win_execute(%d, 'setlocal nocursorline')" % winid) + lfCmd("call win_execute(%d, 'setlocal foldcolumn=0')" % winid) + # lfCmd("call win_execute(%d, 'silent! setlocal signcolumn=no')" % winid) + if lfEval("exists('+cursorlineopt')") == '1': + lfCmd("call win_execute(%d, 'setlocal cursorlineopt=both')" % winid) + lfCmd("call win_execute(%d, 'setlocal colorcolumn=')" % winid) + + def enlargePopupWindow(self): + if self._win_pos not in ('popup', 'floatwin'): + return + + preview_pos = self._arguments.get("--preview-position", [""])[0] + if preview_pos == "": + preview_pos = lfEval("get(g:, 'Lf_PopupPreviewPosition', 'right')") + + if preview_pos.lower() == 'right': + if self._popup_instance.input_win.width > self._initial_maxwidth * 2: + return + + if lfEval("has('nvim')") == '1': + width = self._initial_maxwidth * 2 + 2 + + lfCmd("call leaderf#ResetFloatwinOptions({}, 'width', {})" + .format(self._popup_winid, width)) + lfCmd("call leaderf#ResetFloatwinOptions({}, 'width', {})" + .format(self._popup_instance.input_win.id, width)) + if lfEval("get(g:, 'Lf_PopupShowStatusline', 1)") == '1': + lfCmd("call leaderf#ResetFloatwinOptions({}, 'width', {})" + .format(self._popup_instance.statusline_win.id, width)) + else: + width = self._initial_maxwidth * 2 + 2 + + lfCmd("call leaderf#ResetPopupOptions({}, 'maxwidth', {})" + .format(self._popup_winid, width)) + lfCmd("call leaderf#ResetPopupOptions({}, 'minwidth', {})" + .format(self._popup_winid, width)) + + lfCmd("call leaderf#ResetPopupOptions({}, 'maxwidth', {})" + .format(self._popup_instance.input_win.id, width)) + lfCmd("call leaderf#ResetPopupOptions({}, 'minwidth', {})" + .format(self._popup_instance.input_win.id, width)) + + if lfEval("get(g:, 'Lf_PopupShowStatusline', 1)") == '1': + lfCmd("call leaderf#ResetPopupOptions({}, 'maxwidth', {})" + .format(self._popup_instance.statusline_win.id, width)) + lfCmd("call leaderf#ResetPopupOptions({}, 'minwidth', {})" + .format(self._popup_instance.statusline_win.id, width)) + + self._cli.buildPopupPrompt() + self.refreshPopupStatusline() + + def shrinkPopupWindow(self): + preview_pos = self._arguments.get("--preview-position", [""])[0] + if preview_pos == "": + preview_pos = lfEval("get(g:, 'Lf_PopupPreviewPosition', 'right')") + + if preview_pos.lower() == 'right': + if lfEval("has('nvim')") == '1': + lfCmd("call leaderf#ResetFloatwinOptions({}, 'width', {})" + .format(self._popup_winid, self._initial_maxwidth)) + lfCmd("call leaderf#ResetFloatwinOptions({}, 'width', {})" + .format(self._popup_instance.input_win.id, self._initial_maxwidth)) + + if lfEval("get(g:, 'Lf_PopupShowStatusline', 1)") == '1': + lfCmd("call leaderf#ResetFloatwinOptions({}, 'width', {})" + .format(self._popup_instance.statusline_win.id, self._initial_maxwidth)) + else: + lfCmd("call leaderf#ResetPopupOptions({}, 'maxwidth', {})" + .format(self._popup_winid, self._initial_maxwidth)) + lfCmd("call leaderf#ResetPopupOptions({}, 'minwidth', {})" + .format(self._popup_winid, self._initial_maxwidth)) + + lfCmd("call leaderf#ResetPopupOptions({}, 'maxwidth', {})" + .format(self._popup_instance.input_win.id, self._initial_maxwidth)) + lfCmd("call leaderf#ResetPopupOptions({}, 'minwidth', {})" + .format(self._popup_instance.input_win.id, self._initial_maxwidth)) + + if lfEval("get(g:, 'Lf_PopupShowStatusline', 1)") == '1': + lfCmd("call leaderf#ResetPopupOptions({}, 'maxwidth', {})" + .format(self._popup_instance.statusline_win.id, self._initial_maxwidth)) + lfCmd("call leaderf#ResetPopupOptions({}, 'minwidth', {})" + .format(self._popup_instance.statusline_win.id, self._initial_maxwidth)) + + self._cli.buildPopupPrompt() + self.refreshPopupStatusline() + + def _createPopupWindow(self): + preview_pos = self._arguments.get("--preview-position", [""])[0] + if preview_pos == "": + preview_pos = lfEval("get(g:, 'Lf_PopupPreviewPosition', 'right')") + + if lfEval("has('nvim')") == '0' and self._popup_instance.valid(): + if (self._popup_instance.tabpage == vim.current.tabpage + and lfEval("get(g:, 'Lf_Popup_VimResized', 0)") == '0' + and "--popup-width" not in self._arguments + and "--popup-height" not in self._arguments + and preview_pos == self._preview_position): + self._win_pos = "popup" + self._window_object = self._popup_instance.content_win + self._popup_instance.show() + return else: lfCmd("let g:Lf_Popup_VimResized = 0") self._popup_instance.close() buf_number = int(lfEval("bufadd('{}')".format(escQuote(self._buffer_name)))) + self._preview_position = preview_pos + if preview_pos == "bottom": + self._auto_adjust_height = False + width = lfEval("get(g:, 'Lf_PopupWidth', 0)") width = self._arguments.get("--popup-width", [width])[0] width = width.strip('"').strip("'") width = float(lfEval(width)) if width <= 0: - maxwidth = int(int(lfEval("&columns")) * 2 // 3) + if preview_pos.lower() in ('right', 'left'): + maxwidth = int(int(lfEval("&columns")) * 2 // 5) + else: + maxwidth = int(int(lfEval("&columns")) * 0.618) elif width < 1: maxwidth = int(int(lfEval("&columns")) * width) maxwidth = max(20, maxwidth) @@ -375,28 +523,45 @@ def _createPopupWindow(self, clear): maxwidth = min(width, int(lfEval("&columns"))) maxwidth = max(20, maxwidth) + self._initial_maxwidth = maxwidth + + show_borders = lfEval("get(g:, 'Lf_PopupShowBorder', 1)") == '1' height = lfEval("get(g:, 'Lf_PopupHeight', 0)") height = self._arguments.get("--popup-height", [height])[0] height = height.strip('"').strip("'") height = float(lfEval(height)) if height <= 0: - maxheight = int(int(lfEval("&lines")) * 0.4) + if preview_pos.lower() in ('right', 'left'): + maxheight = int(int(lfEval("&lines")) * 0.7) + else: + maxheight = int(int(lfEval("&lines")) * 0.4) elif height < 1: maxheight = int(int(lfEval("&lines")) * height) if maxheight < 1: maxheight = 1 else: height = int(height) - maxheight = min(height, int(lfEval("&lines"))) + maxheight = min(height, int(lfEval("&lines")) - 2) + if show_borders: + maxheight -= 2 line, col = [int(i) for i in lfEval("get(g:, 'Lf_PopupPosition', [0, 0])")] if line == 0: - line = (int(lfEval("&lines")) - maxheight) // 2 + line = (int(lfEval("&lines")) - 2 - maxheight) // 2 else: line = min(line, int(lfEval("&lines")) - maxheight) if col == 0: - col = (int(lfEval("&columns")) - maxwidth) // 2 + if preview_pos.lower() == 'right': + col = max(0, (int(lfEval("&columns")) - maxwidth*2) // 2) + elif preview_pos.lower() == 'left': + col = (int(lfEval("&columns")) - maxwidth*2) // 2 + if col < 0: + col = int(lfEval("&columns")) - maxwidth + 1 + else: + col += maxwidth + 1 + else: + col = (int(lfEval("&columns")) - maxwidth) // 2 else: col = min(col, int(lfEval("&columns")) - maxwidth) @@ -406,24 +571,54 @@ def _createPopupWindow(self, clear): if col <= 0: col = 1 + if lfEval("has('nvim')") == '1': + line -= 1 + col -= 1 + self._popup_maxheight = max(maxheight - 2, 1) # there is an input window above if lfEval("has('nvim')") == '1': self._win_pos = "floatwin" - floatwin_height = 1 + if self._auto_adjust_height: + floatwin_height = 1 + else: + floatwin_height = self._popup_maxheight config = { "relative": "editor", "anchor" : "NW", "height" : floatwin_height, "width" : maxwidth, + "zindex" : 20480, "row" : line + 1, - "col" : col + "col" : col, + "noautocmd": 1 } + + popup_borders = lfEval("g:Lf_PopupBorders") + borderchars = [ + [popup_borders[4], "Lf_hl_popupBorder"], + [popup_borders[0], "Lf_hl_popupBorder"], + [popup_borders[5], "Lf_hl_popupBorder"], + [popup_borders[1], "Lf_hl_popupBorder"], + [popup_borders[6], "Lf_hl_popupBorder"], + [popup_borders[2], "Lf_hl_popupBorder"], + [popup_borders[7], "Lf_hl_popupBorder"], + [popup_borders[3], "Lf_hl_popupBorder"] + ] + if show_borders: + if lfEval("get(g:, 'Lf_PopupShowStatusline', 1)") == '1': + config["border"] = ['','','',borderchars[3],'','','',borderchars[7]] + else: + config["border"] = ['','',''] + borderchars[3:] + config["row"] += 1 + config["width"] -= 2 + lfCmd("noautocmd silent noswapfile let winid = nvim_open_win(%d, 1, %s)" % (buf_number, str(config))) self._popup_winid = int(lfEval("winid")) + self._window_id = self._popup_winid self._setAttributes() - if lfEval("get(g:, 'Lf_PopupShowFoldcolumn', 1)") == '0': + if lfEval("get(g:, 'Lf_PopupShowFoldcolumn', 1)") == '0' or show_borders: try: lfCmd("call nvim_win_set_option(%d, 'foldcolumn', 0)" % self._popup_winid) except vim.error: @@ -438,7 +633,8 @@ def _createPopupWindow(self, clear): self._tabpage_object = vim.current.tabpage self._buffer_object = vim.buffers[buf_number] - self._window_object = FloatWindow(self._popup_winid, vim.current.window, self._buffer_object, self._tabpage_object, line + 1) + self._window_object = FloatWindow(self._popup_winid, vim.current.window, + self._buffer_object, self._tabpage_object, config["row"]) self._popup_instance.content_win = self._window_object input_win_config = { @@ -446,45 +642,34 @@ def _createPopupWindow(self, clear): "anchor" : "NW", "height" : 1, "width" : maxwidth, + "zindex" : 20480, "row" : line, - "col" : col + "col" : col, + "noautocmd": 1 } + if show_borders: + input_win_config["border"] = borderchars[:4] + ['','','', borderchars[7]] + input_win_config["width"] -= 2 + if self._input_buffer_number == -1: self._input_buffer_number = int(lfEval("bufadd('')")) buf_number = self._input_buffer_number lfCmd("noautocmd silent let winid = nvim_open_win(%d, 0, %s)" % (buf_number, str(input_win_config))) winid = int(lfEval("winid")) - lfCmd("call nvim_buf_set_option(%d, 'buflisted', v:false)" % buf_number) - lfCmd("call nvim_buf_set_option(%d, 'buftype', 'nofile')" % buf_number) - lfCmd("call nvim_buf_set_option(%d, 'bufhidden', 'hide')" % buf_number) - lfCmd("call nvim_buf_set_option(%d, 'undolevels', -1)" % buf_number) - lfCmd("call nvim_buf_set_option(%d, 'swapfile', v:false)" % buf_number) - - lfCmd("call nvim_win_set_option(%d, 'list', v:false)" % winid) - lfCmd("call nvim_win_set_option(%d, 'number', v:false)" % winid) - lfCmd("call nvim_win_set_option(%d, 'relativenumber', v:false)" % winid) - lfCmd("call nvim_win_set_option(%d, 'spell', v:false)" % winid) - lfCmd("call nvim_win_set_option(%d, 'foldenable', v:false)" % winid) - lfCmd("call nvim_win_set_option(%d, 'foldmethod', 'manual')" % winid) - try: - lfCmd("call nvim_win_set_option(%d, 'foldcolumn', 0)" % winid) - except vim.error: - lfCmd("call nvim_win_set_option(%d, 'foldcolumn', '0')" % winid) - # lfCmd("call nvim_win_set_option(%d, 'signcolumn', 'no')" % winid) - lfCmd("call nvim_win_set_option(%d, 'cursorline', v:false)" % winid) - if lfEval("exists('+cursorlineopt')") == '1': - lfCmd("call nvim_win_set_option(%d, 'cursorlineopt', 'both')" % winid) - lfCmd("call nvim_win_set_option(%d, 'colorcolumn', '')" % winid) + self._nvim_set_options(buf_number, winid) + lfCmd("silent! call nvim_win_set_option(%d, 'statuscolumn', '')" % winid) lfCmd("call nvim_win_set_option(%d, 'winhighlight', 'Normal:Lf_hl_popup_inputText')" % winid) + def getWindow(number): for w in vim.windows: if number == w.number: return w return vim.current.window - self._popup_instance.input_win = FloatWindow(winid, getWindow(int(lfEval("win_id2win(%d)" % winid))), vim.buffers[buf_number], vim.current.tabpage, line) + self._popup_instance.input_win = FloatWindow(winid, getWindow(int(lfEval("win_id2win(%d)" % winid))), + vim.buffers[buf_number], vim.current.tabpage, line) show_stl = 0 if lfEval("get(g:, 'Lf_PopupShowStatusline', 1)") == '1': @@ -494,56 +679,56 @@ def getWindow(number): "anchor" : "NW", "height" : 1, "width" : maxwidth, - "row" : line + 1 + floatwin_height, - "col" : col + "zindex" : 20480, + "row" : config["row"] + floatwin_height, + "col" : col, + "noautocmd": 1 } + if show_borders: + stl_win_config["border"] = ['','',''] + borderchars[3:] + stl_win_config["width"] -= 2 + if self._stl_buffer_number == -1: self._stl_buffer_number = int(lfEval("bufadd('')")) buf_number = self._stl_buffer_number lfCmd("noautocmd silent let winid = nvim_open_win(%d, 0, %s)" % (buf_number, str(stl_win_config))) winid = int(lfEval("winid")) - lfCmd("call nvim_buf_set_option(%d, 'buflisted', v:false)" % buf_number) - lfCmd("call nvim_buf_set_option(%d, 'buftype', 'nofile')" % buf_number) - lfCmd("call nvim_buf_set_option(%d, 'bufhidden', 'hide')" % buf_number) - lfCmd("call nvim_buf_set_option(%d, 'undolevels', -1)" % buf_number) - lfCmd("call nvim_buf_set_option(%d, 'swapfile', v:false)" % buf_number) - - lfCmd("call nvim_win_set_option(%d, 'list', v:false)" % winid) - lfCmd("call nvim_win_set_option(%d, 'number', v:false)" % winid) - lfCmd("call nvim_win_set_option(%d, 'relativenumber', v:false)" % winid) - lfCmd("call nvim_win_set_option(%d, 'spell', v:false)" % winid) - lfCmd("call nvim_win_set_option(%d, 'foldenable', v:false)" % winid) - lfCmd("call nvim_win_set_option(%d, 'foldmethod', 'manual')" % winid) - try: - lfCmd("call nvim_win_set_option(%d, 'foldcolumn', 0)" % winid) - except vim.error: - lfCmd("call nvim_win_set_option(%d, 'foldcolumn', '0')" % winid) - # lfCmd("call nvim_win_set_option(%d, 'signcolumn', 'no')" % winid) - lfCmd("call nvim_win_set_option(%d, 'cursorline', v:false)" % winid) - if lfEval("exists('+cursorlineopt')") == '1': - lfCmd("call nvim_win_set_option(%d, 'cursorlineopt', 'both')" % winid) - lfCmd("call nvim_win_set_option(%d, 'colorcolumn', '')" % winid) + self._nvim_set_options(buf_number, winid) + lfCmd("silent! call nvim_win_set_option(%d, 'statuscolumn', '')" % winid) lfCmd("call nvim_win_set_option(%d, 'winhighlight', 'Normal:Lf_hl_popup_blank')" % winid) - self._popup_instance.statusline_win = FloatWindow(winid, getWindow(int(lfEval("win_id2win(%d)" % winid))), vim.buffers[buf_number], vim.current.tabpage, line + 1 + floatwin_height) + self._popup_instance.statusline_win = FloatWindow(winid, + getWindow(int(lfEval("win_id2win(%d)" % winid))), + vim.buffers[buf_number], + vim.current.tabpage, + stl_win_config["row"]) if "--recall" in self._arguments: self.refreshPopupStatusline() lfCmd("augroup Lf_Floatwin_Close") - lfCmd("autocmd! WinEnter * call leaderf#closeAllFloatwin(%d, %d, %d, %d, %d)" % (self._popup_instance.input_win.id, - self._popup_instance.content_win.id, - self._popup_instance.statusline_win.id if show_stl else -1, - show_stl, id(self._manager))) + lfCmd("autocmd! WinEnter * call leaderf#closeAllFloatwin(%d, %d, %d, %d, %d)" + % (self._popup_instance.input_win.id, + self._popup_instance.content_win.id, + self._popup_instance.statusline_win.id if show_stl else -1, + show_stl, id(self._manager))) lfCmd("augroup END") + + if self._float_win_view is not None: + lfCmd("call winrestview(%s)" % self._float_win_view) else: self._win_pos = "popup" + if self._auto_adjust_height: + minheight = 1 + else: + minheight = self._popup_maxheight options = { "maxwidth": maxwidth, "minwidth": maxwidth, "maxheight": self._popup_maxheight, + "minheight": minheight, "zindex": 20480, "pos": "topleft", "line": line + 1, # there is an input window above @@ -551,37 +736,39 @@ def getWindow(number): "padding": [0, 0, 0, 0], "scrollbar": 0, "mapping": 0, + "borderhighlight": ["Lf_hl_previewTitle"], "filter": "leaderf#PopupFilter", } + borderchars = lfEval("g:Lf_PopupBorders") + if show_borders: + if lfEval("get(g:, 'Lf_PopupShowStatusline', 1)") == '1': + options["border"] = [0, 1, 0, 1] + else: + options["border"] = [0, 1, 1, 1] + options["borderchars"] = borderchars + options["maxwidth"] -= 2 + options["minwidth"] -= 2 + options["line"] += 1 + options["borderhighlight"] = ["Lf_hl_popupBorder"] + lfCmd("noautocmd silent noswapfile let winid = popup_create(%d, %s)" % (buf_number, str(options))) self._popup_winid = int(lfEval("winid")) - lfCmd("call win_execute(%d, 'setlocal nobuflisted')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal buftype=nofile')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal bufhidden=hide')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal undolevels=-1')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal noswapfile')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal nolist')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal number norelativenumber')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal nospell')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal nofoldenable')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal foldmethod=manual')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal shiftwidth=4')" % self._popup_winid) + self._window_id = self._popup_winid + self._vim_set_options(self._popup_winid) + lfCmd("call win_execute(%d, 'setlocal number')" % self._popup_winid) lfCmd("call win_execute(%d, 'setlocal cursorline')" % self._popup_winid) - if lfEval("exists('+cursorlineopt')") == '1': - lfCmd("call win_execute(%d, 'setlocal cursorlineopt=both')" % self._popup_winid) - if lfEval("get(g:, 'Lf_PopupShowFoldcolumn', 1)") == '0': + if lfEval("get(g:, 'Lf_PopupShowFoldcolumn', 1)") == '0' or show_borders: lfCmd("call win_execute(%d, 'setlocal foldcolumn=0')" % self._popup_winid) else: lfCmd("call win_execute(%d, 'setlocal foldcolumn=1')" % self._popup_winid) - # lfCmd("call win_execute(%d, 'silent! setlocal signcolumn=no')" % self._popup_winid) - lfCmd("call win_execute(%d, 'setlocal colorcolumn=')" % self._popup_winid) lfCmd("call win_execute(%d, 'setlocal wincolor=Lf_hl_popup_window')" % self._popup_winid) lfCmd("call win_execute(%d, 'silent! setlocal filetype=leaderf')" % self._popup_winid) + lfCmd("call win_execute(%d, 'norm! %dG')" % (self._popup_winid, self._popup_cursor_line)) self._tabpage_object = vim.current.tabpage self._buffer_object = vim.buffers[buf_number] - self._window_object = PopupWindow(self._popup_winid, self._buffer_object, self._tabpage_object, line+1) + self._window_object = PopupWindow(self._popup_winid, self._buffer_object, self._tabpage_object, options["line"]) self._popup_instance.content_win = self._window_object input_win_options = { @@ -594,29 +781,23 @@ def getWindow(number): "col": col, "scrollbar": 0, "mapping": 0, + "borderhighlight": ["Lf_hl_previewTitle"], } + if show_borders: + input_win_options["border"] = [1, 1, 0, 1] + input_win_options["borderchars"] = borderchars + input_win_options["maxwidth"] -= 2 + input_win_options["minwidth"] -= 2 + input_win_options["borderhighlight"] = ["Lf_hl_popupBorder"] + if self._input_buffer_number == -1: self._input_buffer_number = int(lfEval("bufadd('')")) buf_number = self._input_buffer_number lfCmd("noautocmd silent let winid = popup_create(%d, %s)" % (buf_number, str(input_win_options))) winid = int(lfEval("winid")) - lfCmd("call win_execute(%d, 'setlocal nobuflisted')" % winid) - lfCmd("call win_execute(%d, 'setlocal buftype=nofile')" % winid) - lfCmd("call win_execute(%d, 'setlocal bufhidden=hide')" % winid) - lfCmd("call win_execute(%d, 'setlocal undolevels=-1')" % winid) - lfCmd("call win_execute(%d, 'setlocal noswapfile')" % winid) - lfCmd("call win_execute(%d, 'setlocal nolist')" % winid) - lfCmd("call win_execute(%d, 'setlocal nonumber norelativenumber')" % winid) - lfCmd("call win_execute(%d, 'setlocal nospell')" % winid) - lfCmd("call win_execute(%d, 'setlocal nofoldenable')" % winid) - lfCmd("call win_execute(%d, 'setlocal foldmethod=manual')" % winid) - lfCmd("call win_execute(%d, 'setlocal shiftwidth=4')" % winid) - lfCmd("call win_execute(%d, 'setlocal nocursorline')" % winid) - lfCmd("call win_execute(%d, 'setlocal foldcolumn=0')" % winid) - # lfCmd("call win_execute(%d, 'silent! setlocal signcolumn=no')" % winid) - lfCmd("call win_execute(%d, 'setlocal colorcolumn=')" % winid) + self._vim_set_options(winid) lfCmd("call win_execute(%d, 'setlocal wincolor=Lf_hl_popup_inputText')" % winid) self._popup_instance.input_win = PopupWindow(winid, vim.buffers[buf_number], vim.current.tabpage, line) @@ -628,43 +809,41 @@ def getWindow(number): "maxheight": 1, "zindex": 20480, "pos": "topleft", - "line": line + 1 + self._window_object.height, + "line": options["line"] + self._window_object.height, "col": col, "scrollbar": 0, "mapping": 0, + "borderhighlight": ["Lf_hl_previewTitle"], } + if show_borders: + statusline_win_options["border"] = [0, 1, 1, 1] + statusline_win_options["borderchars"] = borderchars + statusline_win_options["maxwidth"] -= 2 + statusline_win_options["minwidth"] -= 2 + statusline_win_options["borderhighlight"] = ["Lf_hl_popupBorder"] + if self._stl_buffer_number == -1: self._stl_buffer_number = int(lfEval("bufadd('')")) buf_number = self._stl_buffer_number lfCmd("noautocmd silent let winid = popup_create(%d, %s)" % (buf_number, str(statusline_win_options))) winid = int(lfEval("winid")) - lfCmd("call win_execute(%d, 'setlocal nobuflisted')" % winid) - lfCmd("call win_execute(%d, 'setlocal buftype=nofile')" % winid) - lfCmd("call win_execute(%d, 'setlocal bufhidden=hide')" % winid) - lfCmd("call win_execute(%d, 'setlocal undolevels=-1')" % winid) - lfCmd("call win_execute(%d, 'setlocal noswapfile')" % winid) - lfCmd("call win_execute(%d, 'setlocal nolist')" % winid) - lfCmd("call win_execute(%d, 'setlocal nonumber norelativenumber')" % winid) - lfCmd("call win_execute(%d, 'setlocal nospell')" % winid) - lfCmd("call win_execute(%d, 'setlocal nofoldenable')" % winid) - lfCmd("call win_execute(%d, 'setlocal foldmethod=manual')" % winid) - lfCmd("call win_execute(%d, 'setlocal shiftwidth=4')" % winid) - lfCmd("call win_execute(%d, 'setlocal nocursorline')" % winid) - lfCmd("call win_execute(%d, 'setlocal foldcolumn=0')" % winid) - # lfCmd("call win_execute(%d, 'silent! setlocal signcolumn=no')" % winid) - lfCmd("call win_execute(%d, 'setlocal colorcolumn=')" % winid) + self._vim_set_options(winid) lfCmd("call win_execute(%d, 'setlocal wincolor=Lf_hl_popup_blank')" % winid) - self._popup_instance.statusline_win = PopupWindow(winid, vim.buffers[buf_number], vim.current.tabpage, line + 1 + self._window_object.height) + self._popup_instance.statusline_win = PopupWindow(winid, + vim.buffers[buf_number], + vim.current.tabpage, + statusline_win_options["line"]) lfCmd("call leaderf#ResetPopupOptions(%d, 'callback', function('leaderf#PopupClosed', [%s, %d]))" % (self._popup_winid, str(self._popup_instance.getWinIdList()), id(self._manager))) if not self._is_popup_colorscheme_autocmd_set: self._is_popup_colorscheme_autocmd_set = True - lfCmd("call leaderf#colorscheme#popup#load('{}', '{}')".format(self._category, lfEval("get(g:, 'Lf_PopupColorscheme', 'default')"))) + lfCmd("call leaderf#colorscheme#popup#load('{}', '{}')".format(self._category, + lfEval("get(g:, 'Lf_PopupColorscheme', 'default')"))) lfCmd("augroup Lf_Popup_{}_Colorscheme".format(self._category)) lfCmd("autocmd ColorScheme * call leaderf#colorscheme#popup#load('{}', '{}')".format(self._category, lfEval("get(g:, 'Lf_PopupColorscheme', 'default')"))) @@ -739,6 +918,8 @@ def _createBufWindow(self, win_pos): lfCmd("doautocmd WinEnter") + if lfEval("exists('*win_getid')") == '1': + self._window_id = int(lfEval("win_getid()")) self._tabpage_object = vim.current.tabpage self._window_object = vim.current.window self._initial_win_height = self._window_object.height @@ -752,15 +933,18 @@ def _createBufWindow(self, win_pos): if not self._is_colorscheme_autocmd_set: self._is_colorscheme_autocmd_set = True - lfCmd("call leaderf#colorscheme#popup#load('{}', '{}')".format(self._category, lfEval("get(g:, 'Lf_PopupColorscheme', 'default')"))) + lfCmd("call leaderf#colorscheme#popup#load('{}', '{}')".format(self._category, + lfEval("get(g:, 'Lf_PopupColorscheme', 'default')"))) lfCmd("call leaderf#colorscheme#highlight('{}', {})".format(self._category, self._buffer_object.number)) lfCmd("augroup Lf_{}_Colorscheme".format(self._category)) lfCmd("autocmd!") - lfCmd("autocmd ColorScheme * call leaderf#colorscheme#highlight('{}', {})".format(self._category, self._buffer_object.number)) + lfCmd("autocmd ColorScheme * call leaderf#colorscheme#highlight('{}', {})".format(self._category, + self._buffer_object.number)) lfCmd("autocmd ColorScheme * call leaderf#colorscheme#highlightMode('{0}', g:Lf_{0}_StlMode)".format(self._category)) lfCmd("autocmd ColorScheme doautocmd syntax") lfCmd("autocmd CursorMoved let g:Lf_{}_StlLineNumber = 1 + line('$') - line('.')".format(self._category)) lfCmd("autocmd VimResized * let g:Lf_VimResized = 1") + lfCmd("autocmd BufHidden call leaderf#Quit({})".format(id(self._manager))) lfCmd("augroup END") saved_eventignore = vim.options['eventignore'] @@ -814,7 +998,8 @@ def mimicCursor(self): if self._popup_winid > 0 and self._window_object.valid: self.hideMimicCursor() lfCmd("""call win_execute(%d, "let cursor_pos = getcurpos()[1:2]")""" % (self._popup_winid)) - lfCmd("""silent! call win_execute(%d, 'let g:Lf_mimicedCursorId = matchaddpos("Cursor", [cursor_pos])')""" % (self._popup_winid)) + lfCmd("""silent! call win_execute(%d, 'let g:Lf_mimicedCursorId = matchaddpos("Cursor", [cursor_pos])')""" + % (self._popup_winid)) def setPopupStl(self, current_mode): statusline_win = self._popup_instance.statusline_win @@ -850,30 +1035,36 @@ def setPopupStl(self, current_mode): if self._win_pos == 'popup': lfCmd("""call popup_settext(%d, '%s')""" % (statusline_win.id, escQuote(text))) - lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_mode'})")""" % (statusline_win.id, self._category)) + lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_mode'})")""" + % (statusline_win.id, self._category)) lfCmd("""call win_execute(%d, "call prop_add(1, 1, {'length': %d, 'type': 'Lf_hl_popup_%s_mode'})")""" % (statusline_win.id, lfBytesLen(current_mode) + 2, self._category)) if sep != "": - lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_sep0'})")""" % (statusline_win.id, self._category)) + lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_sep0'})")""" + % (statusline_win.id, self._category)) lfCmd("""call win_execute(%d, "call prop_add(1, %d, {'length': %d, 'type': 'Lf_hl_popup_%s_sep0'})")""" % (statusline_win.id, sep0_start, sep_len, self._category)) - lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_category'})")""" % (statusline_win.id)) + lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_category'})")""" + % (statusline_win.id)) lfCmd("""call win_execute(%d, "call prop_add(1, %d, {'length': %d, 'type': 'Lf_hl_popup_category'})")""" % (statusline_win.id, category_start, category_len)) if sep != "": - lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_sep1'})")""" % (statusline_win.id, self._category)) + lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_sep1'})")""" + % (statusline_win.id, self._category)) lfCmd("""call win_execute(%d, "call prop_add(1, %d, {'length': %d, 'type': 'Lf_hl_popup_%s_sep1'})")""" - % (statusline_win.id, sep1_start, sep_len, self._category)) + % (statusline_win.id, sep1_start, sep_len, self._category)) - lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_matchMode'})")""" % (statusline_win.id, self._category)) + lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_matchMode'})")""" + % (statusline_win.id, self._category)) lfCmd("""call win_execute(%d, "call prop_add(1, %d, {'length': %d, 'type': 'Lf_hl_popup_%s_matchMode'})")""" % (statusline_win.id, match_mode_start, match_mode_len, self._category)) if sep != "": - lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_sep2'})")""" % (statusline_win.id, self._category)) + lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_sep2'})")""" + % (statusline_win.id, self._category)) lfCmd("""call win_execute(%d, "call prop_add(1, %d, {'length': %d, 'type': 'Lf_hl_popup_%s_sep2'})")""" % (statusline_win.id, sep2_start, sep_len, self._category)) @@ -882,7 +1073,8 @@ def setPopupStl(self, current_mode): % (statusline_win.id, cwd_start, cwd_len)) if sep != "": - lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_sep3'})")""" % (statusline_win.id, self._category)) + lfCmd("""call win_execute(%d, "call prop_remove({'type': 'Lf_hl_popup_%s_sep3'})")""" + % (statusline_win.id, self._category)) lfCmd("""call win_execute(%d, "call prop_add(1, %d, {'length': %d, 'type': 'Lf_hl_popup_%s_sep3'})")""" % (statusline_win.id, sep3_start, sep_len, self._category)) elif self._win_pos == 'floatwin': @@ -965,13 +1157,25 @@ def setStlRunning(self, running): return if running: - spin = "{}".format(self._cli._spin_symbols[self._running_status]) + spin = "{} ".format(self._cli._spin_symbols[self._running_status]) self._running_status = (self._running_status + 1) % len(self._cli._spin_symbols) lfCmd("let g:Lf_{}_StlRunning = '{}'".format(self._category, spin)) else: self._running_status = 0 lfCmd("let g:Lf_{}_StlRunning = ''".format(self._category)) + def clearBufferObject(self): + """ + https://github.com/vim/vim/issues/1737 + https://github.com/vim/vim/issues/1738 + """ + if (self._buffer_object is not None and self._buffer_object.valid + and lfEval("g:Lf_RememberLastSearch") == '0' + and "--append" not in self._arguments + and "--recall" not in self._arguments): + self.buffer.options['modifiable'] = True + del self._buffer_object[:] + def enterBuffer(self, win_pos, clear): if self._enterOpeningBuffer(): return @@ -989,6 +1193,9 @@ def enterBuffer(self, win_pos, clear): self._before_enter() + if clear: + self.clearBufferObject() + if win_pos in ('popup', 'floatwin'): if lfEval("exists('g:lf_gcr_stack')") == '0': lfCmd("let g:lf_gcr_stack = []") @@ -998,9 +1205,9 @@ def enterBuffer(self, win_pos, clear): lfCmd("let g:lf_t_ve_stack = []") lfCmd("call add(g:lf_t_ve_stack, &t_ve)") lfCmd("set t_ve=") - self._orig_win_nr = vim.current.window.number - self._orig_win_id = lfWinId(self._orig_win_nr) - self._createPopupWindow(clear) + self._orig_win_num = vim.current.window.number + self._orig_win_id = lfWinId(self._orig_win_num) + self._createPopupWindow() self._arguments["popup_winid"] = self._popup_winid elif win_pos == 'fullScreen': self._orig_tabpage = vim.current.tabpage @@ -1008,8 +1215,8 @@ def enterBuffer(self, win_pos, clear): lfCmd("set showtabline=0") self._createBufWindow(win_pos) else: - self._orig_win_nr = vim.current.window.number - self._orig_win_id = lfWinId(self._orig_win_nr) + self._orig_win_num = vim.current.window.number + self._orig_win_id = lfWinId(self._orig_win_num) self._createBufWindow(win_pos) if not self._is_icon_colorscheme_autocmd_set: @@ -1032,6 +1239,7 @@ def exitBuffer(self): lfCmd("let &gcr = remove(g:lf_gcr_stack, -1)") lfCmd("set t_ve&") lfCmd("let &t_ve = remove(g:lf_t_ve_stack, -1)") + self._popup_cursor_line = self._window_object.cursor[0] self._popup_instance.hide() self._after_exit() return @@ -1040,6 +1248,7 @@ def exitBuffer(self): lfCmd("let &gcr = remove(g:lf_gcr_stack, -1)") lfCmd("set t_ve&") lfCmd("let &t_ve = remove(g:lf_t_ve_stack, -1)") + self._float_win_view = lfEval("winsaveview()") self._popup_instance.close() if self._orig_win_id is not None: lfCmd("call win_gotoid(%d)" % self._orig_win_id) @@ -1068,12 +1277,13 @@ def exitBuffer(self): vim.options['eventignore'] = saved_eventignore if len(vim.windows) > 1: - lfCmd("silent! hide") + if self._manager.is_autocmd == False: + lfCmd("silent! hide") if self._orig_win_id is not None: lfCmd("call win_gotoid(%d)" % self._orig_win_id) else: # 'silent!' is used to skip error E16. - lfCmd("silent! exec '%d wincmd w'" % self._orig_win_nr) + lfCmd("silent! exec '%d wincmd w'" % self._orig_win_num) if lfEval("get(g:, 'Lf_VimResized', 0)") == '0' \ and self._orig_win_count == len(vim.windows): lfCmd(self._restore_sizes) # why this line does not take effect? @@ -1091,7 +1301,8 @@ def exitBuffer(self): orig_win = vim.current.window for w in vim.windows: vim.current.window = w - if lfEval("exists('w:lf_win_view')") != '0' and lfEval("has_key(w:lf_win_view, '%s')" % self._category) != '0': + if (lfEval("exists('w:lf_win_view')") != '0' + and lfEval("has_key(w:lf_win_view, '%s')" % self._category) != '0'): lfCmd("call winrestview(w:lf_win_view['%s'])" % self._category) finally: vim.current.window = orig_win @@ -1108,7 +1319,8 @@ def _actualLength(self, buffer): try: num += (int(lfEval("strdisplaywidth('%s')" % escQuote(i))) + columns - 1)// columns except: - num += (int(lfEval("strdisplaywidth('%s')" % escQuote(i).replace('\x00', '\x01'))) + columns - 1)// columns + num += (int(lfEval("strdisplaywidth('%s')" + % escQuote(i).replace('\x00', '\x01'))) + columns - 1)// columns return num def setBuffer(self, content, need_copy=False): @@ -1146,7 +1358,8 @@ def setBuffer(self, content, need_copy=False): buffer_len = len(self._buffer_object) if buffer_len < self._initial_win_height: if "--nowrap" not in self._arguments: - self._window_object.height = min(self._initial_win_height, self._actualLength(self._buffer_object)) + self._window_object.height = min(self._initial_win_height, + self._actualLength(self._buffer_object)) else: self._window_object.height = buffer_len elif self._window_object.height < self._initial_win_height: @@ -1169,7 +1382,8 @@ def setBuffer(self, content, need_copy=False): buffer_len = len(self._buffer_object) if buffer_len < self._initial_win_height: if "--nowrap" not in self._arguments: - self._window_object.height = min(self._initial_win_height, self._actualLength(self._buffer_object)) + self._window_object.height = min(self._initial_win_height, + self._actualLength(self._buffer_object)) else: self._window_object.height = buffer_len elif self._window_object.height < self._initial_win_height: @@ -1189,6 +1403,9 @@ def setBuffer(self, content, need_copy=False): self.buffer.options['modifiable'] = False def refreshPopupStatusline(self): + if not self._auto_adjust_height: + return + statusline_win = self._popup_instance.statusline_win buffer_len = len(self._buffer_object) if self._win_pos == 'popup': @@ -1196,7 +1413,8 @@ def refreshPopupStatusline(self): lfCmd("call leaderf#ResetPopupOptions(%d, 'minheight', %d)" % (self._popup_winid, min(self._popup_maxheight, self._actualLength(self._buffer_object[:self._popup_maxheight])))) else: - lfCmd("call leaderf#ResetPopupOptions(%d, 'minheight', %d)" % (self._popup_winid, min(self._popup_maxheight, buffer_len))) + lfCmd("call leaderf#ResetPopupOptions(%d, 'minheight', %d)" % (self._popup_winid, + min(self._popup_maxheight, buffer_len))) if statusline_win: expected_line = self._window_object.initialLine + self._window_object.height @@ -1240,7 +1458,8 @@ def appendBuffer(self, content): buffer_len = len(self._buffer_object) if buffer_len < self._initial_win_height: if "--nowrap" not in self._arguments: - self._window_object.height = min(self._initial_win_height, self._actualLength(self._buffer_object)) + self._window_object.height = min(self._initial_win_height, + self._actualLength(self._buffer_object)) else: self._window_object.height = buffer_len elif self._window_object.height < self._initial_win_height: @@ -1266,7 +1485,8 @@ def appendBuffer(self, content): buffer_len = len(self._buffer_object) if buffer_len < self._initial_win_height: if "--nowrap" not in self._arguments: - self._window_object.height = min(self._initial_win_height, self._actualLength(self._buffer_object)) + self._window_object.height = min(self._initial_win_height, + self._actualLength(self._buffer_object)) else: self._window_object.height = buffer_len elif self._window_object.height < self._initial_win_height: @@ -1339,16 +1559,17 @@ def tabpage(self): def window(self): return self._window_object + @property + def windowId(self): + return self._window_id + @property def buffer(self): return self._buffer_object @property def currentLine(self): - if self._win_pos in ('popup', 'floatwin'): - return self._buffer_object[self._window_object.cursor[0] - 1] - else: - return vim.current.line if self._buffer_object == vim.current.buffer else None + return self._buffer_object[self._window_object.cursor[0] - 1] def empty(self): return len(self._buffer_object) == 1 and self._buffer_object[0] == '' @@ -1376,8 +1597,8 @@ def isLastReverseOrder(self): def setLineNumber(self): if self._reverse_order: - line_nr = 1 + len(self._buffer_object) - self._window_object.cursor[0] - lfCmd("let g:Lf_{}_StlLineNumber = '{}'".format(self._category, line_nr)) + line_num = 1 + len(self._buffer_object) - self._window_object.cursor[0] + lfCmd("let g:Lf_{}_StlLineNumber = '{}'".format(self._category, line_num)) def setCwd(self, cwd): self._current_working_directory = cwd @@ -1406,7 +1627,7 @@ def gotoOriginalWindow(self): lfCmd("keepj call win_gotoid(%d)" % self._orig_win_id) else: # 'silent!' is used to skip error E16. - lfCmd("keepj silent! exec '%d wincmd w'" % self._orig_win_nr) + lfCmd("keepj silent! exec '%d wincmd w'" % self._orig_win_num) def getWinPos(self): return self._win_pos @@ -1417,4 +1638,10 @@ def getPopupWinId(self): def getPopupInstance(self): return self._popup_instance + def getPopupHeight(self): + if lfEval("get(g:, 'Lf_PopupShowStatusline', 1)") == '1': + return self._popup_maxheight + 1 + else: + return self._popup_maxheight + # vim: set ts=4 sw=4 tw=0 et : diff --git a/autoload/leaderf/python/leaderf/jumpsExpl.py b/autoload/leaderf/python/leaderf/jumpsExpl.py index 1802847b..af49558a 100644 --- a/autoload/leaderf/python/leaderf/jumpsExpl.py +++ b/autoload/leaderf/python/leaderf/jumpsExpl.py @@ -21,7 +21,7 @@ def getContent(self, *args, **kwargs): return self.getFreshContent(*args, **kwargs) def getFreshContent(self, *args, **kwargs): - content = lfEval("split(execute('jumps'), '\n')") + content = lfEval("split(leaderf#execute('jumps'), '\n')") flag = ' ' self._content = [] @@ -75,6 +75,8 @@ def _acceptSelection(self, *args, **kwargs): file_text = file_text[:-1] orig_buf_num = self._getInstance().getOriginalPos()[2].number orig_buf = vim.buffers[orig_buf_num] + if self._preview_winid: + self._closePreviewPopup() # it's text if int(line) <= len(orig_buf) and orig_buf[int(line) - 1].lstrip().startswith(file_text): self._getInstance().gotoOriginalWindow() @@ -84,9 +86,9 @@ def _acceptSelection(self, *args, **kwargs): else: number = line.split(None, 1)[0] if line.endswith('\t'): - lfCmd(':exec "norm! %s\"' % number) + lfCmd(r':exec "norm! %s\"' % number) else: - lfCmd(':exec "norm! %s\"' % number) + lfCmd(r':exec "norm! %s\"' % number) if "preview" not in kwargs: lfCmd("setlocal cursorline! | redraw | sleep 150m | setlocal cursorline!") @@ -130,28 +132,28 @@ def _createHelp(self): def _afterEnter(self): super(JumpsExplManager, self)._afterEnter() if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_jumpsTitle'', ''^ \D\+'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_jumpsTitle'', ''^ \D\+'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_jumpsNumber'', ''^>\?\s*\zs\d\+'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_jumpsNumber'', ''^>\?\s*\zs\d\+'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_jumpsLineCol'', ''^>\?\s*\d\+\s*\zs\d\+\s*\d\+'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_jumpsLineCol'', ''^>\?\s*\d\+\s*\zs\d\+\s*\d\+'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_jumpsIndicator'', ''^>'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_jumpsIndicator'', ''^>'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) else: - id = int(lfEval('''matchadd('Lf_hl_jumpsTitle', '^ \D\+')''')) + id = int(lfEval(r'''matchadd('Lf_hl_jumpsTitle', '^ \D\+')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_jumpsNumber', '^>\?\s*\zs\d\+')''')) + id = int(lfEval(r'''matchadd('Lf_hl_jumpsNumber', '^>\?\s*\zs\d\+')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_jumpsLineCol', '^>\?\s*\d\+\s*\zs\d\+\s*\d\+')''')) + id = int(lfEval(r'''matchadd('Lf_hl_jumpsLineCol', '^>\?\s*\d\+\s*\zs\d\+\s*\d\+')''')) self._match_ids.append(id) id = int(lfEval('''matchadd('Lf_hl_jumpsIndicator', '^>')''')) self._match_ids.append(id) @@ -176,11 +178,13 @@ def _readFinished(self): break def _bangReadFinished(self): + super(JumpsExplManager, self)._bangReadFinished() self._readFinished() def _previewInPopup(self, *args, **kwargs): - if len(args) == 0: + if len(args) == 0 or args[0] == '': return + line = args[0] # title diff --git a/autoload/leaderf/python/leaderf/lineExpl.py b/autoload/leaderf/python/leaderf/lineExpl.py index 0b9a136c..c26cedee 100644 --- a/autoload/leaderf/python/leaderf/lineExpl.py +++ b/autoload/leaderf/python/leaderf/lineExpl.py @@ -65,8 +65,8 @@ def _acceptSelection(self, *args, **kwargs): return line = args[0] line = line.rsplit("\t", 1)[1][1:-1] # file:line buf_number - line_nr, buf_number = line.rsplit(":", 1)[1].split() - lfCmd("hide buffer +%s %s" % (line_nr, buf_number)) + line_num, buf_number = line.rsplit(":", 1)[1].split() + lfCmd("hide buffer +%s %s" % (line_num, buf_number)) lfCmd("norm! ^zv") lfCmd("norm! zz") @@ -115,12 +115,12 @@ def _createHelp(self): def _afterEnter(self): super(LineExplManager, self)._afterEnter() if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_lineLocation'', ''\t\zs\[.*:\d\+ \d\+]$'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_lineLocation'', ''\t\zs\[.*:\d\+ \d\+]$'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) else: - id = int(lfEval('''matchadd('Lf_hl_lineLocation', '\t\zs\[.*:\d\+ \d\+]$')''')) + id = int(lfEval(r'''matchadd('Lf_hl_lineLocation', '\t\zs\[.*:\d\+ \d\+]$')''')) self._match_ids.append(id) def _beforeExit(self): @@ -131,14 +131,14 @@ def _beforeExit(self): self._cursorline_dict.clear() def _previewInPopup(self, *args, **kwargs): - if len(args) == 0: + if len(args) == 0 or args[0] == '': return line = args[0] line = line.rsplit("\t", 1)[1][1:-1] # file:line buf_number - line_nr, buf_number = line.rsplit(":", 1)[1].split() + line_num, buf_number = line.rsplit(":", 1)[1].split() buf_number = int(buf_number) - self._createPopupPreview(vim.buffers[int(buf_number)].name, buf_number, line_nr) + self._createPopupPreview(vim.buffers[int(buf_number)].name, buf_number, line_num) def outputToQflist(self, *args, **kwargs): items = self._getFormatedContents() @@ -156,10 +156,10 @@ def _getFormatedContents(self): for line in self._instance._buffer_object[self._help_length:]: text, info = line.rsplit("\t", 1) info = info[1:-1] # file:line buf_number - line_nr, buf_number = info.rsplit(":", 1)[1].split() + line_num, buf_number = info.rsplit(":", 1)[1].split() items.append({ "filename": lfEval("getbufinfo(%d)[0]['name']" % int(buf_number)), - "lnum": line_nr, + "lnum": line_num, "col": 1, "text": text, }) diff --git a/autoload/leaderf/python/leaderf/manager.py b/autoload/leaderf/python/leaderf/manager.py index 90a2d275..5e8500db 100644 --- a/autoload/leaderf/python/leaderf/manager.py +++ b/autoload/leaderf/python/leaderf/manager.py @@ -60,14 +60,17 @@ def modifiableController(func): def deco(self, *args, **kwargs): self._getInstance().buffer.options['modifiable'] = True func(self, *args, **kwargs) - self._getInstance().buffer.options['modifiable'] = False + try: + self._getInstance().buffer.options['modifiable'] = False + except: + pass return deco def catchException(func): @wraps(func) def deco(self, *args, **kwargs): try: - func(self, *args, **kwargs) + return func(self, *args, **kwargs) except vim.error as e: # for neovim if str(e) != "b'Keyboard interrupt'" and str(e) != 'Keyboard interrupt': raise e @@ -80,19 +83,20 @@ def deco(self, *args, **kwargs): self._timer_id = None return deco -def ignoreEvent(events): - def wrapper(func): - @wraps(func) - def deco(self, *args, **kwargs): - try: - saved_eventignore = vim.options['eventignore'] - vim.options['eventignore'] = events +def windo(func): + if lfEval("has('nvim')") == '0': + return func + + @wraps(func) + def deco(self, *args, **kwargs): + try: + cur_winid = lfEval("win_getid()") + lfCmd("noautocmd call win_gotoid(%d)" % self._getInstance().windowId) + return func(self, *args, **kwargs) + finally: + lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) - func(self, *args, **kwargs) - finally: - vim.options['eventignore'] = saved_eventignore - return deco - return wrapper + return deco #***************************************************** # Manager @@ -100,9 +104,9 @@ def deco(self, *args, **kwargs): class Manager(object): def __init__(self): self._autochdir = 0 - self._instance = None self._cli = LfCli() self._explorer = None + self._instance = None self._content = [] self._index = 0 self._help_length = 0 @@ -112,8 +116,7 @@ def __init__(self): self._highlight_pos_list = [] self._highlight_refine_pos = [] self._highlight_ids = [] - self._orig_line = '' - self._ctrlp_pressed = False + self._orig_line = None self._fuzzy_engine = None self._result_content = [] self._reader_thread = None @@ -128,6 +131,14 @@ def __init__(self): self._vim_file_autoloaded = False self._arguments = {} self._getExplClass() + self._preview_filetype = None + self._orig_source = None + self._preview_config = {} + self.is_autocmd = False + self.is_ctrl_c = False + self._circular_scroll = lfEval("get(g:, 'Lf_EnableCircularScroll', 0)") == '1' + if lfEval("has('patch-8.1.1615') || has('nvim-0.5.0')") == '0': + lfCmd("let g:Lf_PreviewInPopup = 0") #************************************************************** # abstract methods, in fact all the functions can be overridden @@ -143,9 +154,26 @@ def _getExplClass(self): def _defineMaps(self): pass + def _defineNormalCommandMaps(self): + normal_map = lfEval("get(g:, 'Lf_NormalCommandMap', {})") + if not normal_map: + return + + command_map = normal_map.get("*", {}) + command_map.update(normal_map.get(self._getExplorer().getStlCategory(), {})) + new_commands = set(i.lower() if i.startswith('<') else i for i in command_map.values()) + map_rhs = {k: lfEval("maparg('{}', 'n', 0, 1)".format(k)) for k in command_map} + for old, new in command_map.items(): + maparg = map_rhs[old] + if maparg and maparg["buffer"] == '1': + lfCmd("silent! nnoremap {} {}".format(new, maparg["rhs"])) + old_cmd = old.lower() if old.startswith('<') else old + if old_cmd not in new_commands: + lfCmd("silent! nunmap {}".format(old)) + def _defineCommonMaps(self): normal_map = lfEval("get(g:, 'Lf_NormalMap', {})") - if "_" not in normal_map: + if "_" not in normal_map: return for [lhs, rhs] in normal_map["_"]: @@ -163,9 +191,16 @@ def _cmdExtension(self, cmd): @removeDevIcons def _argaddFiles(self, files): - # It will raise E480 without 'silent!' - lfCmd("silent! argdelete *") + # simply delete all, without err print + lfCmd("%argdelete") for file in files: + if not os.path.isabs(file): + if self._getExplorer()._cmd_work_dir: + file = os.path.join(self._getExplorer()._cmd_work_dir, lfDecode(file)) + else: + file = os.path.join(self._getInstance().getCwd(), lfDecode(file)) + file = os.path.normpath(lfEncode(file)) + lfCmd("argadd %s" % escSpecial(file)) def _issue_422_set_option(self): @@ -179,6 +214,9 @@ def _issue_422_set_option(self): def _acceptSelection(self, *args, **kwargs): pass + def autoJump(self, content): + return False + def _getDigest(self, line, mode): """ this function can be overridden @@ -212,7 +250,7 @@ def _getDigestStartPos(self, line, mode): def _createHelp(self): return [] - def _setStlMode(self, **kwargs): + def _setStlMode(self, the_mode=None, **kwargs): if self._cli.isFuzzy: if self._getExplorer().supportsNameOnly(): if self._cli.isFullPath: @@ -240,16 +278,27 @@ def _setStlMode(self, **kwargs): else: mode = 'NameOnly' elif opt in ("--nameOnly", "--fullPath", "--fuzzy"): - mode = 'Fuzzy' + mode = 'Fuzzy' break - self._getInstance().setStlMode(mode) + if the_mode is not None: + mode = the_mode + elif self._cli._is_live: + mode = 'Fuzzy' + + self.setStlMode(mode) self._cli.setCurrentMode(mode) + def setStlMode(self, mode): + self._getInstance().setStlMode(mode) + def _beforeEnter(self): self._resetAutochdir() self._cur_buffer = vim.current.buffer + self._laststatus = lfEval("&laststatus") + if self._laststatus == '0': + lfCmd("set laststatus=2") def _afterEnter(self): if self._vim_file_autoloaded == False: @@ -277,11 +326,12 @@ def _afterEnter(self): if self._getInstance().getWinPos() != 'popup': self._defineMaps() self._defineCommonMaps() + self._defineNormalCommandMaps() - id = int(lfEval("matchadd('Lf_hl_cursorline', '.*\%#.*', 9)")) + id = int(lfEval(r"matchadd('Lf_hl_cursorline', '.*\%#.*', -100)")) self._match_ids.append(id) else: - lfCmd("""call win_execute({}, 'let matchid = matchadd(''Lf_hl_cursorline'', ''.*\%#.*'', 9)')""" + lfCmd(r"""call win_execute({}, 'let matchid = matchadd(''Lf_hl_cursorline'', ''.*\%#.*'', -100)')""" .format(self._getInstance().getPopupWinId())) id = int(lfEval("matchid")) self._match_ids.append(id) @@ -313,10 +363,10 @@ def _beforeExit(self): self._match_ids = [] def _afterExit(self): - pass + if self._laststatus == '0': + lfCmd("set laststatus=%s" % self._laststatus) def _bangEnter(self): - self._preview_open = False self._current_mode = 'NORMAL' if self._getInstance().getWinPos() == 'popup': self._cli.hideCursor() @@ -334,9 +384,7 @@ def _bangEnter(self): self._getInstance().appendBuffer(self._result_content[self._initial_count:]) def _bangReadFinished(self): - if self._preview_open == False and self._getInstance().getWinPos() in ('popup', 'floatwin'): - self._previewResult(False) - self._preview_open = True + pass def _getList(self, pairs): """ @@ -359,35 +407,52 @@ def _supportsRefine(self): def _previewInPopup(self, *args, **kwargs): pass + def closePreviewPopupOrQuit(self): + if self._getInstance().getWinPos() in ('popup', 'floatwin'): + self.quit() + elif self._preview_winid: + self._closePreviewPopup() + else: + self.quit() + def _closePreviewPopup(self): if lfEval("has('nvim')") == '1': if self._preview_winid: if int(lfEval("nvim_win_is_valid(%d) == v:true" % self._preview_winid)): - lfCmd("noautocmd call nvim_win_close(%d, 1)" % self._preview_winid) + lfCmd("call nvim_win_close(%d, 1)" % self._preview_winid) self._preview_winid = 0 else: if self._preview_winid: - lfCmd("noautocmd call popup_close(%d)" % self._preview_winid) + lfCmd("call popup_close(%d)" % self._preview_winid) self._preview_winid = 0 + lfCmd("silent! noautocmd bwipe /Lf_preview_{}".format(id(self))) + + self._preview_filetype = None def _previewResult(self, preview): - if self._getInstance().getWinPos() == 'floatwin': - self._cli.buildPopupPrompt() + preview_in_popup = (lfEval("get(g:, 'Lf_PreviewInPopup', 1)") == '1' + or self._getInstance().getWinPos() in ('popup', 'floatwin')) - if lfEval("get(g:, 'Lf_PreviewInPopup', 0)") == '1': - if self._orig_line != self._getInstance().currentLine: - self._closePreviewPopup() - else: - return + if preview == False and self._orig_line == self._getInstance().currentLine: + return - if not self._needPreview(preview): + self._orig_line = self._getInstance().currentLine + + if not self._needPreview(preview, preview_in_popup): + if preview_in_popup: + self._closePreviewPopup() + self._getInstance().enlargePopupWindow() return - line = self._getInstance().currentLine + line_num = self._getInstance().window.cursor[0] + line = self._getInstance().buffer[line_num - 1] + + if preview_in_popup: + self._previewInPopup(line, self._getInstance().buffer, line_num) + + if not self.isPreviewWindowOpen(): + self._getInstance().enlargePopupWindow() - if lfEval("get(g:, 'Lf_PreviewInPopup', 0)") == '1': - line_nr = self._getInstance().window.cursor[0] - self._previewInPopup(line, self._getInstance().buffer, line_nr) return orig_pos = self._getInstance().getOriginalPos() @@ -397,8 +462,8 @@ def _previewResult(self, preview): vim.options['eventignore'] = 'BufLeave,WinEnter,BufEnter' try: vim.current.tabpage, vim.current.window = orig_pos[:2] - line_nr = self._getInstance().window.cursor[0] - self._acceptSelection(line, self._getInstance().buffer, line_nr, preview=True) + line_num = self._getInstance().window.cursor[0] + self._acceptSelection(line, self._getInstance().buffer, line_num, preview=True) lfCmd("augroup Lf_Cursorline") lfCmd("autocmd! BufwinEnter setlocal cursorline<") lfCmd("augroup END") @@ -435,8 +500,149 @@ def getArguments(self): #************************************************************** + def _setWinOptions(self, winid): + if lfEval("has('nvim')") == '1': + lfCmd("call nvim_win_set_option(%d, 'number', v:true)" % winid) + lfCmd("call nvim_win_set_option(%d, 'relativenumber', v:false)" % winid) + lfCmd("call nvim_win_set_option(%d, 'cursorline', v:true)" % winid) + lfCmd("call nvim_win_set_option(%d, 'foldenable', v:false)" % winid) + lfCmd("call nvim_win_set_option(%d, 'foldmethod', 'manual')" % winid) + lfCmd("call nvim_win_set_option(%d, 'foldcolumn', '0')" % winid) + lfCmd("call nvim_win_set_option(%d, 'signcolumn', 'no')" % winid) + if lfEval("exists('+cursorlineopt')") == '1': + lfCmd("call nvim_win_set_option(%d, 'cursorlineopt', 'both')" % winid) + lfCmd("call nvim_win_set_option(%d, 'colorcolumn', '')" % winid) + lfCmd("call nvim_win_set_option(%d, 'winhighlight', 'Normal:Lf_hl_popup_window')" % winid) + else: + lfCmd("noautocmd call win_execute(%d, 'setlocal number norelativenumber cursorline')" % winid) + lfCmd("noautocmd call win_execute(%d, 'setlocal nofoldenable foldmethod=manual')" % winid) + if lfEval("get(g:, 'Lf_PopupShowFoldcolumn', 1)") == '0' or lfEval("get(g:, 'Lf_PopupShowBorder', 1)") == '1': + lfCmd("call win_execute(%d, 'setlocal foldcolumn=0')" % winid) + else: + lfCmd("call win_execute(%d, 'setlocal foldcolumn=1')" % winid) + if lfEval("exists('+cursorlineopt')") == '1': + lfCmd("call win_execute(%d, 'setlocal cursorlineopt=both')" % winid) + lfCmd("call win_execute(%d, 'setlocal colorcolumn=')" % winid) + lfCmd("call win_execute(%d, 'setlocal wincolor=Lf_hl_popup_window')" % winid) + + def _createPreviewWindow(self, config, source, line_num, jump_cmd): + self._preview_config = config + self._orig_source = source + + if lfEval("has('nvim')") == '1': + if isinstance(source, int): + buffer_len = len(vim.buffers[source]) + self._preview_winid = int(lfEval("nvim_open_win(%d, 0, %s)" % (source, str(config)))) + else: + try: + if self._isBinaryFile(source): + lfCmd("""let content = map(range(128), '"^@"')""") + else: + lfCmd("let content = readfile('%s', '', 20480)" % escQuote(source)) + except vim.error as e: + lfPrintError(e) + return + buffer_len = int(lfEval("len(content)")) + lfCmd("noautocmd let g:Lf_preview_scratch_buffer = nvim_create_buf(0, 1)") + lfCmd("noautocmd call setbufline(g:Lf_preview_scratch_buffer, 1, content)") + lfCmd("noautocmd call nvim_buf_set_option(g:Lf_preview_scratch_buffer, 'bufhidden', 'wipe')") + lfCmd("noautocmd call nvim_buf_set_option(g:Lf_preview_scratch_buffer, 'undolevels', -1)") + lfCmd("noautocmd call nvim_buf_set_option(g:Lf_preview_scratch_buffer, 'modeline', v:true)") + + self._preview_winid = int(lfEval("nvim_open_win(g:Lf_preview_scratch_buffer, 0, %s)" % str(config))) + + cur_winid = lfEval("win_getid()") + lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) + if not isinstance(source, int): + file_type = getExtension(source) + if file_type is None: + lfCmd("silent! doautocmd filetypedetect BufNewFile %s" % source) + else: + lfCmd("noautocmd set ft=%s" % getExtension(source)) + lfCmd("set syntax=%s" % getExtension(source)) + lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) + + self._setWinOptions(self._preview_winid) + self._preview_filetype = lfEval("getbufvar(winbufnr(%d), '&ft')" % self._preview_winid) + + lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) + if jump_cmd: + lfCmd(jump_cmd) + if buffer_len >= line_num > 0: + lfCmd("""call nvim_win_set_cursor(%d, [%d, 1])""" % (self._preview_winid, line_num)) + lfCmd("norm! zz") + lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) + elif lfEval("exists('*popup_setbuf')") == "1": + if isinstance(source, int): + lfCmd("noautocmd silent! let winid = popup_create(%d, %s)" + % (source, json.dumps(config))) + else: + filename = source + try: + if self._isBinaryFile(filename): + lfCmd("""let content = map(range(128), '"^@"')""") + else: + lfCmd("let content = readfile('%s', '', 20480)" % escQuote(filename)) + except vim.error as e: + lfPrintError(e) + return + + lfCmd("noautocmd silent! let winid = popup_create(bufadd('/Lf_preview_%s'), %s)" + % (id(self), json.dumps(config))) + lfCmd("call win_execute(winid, 'setlocal modeline')") + lfCmd("call win_execute(winid, 'setlocal undolevels=-1')") + lfCmd("call win_execute(winid, 'setlocal noswapfile')") + lfCmd("call win_execute(winid, 'setlocal nobuflisted')") + lfCmd("call win_execute(winid, 'setlocal bufhidden=hide')") + lfCmd("call win_execute(winid, 'setlocal buftype=nofile')") + lfCmd("noautocmd call popup_settext(winid, content)") + + lfCmd("call win_execute(winid, 'silent! doautocmd filetypedetect BufNewFile %s')" + % escQuote(filename)) + + self._preview_winid = int(lfEval("winid")) + self._setWinOptions(self._preview_winid) + self._preview_filetype = lfEval("getbufvar(winbufnr(winid), '&ft')") + + if jump_cmd: + lfCmd("""call win_execute(%d, '%s')""" % (self._preview_winid, escQuote(jump_cmd))) + lfCmd("call win_execute(%d, 'norm! zz')" % self._preview_winid) + elif line_num > 0: + lfCmd("""call win_execute(%d, "call cursor(%d, 1)")""" % (self._preview_winid, line_num)) + lfCmd("call win_execute(%d, 'norm! zz')" % self._preview_winid) + else: + if isinstance(source, int): + lfCmd("let content = getbufline(%d, 1, '$')" % source) + filename = vim.buffers[source].name + else: + filename = source + try: + if self._isBinaryFile(filename): + lfCmd("""let content = map(range(128), '"^@"')""") + else: + lfCmd("let content = readfile('%s', '', 20480)" % escQuote(filename)) + except vim.error as e: + lfPrintError(e) + return + + lfCmd("noautocmd silent! let winid = popup_create(content, %s)" % json.dumps(config)) + lfCmd("call win_execute(winid, 'setlocal modeline')") + + lfCmd("call win_execute(winid, 'silent! doautocmd filetypedetect BufNewFile %s')" % escQuote(filename)) + + self._preview_winid = int(lfEval("winid")) + self._setWinOptions(self._preview_winid) + self._preview_filetype = lfEval("getbufvar(winbufnr(winid), '&ft')") + + if jump_cmd: + lfCmd("""call win_execute(%d, '%s')""" % (self._preview_winid, escQuote(jump_cmd))) + lfCmd("call win_execute(%d, 'norm! zz')" % self._preview_winid) + elif line_num > 0: + lfCmd("""call win_execute(%d, "call cursor(%d, 1)")""" % (self._preview_winid, line_num)) + lfCmd("call win_execute(%d, 'norm! zz')" % self._preview_winid) + @ignoreEvent('BufWinEnter,BufEnter') - def _createPopupModePreview(self, title, source, line_nr, jump_cmd): + def _createPopupModePreview(self, title, source, line_num, jump_cmd): """ Args: source: @@ -444,31 +650,41 @@ def _createPopupModePreview(self, title, source, line_nr, jump_cmd): if the type is str, it is a file name """ + self._getInstance().shrinkPopupWindow() + self._is_previewed = True + + show_borders = lfEval("get(g:, 'Lf_PopupShowBorder', 1)") == '1' + preview_pos = self._arguments.get("--preview-position", [""])[0] + if preview_pos == "": + preview_pos = lfEval("get(g:, 'Lf_PopupPreviewPosition', 'right')") + if lfEval("has('nvim')") == '1': width = int(lfEval("get(g:, 'Lf_PreviewPopupWidth', 0)")) - if width == 0: + if width <= 0: maxwidth = int(lfEval("&columns"))//2 else: maxwidth = min(width, int(lfEval("&columns"))) - relative = 'editor' - if isinstance(source, int): - buffer_len = len(vim.buffers[source]) - else: - try: - lfCmd("let content = readfile('%s', '', 4096)" % escQuote(source)) - except vim.error as e: - lfPrintError(e) - return - buffer_len = int(lfEval("len(content)")) - lfCmd("let scratch_buffer = nvim_create_buf(0, 1)") - lfCmd("call setbufline(scratch_buffer, 1, content)") - lfCmd("call nvim_buf_set_option(scratch_buffer, 'bufhidden', 'wipe')") float_window = self._getInstance().window + # row and col start from 0 float_win_row = int(float(lfEval("nvim_win_get_config(%d).row" % float_window.id))) float_win_col = int(float(lfEval("nvim_win_get_config(%d).col" % float_window.id))) - preview_pos = lfEval("get(g:, 'Lf_PopupPreviewPosition', 'top')") + float_win_height = int(float(lfEval("nvim_win_get_config(%d).height" % float_window.id))) + float_win_width= int(float(lfEval("nvim_win_get_config(%d).width" % float_window.id))) + popup_borders = lfEval("g:Lf_PopupBorders") + borderchars = [ + [popup_borders[4], "Lf_hl_popupBorder"], + [popup_borders[0], "Lf_hl_popupBorder"], + [popup_borders[5], "Lf_hl_popupBorder"], + [popup_borders[1], "Lf_hl_popupBorder"], + [popup_borders[6], "Lf_hl_popupBorder"], + [popup_borders[2], "Lf_hl_popupBorder"], + [popup_borders[7], "Lf_hl_popupBorder"], + [popup_borders[3], "Lf_hl_popupBorder"] + ] + + relative = 'editor' if preview_pos.lower() == 'bottom': anchor = "NW" if self._getInstance().getPopupInstance().statusline_win: @@ -477,27 +693,65 @@ def _createPopupModePreview(self, title, source, line_nr, jump_cmd): statusline_height = 0 row = float_win_row + float_window.height + statusline_height col = float_win_col - height = int(lfEval("&lines")) - row - 2 + height = int(lfEval("&lines")) - row - 3 if height < 1: return width = float_window.width + borderchars[0] = '' + borderchars[1] = '' + borderchars[2] = '' elif preview_pos.lower() == 'top': anchor = "SW" row = float_win_row - 1 + if show_borders: + row -= 1 col = float_win_col height = row if height < 1: return width = float_window.width + borderchars[4] = '' + borderchars[5] = '' + borderchars[6] = '' + elif preview_pos.lower() == 'right': + anchor = "NW" + row = float_win_row - 1 + col = float_win_col + float_win_width + if show_borders: + row -= 1 + col += 2 + height = self._getInstance().getPopupHeight() + 1 + if width <= 0: + width = float_win_width + if show_borders: + width = min(width, int(lfEval("&columns")) - col - 2) + else: + width = min(width, int(lfEval("&columns")) - col) + elif preview_pos.lower() == 'left': + anchor = "NE" + row = float_win_row - 1 + col = float_win_col + if show_borders: + row -= 1 + height = self._getInstance().getPopupHeight() + 1 + if width <= 0: + width = float_win_width + width = min(width, col) else: - anchor = "SW" start = int(lfEval("line('w0')")) - 1 end = int(lfEval("line('.')")) - 1 col_width = float_window.width - int(lfEval("&numberwidth")) - 1 delta_height = lfActualLineCount(self._getInstance().buffer, start, end, col_width) - row = float_win_row + delta_height + win_height = int(lfEval("&lines")) + if float_win_row + delta_height < win_height // 2: + anchor = "NW" + row = float_win_row + delta_height + 1 + height = win_height - int(lfEval("&cmdheight")) - row + else: + anchor = "SW" + row = float_win_row + delta_height + height = row col = float_win_col + int(lfEval("&numberwidth")) + 1 + float_window.cursor[1] - height = row width = maxwidth config = { @@ -505,60 +759,30 @@ def _createPopupModePreview(self, title, source, line_nr, jump_cmd): "anchor" : anchor, "height" : height, "width" : width, + "zindex" : 20481, "row" : row, - "col" : col + "col" : col, + "noautocmd": 1 } - if isinstance(source, int): - self._preview_winid = int(lfEval("nvim_open_win(%d, 0, %s)" % (source, str(config)))) - else: - self._preview_winid = int(lfEval("nvim_open_win(scratch_buffer, 0, %s)" % str(config))) - lfCmd("let g:Lf_PreviewWindowID[%d] = %d" % (id(self), self._preview_winid)) - if jump_cmd: - cur_winid = lfEval("win_getid()") - lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) - lfCmd(jump_cmd) - lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) - if buffer_len >= line_nr > 0: - lfCmd("""call nvim_win_set_cursor(%d, [%d, 1])""" % (self._preview_winid, line_nr)) + if show_borders: + config["border"] = borderchars + if lfEval("has('nvim-0.9.0')") == '1': + config["title"] = " Preview " + config["title_pos"] = "center" - lfCmd("call nvim_win_set_option(%d, 'number', v:true)" % self._preview_winid) - lfCmd("call nvim_win_set_option(%d, 'relativenumber', v:false)" % self._preview_winid) - lfCmd("call nvim_win_set_option(%d, 'cursorline', v:true)" % self._preview_winid) - lfCmd("call nvim_win_set_option(%d, 'foldmethod', 'manual')" % self._preview_winid) - if lfEval("exists('+cursorlineopt')") == '1': - lfCmd("call nvim_win_set_option(%d, 'cursorlineopt', 'both')" % self._preview_winid) - lfCmd("call nvim_win_set_option(%d, 'colorcolumn', '')" % self._preview_winid) - lfCmd("call nvim_win_set_option(%d, 'winhighlight', 'Normal:Lf_hl_popup_window')" % self._preview_winid) - cur_winid = lfEval("win_getid()") - lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) - if not isinstance(source, int): - lfCmd("doautocmd filetypedetect BufNewFile %s" % source) - lfCmd("silent! %foldopen!") - lfCmd("norm! zz") - lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) - # lfCmd("redraw!") # maybe we don't need it, it makes the preview slow + self._createPreviewWindow(config, source, line_num, jump_cmd) + lfCmd("let g:Lf_PreviewWindowID[%d] = %d" % (id(self), self._preview_winid)) else: popup_window = self._getInstance().window popup_pos = lfEval("popup_getpos(%d)" % popup_window.id) width = int(lfEval("get(g:, 'Lf_PreviewPopupWidth', 0)")) - if width == 0: + if width <= 0: maxwidth = int(lfEval("&columns"))//2 - 1 else: maxwidth = min(width, int(lfEval("&columns"))) - if isinstance(source, int): - buffer_len = len(vim.buffers[source]) - else: - try: - lfCmd("let content = readfile('%s', '', 4096)" % escQuote(source)) - except vim.error as e: - lfPrintError(e) - return - buffer_len = int(lfEval("len(content)")) - - preview_pos = lfEval("get(g:, 'Lf_PopupPreviewPosition', 'top')") if preview_pos.lower() == 'bottom': maxwidth = int(popup_pos["width"]) col = int(popup_pos["col"]) @@ -568,12 +792,10 @@ def _createPopupModePreview(self, title, source, line_nr, jump_cmd): statusline_height = 0 line = int(popup_pos["line"]) + int(popup_pos["height"]) + statusline_height pos = "topleft" - maxheight = int(lfEval("&lines")) - line + maxheight = int(lfEval("&lines")) - line - 2 if maxheight < 1: return - if buffer_len >= maxheight: # scrollbar appear - maxwidth -= 1 elif preview_pos.lower() == 'top': maxwidth = int(popup_pos["width"]) col = int(popup_pos["col"]) @@ -582,11 +804,24 @@ def _createPopupModePreview(self, title, source, line_nr, jump_cmd): if maxheight < 1: return - if buffer_len >= maxheight: # scrollbar appear - maxwidth -= 1 - pos = "botleft" line = maxheight + 1 + elif preview_pos.lower() == 'right': + col = int(popup_pos["col"]) + int(popup_pos["width"]) + line = int(popup_pos["line"]) - 1 + maxheight = self._getInstance().getPopupHeight() + pos = "topleft" + if width == 0: + maxwidth = int(popup_pos["width"]) + maxwidth = min(maxwidth, int(lfEval("&columns")) - col + 1) + elif preview_pos.lower() == 'left': + col = int(popup_pos["col"]) - 1 + line = int(popup_pos["line"]) - 1 + maxheight = self._getInstance().getPopupHeight() + pos = "topright" + if width == 0: + maxwidth = int(popup_pos["width"]) + maxwidth = min(maxwidth, col) else: # cursor lfCmd("""call win_execute(%d, "let numberwidth = &numberwidth")""" % popup_window.id) col = int(popup_pos["core_col"]) + int(lfEval("numberwidth")) + popup_window.cursor[1] @@ -603,7 +838,7 @@ def _createPopupModePreview(self, title, source, line_nr, jump_cmd): line = maxheight + 1 options = { - "title": title, + "title": " Preview ", "maxwidth": maxwidth, "minwidth": maxwidth, "maxheight": maxheight, @@ -612,6 +847,7 @@ def _createPopupModePreview(self, title, source, line_nr, jump_cmd): "pos": pos, "line": line, "col": col, + "scrollbar": 0, "padding": [0, 0, 0, 0], "border": [1, 0, 0, 0], "borderchars": [' '], @@ -619,9 +855,34 @@ def _createPopupModePreview(self, title, source, line_nr, jump_cmd): "filter": "leaderf#popupModePreviewFilter", } + if show_borders: + options["borderchars"] = lfEval("g:Lf_PopupBorders") + options["maxwidth"] -= 2 + options["minwidth"] -= 2 + options["borderhighlight"] = ["Lf_hl_popupBorder"] + if preview_pos.lower() == 'bottom': del options["title"] options["border"] = [0, 0, 1, 0] + if show_borders: + options["border"] = [0, 1, 1, 1] + elif preview_pos.lower() == 'top': + if show_borders: + options["border"] = [1, 1, 0, 1] + elif preview_pos.lower() == 'right': + if show_borders: + options["border"] = [1, 1, 1, 1] + options["line"] -= 1 + # options["col"] += 1 + options["maxheight"] += 1 + options["minheight"] += 1 + elif preview_pos.lower() == 'left': + if show_borders: + options["border"] = [1, 1, 1, 1] + options["line"] -= 1 + # options["col"] -= 1 + options["maxheight"] += 1 + options["minheight"] += 1 elif preview_pos.lower() == 'cursor' and maxheight < int(lfEval("&lines"))//2 - 2: maxheight = int(lfEval("&lines")) - maxheight - 5 del options["title"] @@ -629,218 +890,426 @@ def _createPopupModePreview(self, title, source, line_nr, jump_cmd): options["maxheight"] = maxheight options["minheight"] = maxheight - if isinstance(source, int): - lfCmd("noautocmd silent! let winid = popup_create(%d, %s)" % (source, json.dumps(options))) + self._createPreviewWindow(options, source, line_num, jump_cmd) + + + def isPreviewWindowOpen(self): + return self._preview_winid > 0 and int(lfEval("winbufnr(%d)" % self._preview_winid)) != -1 + + def _isBinaryFile(self, filename): + try: + is_binary = False + with lfOpen(filename, 'r', encoding='utf-8', errors='ignore') as f: + data = f.read(128) + for i in data: + if i == '\0': + is_binary = True + break + + return is_binary + except Exception as e: + lfPrintError(e) + return True + + def setOptionsForCursor(self): + preview_pos = self._arguments.get("--preview-position", [""])[0] + if preview_pos == "": + preview_pos = lfEval("get(g:, 'Lf_PreviewPosition', 'top')") + + if preview_pos == "cursor" and self._getInstance().getWinPos() not in ('popup', 'floatwin'): + show_borders = lfEval("get(g:, 'Lf_PopupShowBorder', 1)") == '1' + self._updateOptions(preview_pos, show_borders, self._preview_config) + if lfEval("has('nvim')") == '1': + if 'noautocmd' in self._preview_config: + del self._preview_config['noautocmd'] + lfCmd("call nvim_win_set_config(%d, %s)" % (self._preview_winid, str(self._preview_config))) else: - lfCmd("silent! let winid = popup_create(content, %s)" % json.dumps(options)) - lfCmd("call win_execute(winid, 'doautocmd filetypedetect BufNewFile %s')" % escQuote(source)) + lfCmd("call popup_setoptions(%d, %s)" % (self._preview_winid, str(self._preview_config))) - self._preview_winid = int(lfEval("winid")) - if jump_cmd: - lfCmd("""call win_execute(%d, '%s')""" % (self._preview_winid, escQuote(jump_cmd))) - elif line_nr > 0: - lfCmd("""call win_execute(%d, "call cursor(%d, 1)")""" % (self._preview_winid, line_nr)) - lfCmd("call win_execute(%d, 'setlocal cursorline number norelativenumber colorcolumn= ')" % self._preview_winid) - lfCmd("call win_execute(%d, 'setlocal foldmethod=manual')" % self._preview_winid) - if lfEval("exists('+cursorlineopt')") == '1': - lfCmd("call win_execute(%d, 'setlocal cursorlineopt=both')" % self._preview_winid) - lfCmd("call win_execute(%d, 'setlocal wincolor=Lf_hl_popup_window')" % self._preview_winid) - if lfEval("get(g:, 'Lf_PopupShowFoldcolumn', 1)") == '0': - lfCmd("call win_execute(%d, 'setlocal foldcolumn=0')" % self._preview_winid) + def _useExistingWindow(self, title, source, line_num, jump_cmd): + self.setOptionsForCursor() + + if self._orig_source != source: + self._orig_source = source + + if lfEval("has('nvim')") == '1': + if isinstance(source, int): + lfCmd("silent noautocmd call nvim_win_set_buf(%d, %d)" % (self._preview_winid, source)) + self._setWinOptions(self._preview_winid) + else: + try: + if self._isBinaryFile(source): + lfCmd("""let content = map(range(128), '"^@"')""") + else: + lfCmd("let content = readfile('%s', '', 20480)" % escQuote(source)) + except vim.error as e: + lfPrintError(e) + return + if lfEval("!exists('g:Lf_preview_scratch_buffer') || !bufexists(g:Lf_preview_scratch_buffer)") == '1': + lfCmd("noautocmd let g:Lf_preview_scratch_buffer = nvim_create_buf(0, 1)") + lfCmd("noautocmd call nvim_buf_set_option(g:Lf_preview_scratch_buffer, 'undolevels', -1)") + lfCmd("noautocmd call nvim_buf_set_option(g:Lf_preview_scratch_buffer, 'modeline', v:true)") + lfCmd("noautocmd call nvim_buf_set_lines(g:Lf_preview_scratch_buffer, 0, -1, v:false, content)") + lfCmd("silent noautocmd call nvim_win_set_buf(%d, g:Lf_preview_scratch_buffer)" % self._preview_winid) + preview_filetype = lfEval("getbufvar(winbufnr(%d), '&ft')" % self._preview_winid) + + cur_filetype = getExtension(source) + if cur_filetype != preview_filetype: + if cur_filetype is None: + lfCmd("call win_execute(%d, 'silent! doautocmd filetypedetect BufNewFile %s')" + % (self._preview_winid, escQuote(source))) + else: + lfCmd("call win_execute(%d, 'noautocmd set ft=%s')" % (self._preview_winid, cur_filetype)) + lfCmd("call win_execute(%d, 'set syntax=%s')" % (self._preview_winid, cur_filetype)) + elif lfEval("exists('*popup_setbuf')") == "1": + if isinstance(source, int): + lfCmd("call popup_setbuf(%d, %d)" % (self._preview_winid, source)) + else: + filename = source + try: + if self._isBinaryFile(filename): + lfCmd("""let content = map(range(128), '"^@"')""") + else: + lfCmd("let content = readfile('%s', '', 20480)" % escQuote(filename)) + except vim.error as e: + lfPrintError(e) + return + lfCmd("silent call popup_setbuf(%d, bufadd('/Lf_preview_%d'))" % (self._preview_winid, id(self))) + lfCmd("call win_execute(%d, 'setlocal modeline')" % self._preview_winid) + lfCmd("call win_execute(%d, 'setlocal undolevels=-1')" % self._preview_winid) + lfCmd("call win_execute(%d, 'setlocal noswapfile')" % self._preview_winid) + lfCmd("call win_execute(%d, 'setlocal nobuflisted')" % self._preview_winid) + lfCmd("call win_execute(%d, 'setlocal bufhidden=hide')" % self._preview_winid) + lfCmd("call win_execute(%d, 'setlocal buftype=nofile')" % self._preview_winid) + lfCmd("noautocmd call popup_settext(%d, content)" % self._preview_winid) + cur_filetype = lfEval("getbufvar(winbufnr(%d), '&ft')" % self._preview_winid) + + if cur_filetype != getExtension(filename): + lfCmd("call win_execute(%d, 'silent! doautocmd filetypedetect BufNewFile %s')" + % (self._preview_winid, escQuote(filename))) else: - lfCmd("call win_execute(%d, 'setlocal foldcolumn=1')" % self._preview_winid) + if isinstance(source, int): + lfCmd("noautocmd call popup_settext(%d, getbufline(%d, 1, 20480))" % (self._preview_winid, source)) + filename = vim.buffers[source].name + else: + filename = source + try: + if self._isBinaryFile(filename): + lfCmd("""let content = map(range(128), '"^@"')""") + else: + lfCmd("let content = readfile('%s', '', 20480)" % escQuote(filename)) + except vim.error as e: + lfPrintError(e) + return + lfCmd("noautocmd call popup_settext(%d, content)" % self._preview_winid) + + cur_filetype = getExtension(filename) + if cur_filetype != self._preview_filetype: + lfCmd("call win_execute(%d, 'silent! doautocmd filetypedetect BufNewFile %s')" % (self._preview_winid, escQuote(filename))) + self._preview_filetype = lfEval("getbufvar(winbufnr(%d), '&ft')" % self._preview_winid) + + self._setWinOptions(self._preview_winid) + + if jump_cmd: + lfCmd("""call win_execute(%d, '%s')""" % (self._preview_winid, escQuote(jump_cmd))) + lfCmd("call win_execute(%d, 'norm! zz')" % self._preview_winid) + elif line_num > 0: + lfCmd("""call win_execute(%d, "call cursor(%d, 1)")""" % (self._preview_winid, line_num)) lfCmd("call win_execute(%d, 'norm! zz')" % self._preview_winid) + else: + lfCmd("call win_execute(%d, 'norm! gg')" % self._preview_winid) @ignoreEvent('BufRead,BufReadPre,BufReadPost') - def _createPopupPreview(self, title, source, line_nr, jump_cmd=''): + def _createPopupPreview(self, title, source, line_num, jump_cmd=''): """ Args: source: if the type is int, it is a buffer number if the type is str, it is a file name + return False if use existing window, otherwise True """ self._is_previewed = True - line_nr = int(line_nr) + line_num = int(line_num) + + if self.isPreviewWindowOpen(): + self._useExistingWindow(title, source, line_num, jump_cmd) + return False if self._getInstance().getWinPos() in ('popup', 'floatwin'): - self._createPopupModePreview(title, source, line_nr, jump_cmd) - return + self._createPopupModePreview(title, source, line_num, jump_cmd) + return True + + win_pos = self._getInstance().getWinPos() + show_borders = lfEval("get(g:, 'Lf_PopupShowBorder', 1)") == '1' + preview_pos = self._arguments.get("--preview-position", [""])[0] + if preview_pos == "": + preview_pos = lfEval("get(g:, 'Lf_PreviewPosition', 'top')") if lfEval("has('nvim')") == '1': - width = int(lfEval("get(g:, 'Lf_PreviewPopupWidth', 0)")) - if width == 0: - width = int(lfEval("&columns"))//2 - else: - width = min(width, int(lfEval("&columns"))) - maxheight = int(lfEval("&lines - (line('w$') - line('.')) - 3")) - maxheight -= int(self._getInstance().window.height) - int(lfEval("(line('w$') - line('w0') + 1)")) - relative = 'editor' - anchor = "SW" - row = maxheight - if isinstance(source, int): - buffer_len = len(vim.buffers[source]) - else: - try: - lfCmd("let content = readfile('%s', '', 4096)" % escQuote(source)) - except vim.error as e: - lfPrintError(e) - return - buffer_len = int(lfEval("len(content)")) - lfCmd("let scratch_buffer = nvim_create_buf(0, 1)") - lfCmd("call setbufline(scratch_buffer, 1, content)") - lfCmd("call nvim_buf_set_option(scratch_buffer, 'bufhidden', 'wipe')") - height = min(maxheight, buffer_len) - preview_pos = lfEval("get(g:, 'Lf_PreviewHorizontalPosition', 'right')") - if preview_pos.lower() == 'center': - col = (int(lfEval("&columns")) - width) // 2 - elif preview_pos.lower() == 'left': + if win_pos == 'bottom': + if preview_pos.lower() == 'topleft': + relative = 'editor' + anchor = "SW" + width = self._getInstance().window.width // 2 + height = self._getInstance().window.row + row = self._getInstance().window.row + col = 0 + elif preview_pos.lower() == 'topright': + relative = 'editor' + anchor = "SW" + width = self._getInstance().window.width // 2 + height = self._getInstance().window.row + row = self._getInstance().window.row + col = self._getInstance().window.width - width + elif preview_pos.lower() == 'right': + relative = 'editor' + anchor = "NW" + width = self._getInstance().window.width // 2 + height = self._getInstance().window.height + row = self._getInstance().window.row + col = self._getInstance().window.width - width + else: # preview_pos.lower() == 'top' + relative = 'editor' + anchor = "SW" + width = self._getInstance().window.width + height = self._getInstance().window.row + row = self._getInstance().window.row + col = 0 + elif win_pos == 'top': + if preview_pos.lower() == 'bottom': + relative = 'editor' + anchor = "NW" + width = self._getInstance().window.width + height = int(lfEval("&lines")) - self._getInstance().window.height - 2 + row = self._getInstance().window.height + col = 0 + else: # preview_pos.lower() == 'right' + relative = 'editor' + anchor = "NW" + width = self._getInstance().window.width // 2 + height = self._getInstance().window.height + row = self._getInstance().window.row + col = self._getInstance().window.width - width + elif win_pos == 'left': + relative = 'editor' + anchor = "NW" + width = int(lfEval("&columns")) - 1 - self._getInstance().window.width + height = self._getInstance().window.height + row = self._getInstance().window.row + col = self._getInstance().window.width + 1 + elif win_pos == 'right': + relative = 'editor' + anchor = "NW" + width = int(lfEval("&columns")) - 1 - self._getInstance().window.width + height = self._getInstance().window.height + row = self._getInstance().window.row col = 0 - elif preview_pos.lower() == 'right': - col = int(lfEval("&columns")) - width + elif win_pos == 'fullScreen': + relative = 'editor' + anchor = "NW" + width = self._getInstance().window.width // 2 + height = self._getInstance().window.height + row = self._getInstance().window.row + col = self._getInstance().window.width - width else: - relative = 'cursor' - row = 0 - col = 0 - - if maxheight < int(lfEval("&lines"))//2 - 2: + relative = 'editor' anchor = "NW" - if relative == 'cursor': - row = 1 - else: - row = maxheight + 1 - height = min(int(lfEval("&lines")) - maxheight - 3, buffer_len) + width = self._getInstance().window.width // 2 + height = self._getInstance().window.height + row = self._getInstance().window.row + col = self._getInstance().window.col + self._getInstance().window.width - width config = { "relative": relative, "anchor" : anchor, "height" : height, "width" : width, + "zindex" : 20480, "row" : row, - "col" : col + "col" : col, + "noautocmd": 1 } - if isinstance(source, int): - self._preview_winid = int(lfEval("nvim_open_win(%d, 0, %s)" % (source, str(config)))) - else: - self._preview_winid = int(lfEval("nvim_open_win(scratch_buffer, 0, %s)" % str(config))) - if jump_cmd: - cur_winid = lfEval("win_getid()") - lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) - lfCmd(jump_cmd) - lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) - if buffer_len >= line_nr > 0: - lfCmd("""call nvim_win_set_cursor(%d, [%d, 1])""" % (self._preview_winid, line_nr)) - lfCmd("call nvim_win_set_option(%d, 'number', v:true)" % self._preview_winid) - lfCmd("call nvim_win_set_option(%d, 'relativenumber', v:false)" % self._preview_winid) - lfCmd("call nvim_win_set_option(%d, 'cursorline', v:true)" % self._preview_winid) - lfCmd("call nvim_win_set_option(%d, 'foldmethod', 'manual')" % self._preview_winid) - if lfEval("exists('+cursorlineopt')") == '1': - lfCmd("call nvim_win_set_option(%d, 'cursorlineopt', 'both')" % self._preview_winid) - lfCmd("call nvim_win_set_option(%d, 'colorcolumn', '')" % self._preview_winid) - cur_winid = lfEval("win_getid()") - lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) - if not isinstance(source, int): - lfCmd("doautocmd filetypedetect BufNewFile %s" % source) - lfCmd("silent! %foldopen!") - lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) + self._updateOptions(preview_pos, show_borders, config) + self._createPreviewWindow(config, source, line_num, jump_cmd) else: - preview_pos = lfEval("get(g:, 'Lf_PreviewHorizontalPosition', 'right')") - if preview_pos.lower() == 'center': - col = 0 - elif preview_pos.lower() == 'left': + if win_pos == 'bottom': + if preview_pos.lower() == 'topleft': + maxwidth = self._getInstance().window.width // 2 + maxheight = self._getInstance().window.row - 1 + pos = "botleft" + line = self._getInstance().window.row + col = 1 + elif preview_pos.lower() == 'topright': + maxwidth = self._getInstance().window.width // 2 + maxheight = self._getInstance().window.row - 1 + pos = "botleft" + line = self._getInstance().window.row + col = self._getInstance().window.width - maxwidth + 1 + elif preview_pos.lower() == 'right': + maxwidth = self._getInstance().window.width // 2 + maxheight = self._getInstance().window.height - 1 + pos = "topleft" + line = self._getInstance().window.row + 1 + col = self._getInstance().window.width - maxwidth + 1 + else: # preview_pos.lower() == 'top' + maxwidth = self._getInstance().window.width + maxheight = self._getInstance().window.row - 1 + pos = "botleft" + line = self._getInstance().window.row + col = 1 + elif win_pos == 'top': + if preview_pos.lower() == 'bottom': + maxwidth = self._getInstance().window.width + maxheight = int(lfEval("&lines")) - self._getInstance().window.height - 3 + pos = "topleft" + line = self._getInstance().window.height + 1 + col = 1 + else: # preview_pos.lower() == 'right' + maxwidth = self._getInstance().window.width // 2 + maxheight = self._getInstance().window.height - 1 + pos = "topleft" + line = self._getInstance().window.row + 1 + col = self._getInstance().window.width - maxwidth + 1 + elif win_pos == 'left': + maxwidth = int(lfEval("&columns")) - 1 - self._getInstance().window.width + maxheight = self._getInstance().window.height - 1 + pos = "topleft" + line = self._getInstance().window.row + 1 + col = self._getInstance().window.width + 2 + elif win_pos == 'right': + maxwidth = int(lfEval("&columns")) - 1 - self._getInstance().window.width + maxheight = self._getInstance().window.height - 1 + pos = "topleft" + line = self._getInstance().window.row + 1 col = 1 - elif preview_pos.lower() == 'right': - col = int(lfEval("&columns"))//2 + 2 - else: - col = "cursor" - width = int(lfEval("get(g:, 'Lf_PreviewPopupWidth', 0)")) - if width == 0: - maxwidth = int(lfEval("&columns"))//2 - 1 + elif win_pos == 'fullScreen': + maxwidth = self._getInstance().window.width // 2 + maxheight = self._getInstance().window.height - 1 + pos = "topleft" + line = self._getInstance().window.row + 1 + col = self._getInstance().window.width - maxwidth + 1 else: - maxwidth = min(width, int(lfEval("&columns"))) - maxheight = int(lfEval("&lines - (line('w$') - line('.')) - 4")) - maxheight -= int(self._getInstance().window.height) - int(lfEval("(line('w$') - line('w0') + 1)")) + maxwidth = self._getInstance().window.width // 2 + maxheight = self._getInstance().window.height - 1 + pos = "topleft" + line = self._getInstance().window.row + 1 + col = self._getInstance().window.col + self._getInstance().window.width - maxwidth + 1 options = { - "title": title, + "title": " Preview ", "maxwidth": maxwidth, "minwidth": maxwidth, "maxheight": maxheight, "minheight": maxheight, - "zindex": 20481, - "pos": "botleft", - "line": "cursor-1", + "zindex": 20480, + "pos": pos, + "line": line, "col": col, - "padding": [0, 0, 0, 1], - "border": [1, 0, 0, 0], - "borderchars": [' '], + "scrollbar": 0, + "padding": [0, 0, 0, 0], "borderhighlight": ["Lf_hl_previewTitle"], - "filter": "leaderf#popupModePreviewFilter", + "filter": "leaderf#popupModePreviewFilter", } - if maxheight < int(lfEval("&lines"))//2 - 2: - maxheight = int(lfEval("&lines")) - maxheight - 5 - del options["title"] - options["border"] = [0, 0, 1, 0] - options["maxheight"] = maxheight - options["minheight"] = maxheight - if isinstance(source, int): - lfCmd("noautocmd silent! let winid = popup_create(%d, %s)" % (source, json.dumps(options))) - else: - try: - lfCmd("let content = readfile('%s', '', 4096)" % escQuote(source)) - except vim.error as e: - lfPrintError(e) - return - lfCmd("silent! let winid = popup_create(content, %s)" % json.dumps(options)) - lfCmd("call win_execute(winid, 'doautocmd filetypedetect BufNewFile %s')" % escQuote(source)) + self._updateOptions(preview_pos, show_borders, options) + self._createPreviewWindow(options, source, line_num, jump_cmd) - self._preview_winid = int(lfEval("winid")) - if self._current_mode == 'NORMAL': - lfCmd("call leaderf#ResetPopupOptions(%d, 'filter', function('leaderf#normalModePreviewFilter', [%d]))" - % (self._preview_winid, id(self))) - if jump_cmd: - lfCmd("""call win_execute(%d, '%s')""" % (self._preview_winid, escQuote(jump_cmd))) - elif line_nr > 0: - lfCmd("""call win_execute(%d, "exec 'norm! %dG'")""" % (self._preview_winid, line_nr)) - lfCmd("call win_execute(%d, 'setlocal cursorline number norelativenumber')" % self._preview_winid) - lfCmd("call win_execute(%d, 'setlocal foldmethod=manual')" % self._preview_winid) - if lfEval("exists('+cursorlineopt')") == '1': - lfCmd("call win_execute(%d, 'setlocal cursorlineopt=both')" % self._preview_winid) + return True + + def _updateOptions(self, preview_pos, show_borders, options): + if lfEval("has('nvim')") == '1': + if preview_pos.lower() == 'cursor': + options["anchor"] = "NW" + options["width"] = self._getInstance().window.width + row = int(lfEval("screenpos(%d, line('.'), 1)" % self._getInstance().windowId)['row']) + height = int(lfEval("&lines")) - 2 - row + + if height * 2 < int(lfEval("&lines")) - 2: + height = row - 1 + row = 0 + + options["height"] = height + options["row"] = row + options["col"] = self._getInstance().window.col + + if show_borders: + popup_borders = lfEval("g:Lf_PopupBorders") + borderchars = [ + [popup_borders[4], "Lf_hl_popupBorder"], + [popup_borders[0], "Lf_hl_popupBorder"], + [popup_borders[5], "Lf_hl_popupBorder"], + [popup_borders[1], "Lf_hl_popupBorder"], + [popup_borders[6], "Lf_hl_popupBorder"], + [popup_borders[2], "Lf_hl_popupBorder"], + [popup_borders[7], "Lf_hl_popupBorder"], + [popup_borders[3], "Lf_hl_popupBorder"] + ] + options["border"] = borderchars + options["height"] -= 2 + options["width"] -= 2 + if lfEval("has('nvim-0.9.0')") == '1': + options["title"] = " Preview " + options["title_pos"] = "center" + else: + if preview_pos.lower() == 'cursor': + options["maxwidth"] = self._getInstance().window.width + options["minwidth"] = self._getInstance().window.width + row = int(lfEval("screenpos(%d, line('.'), 1)" % self._getInstance().windowId)['row']) + maxheight = int(lfEval("&lines")) - 2 - row - 1 + + if maxheight * 2 < int(lfEval("&lines")) - 3: + maxheight = int(lfEval("&lines")) - maxheight - 5 - def _needPreview(self, preview): + options["maxheight"] = maxheight + options["minheight"] = maxheight + options["pos"] = "botleft" + options["line"] = "cursor-1" + options["col"] = self._getInstance().window.col + 1 + + if show_borders: + options["border"] = [] + options["borderchars"] = lfEval("g:Lf_PopupBorders") + options["maxwidth"] -= 2 + options["minwidth"] -= 2 + options["maxheight"] -= 1 + options["minheight"] -= 1 + options["borderhighlight"] = ["Lf_hl_popupBorder"] + + def _needPreview(self, preview, preview_in_popup): """ Args: preview: if True, always preview the result no matter what `g:Lf_PreviewResult` is. + + preview_in_popup: + whether preview in popup, if value is true, it means + (lfEval("get(g:, 'Lf_PreviewInPopup', 1)") == '1' or self._getInstance().getWinPos() in ('popup', 'floatwin')) """ - preview_dict = {k.lower(): v for k, v in lfEval("g:Lf_PreviewResult").items()} - category = self._getExplorer().getStlCategory() - if not preview and int(preview_dict.get(category.lower(), 0)) == 0: + if self._inHelpLines(): return False - if self._getInstance().isReverseOrder(): - if self._getInstance().window.cursor[0] > len(self._getInstance().buffer) - self._help_length: - self._orig_line = self._getInstance().currentLine - return False - elif self._getInstance().window.cursor[0] <= self._help_length: - self._orig_line = self._getInstance().currentLine - return False + if preview: + return True - if self._getInstance().empty() or (self._getInstance().getWinPos() != 'popup' and - vim.current.buffer != self._getInstance().buffer): + # non popup preview does not support automatic preview + if not preview_in_popup: return False - if self._ctrlp_pressed == True: + if "--auto-preview" in self._arguments: return True - line = self._getInstance().currentLine - if self._orig_line == line and (self._getInstance().buffer.options['modifiable'] - or self._getInstance().getWinPos() in ('popup', 'floatwin')): + if "--no-auto-preview" in self._arguments: return False - self._orig_line = self._getInstance().currentLine + preview_dict = {k.lower(): v for k, v in lfEval("g:Lf_PreviewResult").items()} + category = self._getExplorer().getStlCategory() + if int(preview_dict.get(category.lower(), 1)) == 0: + return False + + if self._getInstance().empty(): + return False return True @@ -940,30 +1409,69 @@ def _setAutochdir(self): lfCmd("set autochdir") def _toUpInPopup(self): - if self._preview_winid > 0 and int(lfEval("winbufnr(%d)" % self._preview_winid)) != -1: + if self.isPreviewWindowOpen(): + scroll_step_size = int(lfEval("get(g:, 'Lf_PreviewScrollStepSize', 1)")) if lfEval("has('nvim')") == '1': cur_winid = lfEval("win_getid()") lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) - lfCmd("norm! k") + lfCmd("norm! %dk" % (scroll_step_size)) lfCmd("redraw") lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) else: - lfCmd("call win_execute(%d, 'norm! k')" % (self._preview_winid)) + lfCmd("call win_execute(%d, 'norm! %dk')" % (self._preview_winid, scroll_step_size)) def _toDownInPopup(self): - if self._preview_winid > 0 and int(lfEval("winbufnr(%d)" % self._preview_winid)) != -1: + if self.isPreviewWindowOpen(): + scroll_step_size = int(lfEval("get(g:, 'Lf_PreviewScrollStepSize', 1)")) if lfEval("has('nvim')") == '1': cur_winid = lfEval("win_getid()") lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) - lfCmd("norm! j") + lfCmd("norm! %dj" % (scroll_step_size)) lfCmd("redraw") lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) else: - lfCmd("call win_execute(%d, 'norm! j')" % (self._preview_winid)) + lfCmd("call win_execute(%d, 'norm! %dj')" % (self._preview_winid, scroll_step_size)) + + def move(self, direction): + """ + direction is in {'j', 'k'} + """ + if (direction == 'j' and self._getInstance().window.cursor[0] == len(self._getInstance().buffer) + and self._circular_scroll): + lfCmd("noautocmd call win_execute(%d, 'norm! gg')" % (self._getInstance().getPopupWinId())) + elif direction == 'k' and self._getInstance().window.cursor[0] == 1 and self._circular_scroll: + lfCmd("noautocmd call win_execute(%d, 'norm! G')" % (self._getInstance().getPopupWinId())) + else: + lfCmd("noautocmd call win_execute(%d, 'norm! %s')" % (self._getInstance().getPopupWinId(), direction)) + + def moveAndPreview(self, direction): + """ + direction is in {'j', 'k', 'Down', 'Up', 'PageDown', 'PageUp'} + """ + count = int(lfEval("v:count")) + + if (direction in ("j", "Down") and self._getInstance().window.cursor[0] == len(self._getInstance().buffer) + and self._circular_scroll): + lfCmd('noautocmd exec "norm! gg"') + elif direction in ("k", "Up") and self._getInstance().window.cursor[0] == 1 and self._circular_scroll: + lfCmd('noautocmd exec "norm! G"') + else: + if len(direction) > 1: + lfCmd(r'noautocmd exec "norm! {}\<{}>"'.format(count, direction)) + else: + lfCmd('noautocmd exec "norm! {}{}"'.format(count, direction)) + + if self._getInstance().getWinPos() == 'floatwin': + self._cli._buildPopupPrompt() + + self._previewResult(False) def _toUp(self): if self._getInstance().getWinPos() == 'popup': - lfCmd("call win_execute(%d, 'norm! k')" % (self._getInstance().getPopupWinId())) + if self._getInstance().window.cursor[0] == 1 and self._circular_scroll: + lfCmd("noautocmd call win_execute(%d, 'norm! G')" % (self._getInstance().getPopupWinId())) + else: + lfCmd("noautocmd call win_execute(%d, 'norm! k')" % (self._getInstance().getPopupWinId())) self._getInstance().refreshPopupStatusline() return @@ -976,7 +1484,10 @@ def _toUp(self): and len(self._highlight_pos) < int(lfEval("g:Lf_NumberOfHighlight")): self._highlight_method() - lfCmd("norm! k") + if self._getInstance().window.cursor[0] == 1 and self._circular_scroll: + lfCmd("noautocmd norm! G") + else: + lfCmd("noautocmd norm! k") if adjust: lfCmd("norm! zt") @@ -987,22 +1498,28 @@ def _toUp(self): def _toDown(self): if self._getInstance().getWinPos() == 'popup': - lfCmd("call win_execute(%d, 'norm! j')" % (self._getInstance().getPopupWinId())) + if self._getInstance().window.cursor[0] == len(self._getInstance().buffer) and self._circular_scroll: + lfCmd("noautocmd call win_execute(%d, 'norm! gg')" % (self._getInstance().getPopupWinId())) + else: + lfCmd("noautocmd call win_execute(%d, 'norm! j')" % (self._getInstance().getPopupWinId())) self._getInstance().refreshPopupStatusline() return if not self._getInstance().isReverseOrder() \ - and self._getInstance().getCurrentPos()[0] == self._getInstance().window.height: + and self._getInstance().getCurrentPos()[0] == self._initial_count: self._setResultContent() - lfCmd("norm! j") + if self._getInstance().window.cursor[0] == len(self._getInstance().buffer) and self._circular_scroll: + lfCmd("noautocmd norm! gg") + else: + lfCmd("noautocmd norm! j") self._getInstance().setLineNumber() lfCmd("setlocal cursorline!") # these two help to redraw the statusline, lfCmd("setlocal cursorline!") # also fix a weird bug of vim def _pageUp(self): if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'exec "norm! \"')""" % (self._getInstance().getPopupWinId())) + lfCmd(r"""call win_execute(%d, 'exec "norm! \"')""" % (self._getInstance().getPopupWinId())) self._getInstance().refreshPopupStatusline() return @@ -1013,33 +1530,31 @@ def _pageUp(self): and len(self._highlight_pos) < int(lfEval("g:Lf_NumberOfHighlight")): self._highlight_method() - lfCmd('exec "norm! \"') + lfCmd(r'noautocmd exec "norm! \"') self._getInstance().setLineNumber() def _pageDown(self): if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'exec "norm! \"')""" % (self._getInstance().getPopupWinId())) + lfCmd(r"""call win_execute(%d, 'exec "norm! \"')""" % (self._getInstance().getPopupWinId())) self._getInstance().refreshPopupStatusline() return if not self._getInstance().isReverseOrder(): self._setResultContent() - lfCmd('exec "norm! \"') + lfCmd(r'noautocmd exec "norm! \"') self._getInstance().setLineNumber() def _leftClick(self): if self._getInstance().getWinPos() == 'popup': - if int(lfEval("has('patch-8.1.2266')")) == 1: - if self._getInstance().getPopupWinId() == int(lfEval("v:mouse_winid")): - lfCmd("""call win_execute(%d, "exec v:mouse_lnum")""" - % (self._getInstance().getPopupWinId())) - lfCmd("""call win_execute(%d, "exec 'norm!'.v:mouse_col.'|'")""" - % (self._getInstance().getPopupWinId())) + if int(lfEval("has('patch-8.1.2292')")) == 1: + lfCmd("""call win_execute(v:mouse_winid, "exec v:mouse_lnum")""") + lfCmd("""call win_execute(v:mouse_winid, "exec 'norm!'.v:mouse_col.'|'")""") exit_loop = False elif self._getInstance().window.number == int(lfEval("v:mouse_win")): + lfCmd("noautocmd silent! call win_gotoid(v:mouse_winid)") lfCmd("exec v:mouse_lnum") lfCmd("exec 'norm!'.v:mouse_col.'|'") self._getInstance().setLineNumber() @@ -1047,20 +1562,55 @@ def _leftClick(self): exit_loop = False elif self._preview_winid == int(lfEval("v:mouse_winid")): if lfEval("has('nvim')") == '1': - lfCmd("call win_gotoid(%d)" % self._preview_winid) + lfCmd("noautocmd silent! call win_gotoid(v:mouse_winid)") lfCmd("exec v:mouse_lnum") lfCmd("exec 'norm!'.v:mouse_col.'|'") - self._current_mode = 'NORMAL' - lfCmd("call leaderf#colorscheme#popup#hiMode('%s', '%s')" - % (self._getExplorer().getStlCategory(), self._current_mode)) - self._getInstance().setPopupStl(self._current_mode) - exit_loop = True + exit_loop = False else: self.quit() exit_loop = True return exit_loop + def _scrollUp(self): + if lfEval('exists("*getmousepos")') == '1': + lfCmd(r"""call win_execute(getmousepos().winid, "norm! 3\")""") + else: + self._toUp() + + def _scrollDown(self): + if lfEval('exists("*getmousepos")') == '1': + lfCmd(r"""call win_execute(getmousepos().winid, "norm! 3\")""") + else: + self._toDown() + + def _quickSelect(self): + selection = int(self._cli.last_char) + if selection == 0: + selection = 10 + + line_num = self._help_length + selection + if line_num > len(self._getInstance().buffer): + return False + + if self._getInstance().isReverseOrder(): + pass + else: + if self._getInstance().getWinPos() == 'popup': + lfCmd("""call win_execute(%d, "exec %d")""" % (self._getInstance().getPopupWinId(), line_num)) + else: + lfCmd("exec %d" % line_num) + + action = lfEval("get(g:, 'Lf_QuickSelectAction', 'c')") + if action != '' and action in "hvtc": + if action == 'c': + action = '' + self.accept(action) + return True + else: + self._previewResult(False) + return False + def _search(self, content, is_continue=False, step=0): if not is_continue: self.clearSelections() @@ -1287,7 +1837,7 @@ def _fuzzySearch(self, content, is_continue, step): pattern=pattern, is_name_only=True, sort_results=do_sort) getHighlights = partial(fuzzyEngine.getHighlights, engine=self._fuzzy_engine, pattern=pattern, is_name_only=True) - highlight_method = partial(self._highlight, True, getHighlights, True) + highlight_method = partial(self._highlight, False, getHighlights, True) elif is_fuzzyMatch_C and isAscii(self._cli.pattern[0]): use_fuzzy_match_c = True pattern = fuzzyMatchC.initPattern(self._cli.pattern[0]) @@ -1379,7 +1929,7 @@ def _fuzzySearch(self, content, is_continue, step): is_name_only=True, sort_results=do_sort) elif self._getExplorer().getStlCategory() == "Rg": return_index = False - if "--match-path" in self._arguments: + if self._cli.isFullPath or "--match-path" in self._arguments: filter_method = partial(fuzzyEngine.fuzzyMatch, engine=self._fuzzy_engine, pattern=pattern, is_name_only=True, sort_results=do_sort) else: @@ -1410,6 +1960,12 @@ def _fuzzySearch(self, content, is_continue, step): filter_method = partial(fuzzyEngine.fuzzyMatchPart, engine=self._fuzzy_engine, pattern=pattern, category=fuzzyEngine.Category_Line, param=fuzzyEngine.createParameter(1), is_name_only=True, sort_results=do_sort) + elif self._getExplorer().getStlCategory() == "Git_diff": + return_index = False + mode = 0 if self._cli.isFullPath else 1 + filter_method = partial(fuzzyEngine.fuzzyMatchPart, engine=self._fuzzy_engine, + pattern=pattern, category=fuzzyEngine.Category_GitDiff, + param=fuzzyEngine.createParameter(mode), is_name_only=False, sort_results=do_sort) elif self._getExplorer().getStlCategory() in ["Self", "Buffer", "Mru", "BufTag", "Function", "History", "Cmd_History", "Search_History", "Filetype", "Command", "Window", "QuickFix", "LocList"]: @@ -1478,7 +2034,7 @@ def _fuzzySearch(self, content, is_continue, step): if return_index == True: step = 30000 * cpu_count else: - step = 60000 * cpu_count + step = 50000 * cpu_count _, self._result_content = self._filter(step, filter_method, content, is_continue, True, return_index) else: @@ -1505,9 +2061,6 @@ def _fuzzySearch(self, content, is_continue, step): self._highlight_method = highlight_method self._highlight_method() - if len(self._cli.pattern) > 1 and not is_continue: - lfCmd("redraw") - def _guessFilter(self, filename, suffix, dirname, icon, iterable): """ return a list, each item is a pair (weight, line) @@ -1571,6 +2124,7 @@ def _clearHighlightsPos(self): self._highlight_pos_list = [] self._highlight_refine_pos = [] + @windo def _resetHighlights(self): self._clearHighlights() @@ -1794,6 +2348,7 @@ def toggleHelp(self): self.clearSelections() self._resetHighlights() + @ignoreEvent('BufUnload') def _accept(self, file, mode, *args, **kwargs): if file: if self._getExplorer().getStlCategory() != "Jumps": @@ -1816,13 +2371,7 @@ def _accept(self, file, mode, *args, **kwargs): self._cursorline_dict.clear() self._issue_422_set_option() if mode == 't' and len(vim.tabpages) > tabpage_count: - 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") + tabmove() def accept(self, mode=''): if self._getInstance().isReverseOrder(): @@ -1865,6 +2414,8 @@ def accept(self, mode=''): self._getInstance().cursorRow = self._getInstance().window.cursor[0] self._getInstance().helpLength = self._help_length try: + if self._preview_winid: + self._closePreviewPopup() vim.current.tabpage, vim.current.window, vim.current.buffer = self._getInstance().getOriginalPos() except vim.error: # error if original buffer is an No Name buffer pass @@ -1872,7 +2423,7 @@ def accept(self, mode=''): self._getInstance().exitBuffer() # https://github.com/Yggdroot/LeaderF/issues/257 - win_local_cwd = lfEval("getcwd(winnr())") + win_local_cwd = lfEval("getcwd()") if cwd != win_local_cwd: chdir(cwd) @@ -1894,7 +2445,7 @@ def accept(self, mode=''): need_exit = True else: file = self._getInstance().currentLine - line_nr = self._getInstance().window.cursor[0] + line_num = self._getInstance().window.cursor[0] need_exit = self._needExit(file, self._arguments) if need_exit: if "--stayOpen" in self._arguments: @@ -1902,6 +2453,8 @@ def accept(self, mode=''): self._getInstance().cursorRow = self._getInstance().window.cursor[0] self._getInstance().helpLength = self._help_length try: + if self._preview_winid: + self._closePreviewPopup() vim.current.tabpage, vim.current.window, vim.current.buffer = self._getInstance().getOriginalPos() except vim.error: # error if original buffer is an No Name buffer pass @@ -1909,12 +2462,12 @@ def accept(self, mode=''): self._getInstance().exitBuffer() # https://github.com/Yggdroot/LeaderF/issues/257 - win_local_cwd = lfEval("getcwd(winnr())") + win_local_cwd = lfEval("getcwd()") if cwd != win_local_cwd: chdir(cwd) orig_cwd = lfGetCwd() - self._accept(file, mode, self._getInstance().buffer, line_nr) # for bufTag + self._accept(file, mode, self._getInstance().buffer, line_num) # for bufTag if lfGetCwd() != orig_cwd: dir_changed_by_autocmd = True else: @@ -1946,20 +2499,20 @@ def _jumpNext(self): instance.window.options["cursorline"] = True instance.gotoOriginalWindow() - line_nr = self._getInstance().window.cursor[0] - self._accept(instance.buffer[instance.window.cursor[0] - 1], "", self._getInstance().buffer, line_nr) + line_num = self._getInstance().window.cursor[0] + self._accept(instance.buffer[instance.window.cursor[0] - 1], "", self._getInstance().buffer, line_num) else: if instance.cursorRow > len(instance.buffer) - instance.helpLength: instance.cursorRow = len(instance.buffer) - instance.helpLength - line_nr = instance.cursorRow + line_num = instance.cursorRow elif instance.cursorRow == 1: # at the last line - line_nr = instance.cursorRow + line_num = instance.cursorRow instance.cursorRow = len(instance.buffer) - instance.helpLength else: - line_nr = instance.cursorRow + line_num = instance.cursorRow instance.cursorRow -= 1 - self._accept(instance.buffer[instance.cursorRow - 1], "", self._getInstance().buffer, line_nr) + self._accept(instance.buffer[instance.cursorRow - 1], "", self._getInstance().buffer, line_num) lfCmd("echohl WarningMsg | redraw | echo ' (%d of %d)' | echohl NONE" % (len(instance.buffer) - instance.cursorRow - instance.helpLength + 1, len(instance.buffer) - instance.helpLength)) @@ -1974,20 +2527,20 @@ def _jumpNext(self): instance.window.options["cursorline"] = True instance.gotoOriginalWindow() - line_nr = self._getInstance().window.cursor[0] - self._accept(instance.buffer[instance.window.cursor[0] - 1], "", self._getInstance().buffer, line_nr) + line_num = self._getInstance().window.cursor[0] + self._accept(instance.buffer[instance.window.cursor[0] - 1], "", self._getInstance().buffer, line_num) else: if instance.cursorRow <= instance.helpLength: instance.cursorRow = instance.helpLength + 1 - line_nr = instance.cursorRow + line_num = instance.cursorRow elif instance.cursorRow == len(instance.buffer): # at the last line - line_nr = instance.cursorRow + line_num = instance.cursorRow instance.cursorRow = instance.helpLength + 1 else: - line_nr = instance.cursorRow + line_num = instance.cursorRow instance.cursorRow += 1 - self._accept(instance.buffer[instance.cursorRow - 1], "", self._getInstance().buffer, line_nr) + self._accept(instance.buffer[instance.cursorRow - 1], "", self._getInstance().buffer, line_num) lfCmd("echohl WarningMsg | redraw | echo ' (%d of %d)' | echohl NONE" % \ (instance.cursorRow - instance.helpLength, len(instance.buffer) - instance.helpLength)) @@ -2007,17 +2560,17 @@ def _jumpPrevious(self): instance.window.options["cursorline"] = True instance.gotoOriginalWindow() - line_nr = self._getInstance().window.cursor[0] - self._accept(instance.buffer[instance.window.cursor[0] - 1], "", self._getInstance().buffer, line_nr) + line_num = self._getInstance().window.cursor[0] + self._accept(instance.buffer[instance.window.cursor[0] - 1], "", self._getInstance().buffer, line_num) else: if instance.cursorRow >= len(instance.buffer) - instance.helpLength: instance.cursorRow = 1 - line_nr = instance.cursorRow + line_num = instance.cursorRow else: - line_nr = instance.cursorRow + line_num = instance.cursorRow instance.cursorRow += 1 - self._accept(instance.buffer[instance.cursorRow - 1], "", self._getInstance().buffer, line_nr) + self._accept(instance.buffer[instance.cursorRow - 1], "", self._getInstance().buffer, line_num) lfCmd("echohl WarningMsg | redraw | echo ' (%d of %d)' | echohl NONE" % (len(instance.buffer) - instance.cursorRow - instance.helpLength + 1, len(instance.buffer) - instance.helpLength)) @@ -2030,17 +2583,17 @@ def _jumpPrevious(self): instance.window.options["cursorline"] = True instance.gotoOriginalWindow() - line_nr = self._getInstance().window.cursor[0] - self._accept(instance.buffer[instance.window.cursor[0] - 1], "", self._getInstance().buffer, line_nr) + line_num = self._getInstance().window.cursor[0] + self._accept(instance.buffer[instance.window.cursor[0] - 1], "", self._getInstance().buffer, line_num) else: if instance.cursorRow <= instance.helpLength + 1: instance.cursorRow = len(instance.buffer) - line_nr = instance.cursorRow + line_num = instance.cursorRow else: - line_nr = instance.cursorRow + line_num = instance.cursorRow instance.cursorRow -= 1 - self._accept(instance.buffer[instance.cursorRow - 1], "", self._getInstance().buffer, line_nr) + self._accept(instance.buffer[instance.cursorRow - 1], "", self._getInstance().buffer, line_num) lfCmd("echohl WarningMsg | redraw | echo ' (%d of %d)' | echohl NONE" % \ (instance.cursorRow - instance.helpLength, len(instance.buffer) - instance.helpLength)) @@ -2086,13 +2639,13 @@ def addSelections(self): lfCmd("exec v:mouse_lnum") lfCmd("exec 'norm!'.v:mouse_col.'|'") - line_nr = self._getInstance().window.cursor[0] + line_num = self._getInstance().window.cursor[0] if self._getInstance().isReverseOrder(): - if line_nr > len(self._getInstance().buffer) - self._help_length: + if line_num > len(self._getInstance().buffer) - self._help_length: lfCmd("norm! k") return else: - if line_nr <= self._help_length: + if line_num <= self._help_length: if self._getInstance().getWinPos() == 'popup': lfCmd("call win_execute({}, 'norm! j')".format(self._getInstance().getPopupWinId())) else: @@ -2103,20 +2656,20 @@ def addSelections(self): return - if line_nr in self._selections: + if line_num in self._selections: if self._getInstance().getWinPos() == 'popup': - lfCmd("call matchdelete(%d, %d)" % (self._selections[line_nr], self._getInstance().getPopupWinId())) + lfCmd("call matchdelete(%d, %d)" % (self._selections[line_num], self._getInstance().getPopupWinId())) else: - lfCmd("call matchdelete(%d)" % self._selections[line_nr]) - del self._selections[line_nr] + lfCmd("call matchdelete(%d)" % self._selections[line_num]) + del self._selections[line_num] else: if self._getInstance().getWinPos() == 'popup': lfCmd("""call win_execute(%d, "let matchid = matchadd('Lf_hl_selection', '\\\\%%%dl.')")""" - % (self._getInstance().getPopupWinId(), line_nr)) + % (self._getInstance().getPopupWinId(), line_num)) id = int(lfEval("matchid")) else: - id = int(lfEval("matchadd('Lf_hl_selection', '\%%%dl.')" % line_nr)) - self._selections[line_nr] = id + id = int(lfEval(r"matchadd('Lf_hl_selection', '\%%%dl.')" % line_num)) + self._selections[line_num] = id def selectMulti(self): orig_line = self._getInstance().window.cursor[0] @@ -2129,7 +2682,7 @@ def selectMulti(self): 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))) + id = int(lfEval(r"matchadd('Lf_hl_selection', '\%%%dl.')" % (i))) self._selections[i] = id def selectAll(self): @@ -2145,7 +2698,7 @@ def selectAll(self): % (self._getInstance().getPopupWinId(), i+1)) id = int(lfEval("matchid")) else: - id = int(lfEval("matchadd('Lf_hl_selection', '\%%%dl.')" % (i+1))) + id = int(lfEval(r"matchadd('Lf_hl_selection', '\%%%dl.')" % (i+1))) self._selections[i+1] = id def _gotoFirstLine(self): @@ -2157,19 +2710,30 @@ def _gotoFirstLine(self): def _readFinished(self): pass + def _previewFirstLine(self): + time.sleep(0.002) + if len(self._content) > 0: + first_line = self._content[0] + self._getInstance().setBuffer([first_line]) + self._previewResult(False) + self._getInstance().clearBuffer() + def startExplorer(self, win_pos, *args, **kwargs): arguments_dict = kwargs.get("arguments", {}) if "--recall" in arguments_dict: - self._arguments["--recall"] = arguments_dict["--recall"] + self._arguments.update(arguments_dict) elif "--previous" in arguments_dict: self._arguments["--previous"] = arguments_dict["--previous"] elif "--next" in arguments_dict: self._arguments["--next"] = arguments_dict["--next"] else: self.setArguments(arguments_dict) + + self._getInstance().setArguments(self._arguments) + self._cli.setArguments(self._arguments) self._cli.setNameOnlyFeature(self._getExplorer().supportsNameOnly()) self._cli.setRefineFeature(self._supportsRefine()) - self._orig_line = '' + self._orig_line = None if self._getExplorer().getStlCategory() in ["Gtags"]: if "--update" in self._arguments or "--remove" in self._arguments: @@ -2188,7 +2752,6 @@ def startExplorer(self, win_pos, *args, **kwargs): self._cleanup() # lfCmd("echohl WarningMsg | redraw | echo ' searching ...' | echohl NONE") - self._getInstance().setArguments(self._arguments) empty_query = self._empty_query and self._getExplorer().getStlCategory() in ["File"] remember_last_status = "--recall" in self._arguments \ or lfEval("g:Lf_RememberLastSearch") == '1' and self._cli.pattern @@ -2199,14 +2762,11 @@ def startExplorer(self, win_pos, *args, **kwargs): else: content = self._getExplorer().getContent(*args, **kwargs) self._getInstance().setCwd(lfGetCwd()) - if self._getExplorer().getStlCategory() in ["Gtags"] and "--auto-jump" in self._arguments \ - and isinstance(content, list) and len(content) == 1: - mode = self._arguments["--auto-jump"][0] if len(self._arguments["--auto-jump"]) else "" - self._accept(content[0], mode) + if self.autoJump(content) == True: return self._index = 0 - pattern = kwargs.get("pattern", "") or arguments_dict.get("--input", [""])[0] + pattern = arguments_dict.get("--input", [""])[0] if len(pattern) > 1 and (pattern[0] == '"' and pattern[-1] == '"' or pattern[0] == "'" and pattern[-1] == "'"): pattern = pattern[1:-1] @@ -2214,6 +2774,9 @@ def startExplorer(self, win_pos, *args, **kwargs): self._result_content = [] self._cb_content = [] + if content is None: + return + if not content: lfCmd("echohl Error | redraw | echo ' No content!' | echohl NONE") return @@ -2243,7 +2806,7 @@ def startExplorer(self, win_pos, *args, **kwargs): self._bang_count = 0 self._getInstance().buffer.vars['Lf_category'] = self._getExplorer().getStlCategory() - + self._read_content_exception = None if isinstance(content, list): self._is_content_list = True @@ -2259,6 +2822,9 @@ def startExplorer(self, win_pos, *args, **kwargs): self._getInstance().setStlResultsCount(len(self._content)) if not empty_query: self._getInstance().setBuffer(self._content[:self._initial_count]) + self._previewResult(False) + else: + self._previewResult(False) if lfEval("has('nvim')") == '1': lfCmd("redrawstatus") @@ -2282,7 +2848,7 @@ def startExplorer(self, win_pos, *args, **kwargs): if not remember_last_status and not self._cli.pattern and empty_query: self._gotoFirstLine() self._guessSearch(self._content) - if self._result_content: # self._result_content is [] only if + if self._result_content: # self._result_content is [] only if # self._cur_buffer.name == '' or self._cur_buffer.options["buftype"] not in [b'', '']: self._getInstance().appendBuffer(self._result_content[self._initial_count:]) else: @@ -2300,6 +2866,7 @@ def startExplorer(self, win_pos, *args, **kwargs): self._callback = self._workInIdle if lfEval("get(g:, 'Lf_NoAsync', 0)") == '1': self._content = self._getInstance().initBuffer(content, self._getUnit(), self._getExplorer().setContent) + self._previewResult(False) self._read_finished = 1 self._offset_in_content = 0 else: @@ -2310,7 +2877,6 @@ def startExplorer(self, win_pos, *args, **kwargs): self._getInstance().setBuffer(self._content, need_copy=False) self._createHelpHint() else: - self._getInstance().clearBuffer() self._content = [] self._offset_in_content = 0 else: @@ -2323,6 +2889,8 @@ def startExplorer(self, win_pos, *args, **kwargs): self._reader_thread = threading.Thread(target=self._readContent, args=(content,)) self._reader_thread.daemon = True self._reader_thread.start() + if not self._getInstance().isReverseOrder(): + self._previewFirstLine() if not kwargs.get('bang', 0): self.input() @@ -2336,6 +2904,7 @@ def startExplorer(self, win_pos, *args, **kwargs): self._callback = partial(self._workInIdle, content) if lfEval("get(g:, 'Lf_NoAsync', 0)") == '1': self._content = self._getInstance().initBuffer(content, self._getUnit(), self._getExplorer().setContent) + self._previewResult(False) self._read_finished = 1 self._offset_in_content = 0 else: @@ -2378,15 +2947,10 @@ def _workInIdle(self, content=None, bang=False): self._timer_id = None lfPrintError(self._read_content_exception[1]) - return + return None else: raise self._read_content_exception[1] - if bang == False and self._preview_open == False and lfEval("get(g:, 'Lf_PreviewInPopup', 0)") == '1' \ - and not self._getInstance().empty(): - self._previewResult(False) - self._preview_open = True - if self._is_content_list: if self._cli.pattern and (self._index < len(self._content) or len(self._cb_content) > 0): if self._fuzzy_engine: @@ -2396,7 +2960,9 @@ def _workInIdle(self, content=None, bang=False): else: step = 2000 self._search(self._content, True, step) - return + return None + else: + return 100 if content: i = -1 @@ -2418,7 +2984,7 @@ def _workInIdle(self, content=None, bang=False): elif self._empty_query and self._getExplorer().getStlCategory() in ["File"]: self._guessSearch(self._content) if bang: - if self._result_content: # self._result_content is [] only if + if self._result_content: # self._result_content is [] only if # self._cur_buffer.name == '' or self._cur_buffer.options["buftype"] != b'': self._getInstance().appendBuffer(self._result_content[self._initial_count:]) else: @@ -2455,6 +3021,9 @@ def _workInIdle(self, content=None, bang=False): self._getInstance().setStlResultsCount(len(self._content)) + if not self.isPreviewWindowOpen(): + self._previewResult(False) + if self._getInstance().getWinPos() not in ('popup', 'floatwin'): lfCmd("redrawstatus") @@ -2470,6 +3039,10 @@ def _workInIdle(self, content=None, bang=False): if bang: self._getInstance().appendBuffer(self._result_content[self._initial_count:]) + else: + return 100 + else: + return 100 else: cur_len = len(self._content) if time.time() - self._start_time > 0.1: @@ -2514,9 +3087,14 @@ def _workInIdle(self, content=None, bang=False): elif len(self._getInstance().buffer) < min(cur_len, self._initial_count): self._getInstance().setBuffer(self._content[:self._initial_count]) + if not self.isPreviewWindowOpen(): + self._previewResult(False) + + return None + + @modifiableController def input(self): - self._preview_open = False self._current_mode = 'INPUT' self._getInstance().hideMimicCursor() if self._getInstance().getWinPos() in ('popup', 'floatwin'): @@ -2537,12 +3115,14 @@ def input(self): self._hideHelp() self._resetHighlights() - if self._cli.pattern: # --input xxx or from normal mode to input mode + # --input xxx or from normal mode to input mode + if self._cli.pattern and "--live" not in self._arguments: if self._index == 0: # --input xxx self._search(self._content) elif self._empty_query and self._getExplorer().getStlCategory() in ["File"] \ and "--recall" not in self._arguments: self._guessSearch(self._content) + self._previewResult(False) for cmd in self._cli.input(self._callback): cur_len = len(self._content) @@ -2569,7 +3149,7 @@ def input(self): else: self._gotoFirstLine() self._index = 0 # search from beginning - if self._cli.pattern: + if self._cli.pattern and "--live" not in self._arguments: self._search(cur_content) elif equal(cmd, ''): self._toUp() @@ -2613,7 +3193,7 @@ def input(self): elif equal(cmd, ''): if self.accept('t') is None: break - elif equal(cmd, ''): + elif equal(cmd, r''): actions = ['', 'h', 'v', 't', 'dr'] action_count = len(actions) selection = int( vim.eval( @@ -2668,9 +3248,7 @@ def input(self): elif equal(cmd, ''): self.clearSelections() elif equal(cmd, ''): - self._ctrlp_pressed = True self._previewResult(True) - self._ctrlp_pressed = False elif equal(cmd, ''): self._pageUp() self._previewResult(False) @@ -2681,6 +3259,15 @@ def input(self): self._toUpInPopup() elif equal(cmd, ''): self._toDownInPopup() + elif equal(cmd, ''): + self._scrollUp() + self._previewResult(False) + elif equal(cmd, ''): + self._scrollDown() + self._previewResult(False) + elif equal(cmd, ''): + if self._quickSelect(): + break else: if self._cmdExtension(cmd): break diff --git a/autoload/leaderf/python/leaderf/mru.py b/autoload/leaderf/python/leaderf/mru.py index aa13e21d..0fb315c6 100644 --- a/autoload/leaderf/python/leaderf/mru.py +++ b/autoload/leaderf/python/leaderf/mru.py @@ -15,10 +15,11 @@ class Mru(object): def __init__(self): self._cache_dir = os.path.join(lfEval("g:Lf_CacheDirectory"), - '.LfCache', + 'LeaderF', 'python' + lfEval("g:Lf_PythonVersion"), 'mru') - self._cache_file = os.path.join(self._cache_dir, 'mruCache') + self._cache_file = os.path.join(self._cache_dir, 'frecency') + self._old_cache_file = os.path.join(self._cache_dir, 'mruCache') self._initCache() self._mru_bufnrs = { b.number: 0 for b in vim.buffers } self._timestamp = 0 @@ -33,6 +34,9 @@ def _initCache(self): def getCacheFileName(self): return self._cache_file + def getOldCacheFileName(self): + return self._old_cache_file + def normalize(self, name): if '~' in name: name = os.path.expanduser(name) @@ -48,37 +52,40 @@ def normalize(self, name): name = name[:11].lower() + name[11:] return name - def saveToCache(self, buf_name_list): - buf_names = [] - for name in buf_name_list: - name = self.normalize(name) + def filename(self, line): + return line.rstrip().split(None, 2)[2] + + def saveToCache(self, data_list): + frecency_list = [] + for item in data_list: + name = self.normalize(self.filename(item)) if True in (fnmatch.fnmatch(name, i) for i in lfEval("g:Lf_MruFileExclude")): continue - buf_names.append(name) + frecency_list.append(item) - if not buf_names: + if not frecency_list: return - with lfOpen(self._cache_file, 'r+', errors='ignore') as f: + with lfOpen(self._cache_file, 'r+', errors='ignore', encoding='utf-8') as f: lines = f.readlines() - for name in buf_names: + for item in frecency_list: nocase = False - compare = name + compare = self.filename(item) if sys.platform[:3] == 'win' or sys.platform in ('cygwin', 'msys'): nocase = True - compare = name.lower() + compare = compare.lower() for i, line in enumerate(lines): - text = line.rstrip() + text = self.filename(line) if (compare == text) or (nocase and compare == text.lower()): - del lines[i] + time1, rank1, filename = item.split(None, 2) + time2, rank2, _ = lines[i].split(None, 2) + lines[i] = "{} {} {}\n".format(time1, int(rank1) + int(rank2), filename) break + else: + lines.append(item + '\n') - lines = [name + '\n' for name in buf_names] + lines - max_files = int(lfEval("g:Lf_MruMaxFiles")) - if len(lines) > max_files: - del lines[max_files:] f.seek(0) f.truncate(0) f.writelines(lines) diff --git a/autoload/leaderf/python/leaderf/mruExpl.py b/autoload/leaderf/python/leaderf/mruExpl.py index 36dc7050..7202bfe6 100644 --- a/autoload/leaderf/python/leaderf/mruExpl.py +++ b/autoload/leaderf/python/leaderf/mruExpl.py @@ -4,7 +4,10 @@ import vim import os import os.path +import time +import operator from fnmatch import fnmatch +from functools import partial from .utils import * from .explorer import * from .manager import * @@ -26,22 +29,74 @@ class MruExplorer(Explorer): def __init__(self): self._prefix_length = 0 self._max_bufname_len = 0 + self._root_markers = lfEval("g:Lf_RootMarkers") + + def getFrecency(self, current_time, item): + """ + item is [time, rank, filename] + """ + rank = int(item[1]) + delta_time = int(current_time) - int(item[0]) + frecency = 0 + if delta_time < 3600: + frecency = rank * 4 + elif delta_time < 86400: + frecency = rank * 2 + elif delta_time < 604800: + frecency = rank * 0.5 + else: + frecency = rank * 0.25 + + return frecency def getContent(self, *args, **kwargs): mru.saveToCache(lfEval("readfile(lfMru#CacheFileName())")) lfCmd("call writefile([], lfMru#CacheFileName())") - with lfOpen(mru.getCacheFileName(), 'r+', errors='ignore') as f: - lines = f.readlines() - lines = [name for name in lines if os.path.exists(lfDecode(name.rstrip()))] + with lfOpen(mru.getCacheFileName(), 'r+', errors='ignore', encoding='utf8') as f: + data_list = [] + for line in f.readlines(): + data = line.split(None, 2) + if os.path.exists(lfDecode(data[2].rstrip())): + data[0] = int(data[0]) + data_list.append(data) + + if len(data_list) == 0: + # import old data + try: + with lfOpen(mru.getOldCacheFileName(), 'r+', errors='ignore', encoding='utf8') as old_f: + current_time = time.time() + data_list = [[int(current_time), 1, filename] for filename in old_f.readlines() + if os.path.exists(lfDecode(filename.rstrip())) + ] + except FileNotFoundError: + pass + + arguments_dict = kwargs.get("arguments", {}) + if "--frecency" in arguments_dict or lfEval("get(g:, 'Lf_MruEnableFrecency', 0)") == '1': + data_list.sort(key=partial(self.getFrecency, time.time()), reverse=True) + else: + data_list.sort(key=operator.itemgetter(0), reverse=True) + + max_files = int(lfEval("g:Lf_MruMaxFiles")) + if len(data_list) > max_files: + del data_list[max_files:] + f.seek(0) f.truncate(0) - f.writelines(lines) + f.writelines(["{} {} {}".format(data[0], data[1], data[2]) for data in data_list]) - if "--cwd" in kwargs.get("arguments", {}): + lines = [data[2].rstrip() for data in data_list] + + if "--cwd" in arguments_dict: lines = [name for name in lines if lfDecode(name).startswith(lfGetCwd())] + elif "--project" in arguments_dict: + project_root = lfGetCwd() + ancestor = nearestAncestor(self._root_markers, project_root) + if ancestor != "": + project_root = ancestor + lines = [name for name in lines if lfDecode(name).startswith(os.path.join(project_root, ''))] - lines = [line.rstrip() for line in lines] # remove the '\n' wildignore = lfEval("g:Lf_MruWildIgnore") lines = [name for name in lines if True not in (fnmatch(name, j) for j in wildignore.get('file', [])) and True not in (fnmatch(name, "*/" + j + "/*") for j in wildignore.get('dir', []))] @@ -58,21 +113,23 @@ def getContent(self, *args, **kwargs): self.show_icon = True self._prefix_length = webDevIconsStrLen() - if "--no-split-path" in kwargs.get("arguments", {}): - if lfEval("get(g:, 'Lf_ShowDevIcons', 1)") == "0": - return lines - - for line in lines: - return [ + show_absolute = "--absolute-path" in arguments_dict + if "--no-split-path" in arguments_dict: + if lfEval("g:Lf_ShowRelativePath") == '1' and show_absolute == False: + lines = [lfRelpath(line) for line in lines] + if lfEval("get(g:, 'Lf_ShowDevIcons', 1)") == "1": + lines = [ webDevIconsGetFileTypeSymbol(getBasename(line)) + line for line in lines ] + return lines self._max_bufname_len = max(int(lfEval("strdisplaywidth('%s')" % escQuote(getBasename(line)))) for line in lines) + self._max_bufname_len = min(25, self._max_bufname_len) for i, line in enumerate(lines): - if lfEval("g:Lf_ShowRelativePath") == '1': + if lfEval("g:Lf_ShowRelativePath") == '1' and show_absolute == False: line = lfRelpath(line) basename = getBasename(line) dirname = getDirname(line) @@ -101,7 +158,7 @@ def supportsNameOnly(self): return True def delFromCache(self, name): - with lfOpen(mru.getCacheFileName(), 'r+', errors='ignore') as f: + with lfOpen(mru.getCacheFileName(), 'r+', errors='ignore', encoding='utf8') as f: lines = f.readlines() lines.remove(lfEncode(os.path.abspath(lfDecode(name))) + '\n') f.seek(0) @@ -182,8 +239,9 @@ def _acceptSelection(self, *args, **kwargs): lfCmd("setlocal bufhidden=wipe") lfCmd("hide edit %s" % escSpecial(file)) - except vim.error: # E37 - lfPrintTraceback() + except vim.error as e: # E37 + if 'E325' not in str(e).split(':'): + lfPrintTraceback() def _getDigest(self, line, mode): """ @@ -266,12 +324,12 @@ def _afterEnter(self): if "--no-split-path" not in self._arguments: if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufDirname'', '' \zs".*"$'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_bufDirname'', '' \zs".*"$'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) else: - id = int(lfEval('''matchadd('Lf_hl_bufDirname', ' \zs".*"$')''')) + id = int(lfEval(r'''matchadd('Lf_hl_bufDirname', ' \zs".*"$')''')) self._match_ids.append(id) if lfEval("get(g:, 'Lf_ShowDevIcons', 1)") == '1': @@ -314,7 +372,7 @@ def deleteMru(self): lfCmd("setlocal nomodifiable") def _previewInPopup(self, *args, **kwargs): - if len(args) == 0: + if len(args) == 0 or args[0] == '': return line = args[0] @@ -330,7 +388,9 @@ def _previewInPopup(self, *args, **kwargs): source = int(lfEval("bufadd('%s')" % escQuote(file))) else: source = file - self._createPopupPreview(file, source, 0) + + jump_cmd = 'silent! normal! g`"' + self._createPopupPreview(file, source, 0, jump_cmd) #***************************************************** diff --git a/autoload/leaderf/python/leaderf/qfloclistExpl.py b/autoload/leaderf/python/leaderf/qfloclistExpl.py index edcfcc3f..c034a01e 100644 --- a/autoload/leaderf/python/leaderf/qfloclistExpl.py +++ b/autoload/leaderf/python/leaderf/qfloclistExpl.py @@ -82,8 +82,9 @@ def _acceptSelection(self, *args, **kwargs): lfCmd("call cursor(%s, %s)" % (line_num, col)) lfCmd("norm! zv") lfCmd("norm! zz") - except vim.error: # E37 - lfPrintTraceback() + except vim.error as e: # E37 + if 'E325' not in str(e).split(':'): + lfPrintTraceback() def _getDigest(self, line, mode): """ @@ -187,7 +188,7 @@ def _createHelp(self): return help def _previewInPopup(self, *args, **kwargs): - if len(args) == 0: + if len(args) == 0 or args[0] == '': return line = args[0] diff --git a/autoload/leaderf/python/leaderf/rgExpl.py b/autoload/leaderf/python/leaderf/rgExpl.py index 476e4744..6f54556d 100644 --- a/autoload/leaderf/python/leaderf/rgExpl.py +++ b/autoload/leaderf/python/leaderf/rgExpl.py @@ -11,7 +11,6 @@ from .utils import * from .explorer import * from .manager import * -from .mru import * def workingDirectory(func): @wraps(func) @@ -44,13 +43,19 @@ def __init__(self): self._display_multi = False self._cmd_work_dir = "" self._rg = lfEval("get(g:, 'Lf_Rg', 'rg')") + self.current_buffer_num = -1 + self.current_buffer_name_len = 0 def getContent(self, *args, **kwargs): arguments_dict = kwargs.get("arguments", {}) - if "--recall" in arguments_dict: + if "--recall" in arguments_dict and "--live" not in arguments_dict: return [] self._cmd_work_dir = lfGetCwd() + + if "--live" in arguments_dict and "pattern" not in kwargs: + return AsyncExecutor.Result(iter([])) + rg_config = lfEval("get(g:, 'Lf_RgConfig', [])") extra_options = ' '.join(rg_config) for opt in rg_config: @@ -96,6 +101,8 @@ def getContent(self, *args, **kwargs): zero_args_options = '' if "-F" in arguments_dict: zero_args_options += "-F " + if "--no-fixed-strings" in arguments_dict: + zero_args_options += "--no-fixed-strings " if "-L" in arguments_dict: zero_args_options += "-L " if "-P" in arguments_dict: @@ -105,6 +112,10 @@ def getContent(self, *args, **kwargs): is_perl = False if "-v" in arguments_dict: zero_args_options += "-v " + if "--binary" in arguments_dict: + zero_args_options += "--binary " + if "--column" in arguments_dict: + zero_args_options += "--column " if "--hidden" in arguments_dict: zero_args_options += "--hidden " if "--no-config" in arguments_dict: @@ -125,6 +136,8 @@ def getContent(self, *args, **kwargs): zero_args_options += "-U " if "--multiline-dotall" in arguments_dict: zero_args_options += "--multiline-dotall " + if "--crlf" in arguments_dict: + zero_args_options += "--crlf " one_args_options = '' if "--context-separator" in arguments_dict: @@ -147,6 +160,12 @@ def getContent(self, *args, **kwargs): one_args_options += "-E %s " % arguments_dict["-E"][0] if "-M" in arguments_dict: one_args_options += "-M %s " % arguments_dict["-M"][0] + else: + for opt in rg_config: + if opt.lstrip().startswith("--max-columns=") or opt.lstrip().startswith("-M "): + break + else: + one_args_options += "-M 512 " if "-m" in arguments_dict: one_args_options += "-m %s " % arguments_dict["-m"][0] if "--max-depth" in arguments_dict: @@ -177,20 +196,30 @@ def getContent(self, *args, **kwargs): repeatable_options += "-T %s " % " -T ".join(arguments_dict["-T"]) is_literal = "-F" in arguments_dict - pattern = '' + if "--append" not in arguments_dict: self._pattern_regex = [] path_list = arguments_dict.get("PATH", []) path = ' '.join(path_list) - pattern_list = arguments_dict.get("-e", []) + if "--live" in arguments_dict: + pattern_list = [kwargs["pattern"]] + raise_except = False + # --live implies -F + if "-F" not in arguments_dict and "--no-fixed-strings" not in arguments_dict: + zero_args_options += "-F " + is_literal = True + else: + pattern_list = arguments_dict.get("-e", []) + raise_except = True + + pattern = '' for i in pattern_list or path_list[:1]: if len(pattern_list) == 0: # treat the first PATH as pattern path = ' '.join(path_list[1:]) - pattern += r'-e %s ' % i if case_flag == '-i': case_pattern = r'\c' elif case_flag == '-s': @@ -201,21 +230,38 @@ def getContent(self, *args, **kwargs): else: case_pattern = r'\C' - if len(i) > 1 and (i[0] == i[-1] == '"' or i[0] == i[-1] == "'"): - p = i[1:-1] + if "--live" in arguments_dict: + if "--no-fixed-strings" in arguments_dict: + p = i.replace('"', r'\"') + if os.name != 'nt': + pattern += r'-e "%s" ' % p.replace(r'\$', r'\\$').replace('$', r'\$') + else: + pattern += r'-e "%s" ' % p + else: + p = i.replace('\\', r'\\').replace('"', r'\"') + if os.name != 'nt': + pattern += r'-e "%s" ' % p.replace('$', r'\$') + else: + pattern += r'-e "%s" ' % p else: - p = i + if len(i) > 1 and (i[0] == i[-1] == '"' or i[0] == i[-1] == "'"): + p = i[1:-1] + else: + p = i - # -e "" - if p == '': - continue + # -e "" + if p == '': + continue - if is_literal: - if len(i) > 1 and i[0] == i[-1] == '"': - p = re.sub(r'\\(?!")', r'\\\\', p) + if os.name != 'nt': + if "-F" in arguments_dict: + pattern += r'-e "%s" ' % p.replace('$', r'\$') + else: + pattern += r'-e "%s" ' % p.replace(r'\$', r'\\$').replace('$', r'\$') else: - p = p.replace('\\', r'\\') + pattern += r'-e "%s" ' % p + if is_literal: if word_or_line == '-w ': p = r'\<' + p + r'\>' @@ -242,52 +288,93 @@ def removeFiles(names): except: pass + buffer_names = { b.number: lfRelpath(b.name) for b in vim.buffers } + + def formatLine(line): + try: + if "@LeaderF@" not in line: + return line + + _, line = line.split("@LeaderF@", 1) + buf_number = re.split("[:-]", line, 1)[0] + buf_name = buffer_names[int(buf_number)] + return line.replace(buf_number, buf_name, 1) + except: + return line + + format_line = None + if sys.version_info >= (3, 0): tmp_file = partial(tempfile.NamedTemporaryFile, encoding=lfEval("&encoding")) else: tmp_file = tempfile.NamedTemporaryFile + self.current_buffer_num = -1 + self.current_buffer_name_len = 0 if "--current-buffer" in arguments_dict: + self.current_buffer_num = vim.current.buffer.number path = '' # omit the option if vim.current.buffer.name: - try: - path = '"%s"' % os.path.relpath(lfDecode(vim.current.buffer.name)) - except ValueError: - path = '"%s"' % lfDecode(vim.current.buffer.name) + if vim.current.buffer.options["modified"] == False and not vim.current.buffer.options["bt"]: + try: + path = '"%s"' % os.path.relpath(lfDecode(vim.current.buffer.name)) + except ValueError: + path = '"%s"' % lfDecode(vim.current.buffer.name) + else: + with tmp_file(mode='w', suffix='@LeaderF@'+str(vim.current.buffer.number), + delete=False) as f: + file_name = lfDecode(f.name) + for line in vim.current.buffer: + f.write(line + '\n') + + path = '"' + file_name + '"' + tmpfilenames.append(file_name) + format_line = formatLine + self.current_buffer_name_len = len(lfRelpath(vim.current.buffer.name)) else: file_name = "%d_'No_Name_%d'" % (os.getpid(), vim.current.buffer.number) try: with lfOpen(file_name, 'w', errors='ignore') as f: - for line in vim.current.buffer[:]: + for line in vim.current.buffer: f.write(line + '\n') except IOError: with tmp_file(mode='w', suffix='_'+file_name, delete=False) as f: file_name = lfDecode(f.name) - for line in vim.current.buffer[:]: + for line in vim.current.buffer: f.write(line + '\n') path = '"' + file_name + '"' tmpfilenames.append(file_name) - - if "--all-buffers" in arguments_dict: + elif "--all-buffers" in arguments_dict: path = '' # omit the option for b in vim.buffers: if lfEval("buflisted(%d)" % b.number) == '1': if b.name: - try: - path += '"' + os.path.relpath(lfDecode(b.name)) + '" ' - except ValueError: - path += '"' + lfDecode(b.name) + '" ' + if b.options["modified"] == False: + try: + path += '"' + os.path.relpath(lfDecode(b.name)) + '" ' + except ValueError: + path += '"' + lfDecode(b.name) + '" ' + else: + with tmp_file(mode='w', suffix='@LeaderF@'+str(b.number), + delete=False) as f: + file_name = lfDecode(f.name) + for line in b: + f.write(line + '\n') + + path += '"' + file_name + '" ' + tmpfilenames.append(file_name) + format_line = formatLine else: file_name = "%d_'No_Name_%d'" % (os.getpid(), b.number) try: with lfOpen(file_name, 'w', errors='ignore') as f: - for line in b[:]: + for line in b: f.write(line + '\n') except IOError: with tmp_file(mode='w', suffix='_'+file_name, delete=False) as f: file_name = lfDecode(f.name) - for line in b[:]: + for line in b: f.write(line + '\n') path += '"' + file_name + '" ' @@ -304,15 +391,31 @@ def removeFiles(names): heading = "--no-heading" cmd = '''{} {} --no-config --no-ignore-messages {} --with-filename --color never --line-number '''\ - '''{} {}{}{}{}{}{}'''.format(self._rg, extra_options, heading, case_flag, word_or_line, zero_args_options, - one_args_options, repeatable_options, lfDecode(pattern), path) + '''{} {}{}{}{}{}{}'''.format(self._rg, extra_options, heading, case_flag, + word_or_line, zero_args_options, one_args_options, + repeatable_options, lfDecode(pattern), path) lfCmd("let g:Lf_Debug_RgCmd = '%s'" % escQuote(cmd)) - content = executor.execute(cmd, encoding=lfEval("&encoding"), cleanup=partial(removeFiles, tmpfilenames)) + content = executor.execute(cmd, encoding=lfEval("&encoding"), + cleanup=partial(removeFiles, tmpfilenames), + raise_except=raise_except, + format_line=format_line) return content def translateRegex(self, regex, is_perl=False): + + def replace(text, pattern, repl): + r""" + only replace pattern with even number of \ preceding it + """ + result = '' + for s in re.split(r'((?:\\\\)+)', text): + result += re.sub(pattern, repl, s) + + return result + vim_regex = regex + vim_regex = vim_regex.replace(r"\\", "\\") vim_regex = re.sub(r'([%@&])', r'\\\1', vim_regex) # non-greedy pattern @@ -338,12 +441,12 @@ def translateRegex(self, regex, is_perl=False): vim_regex = re.sub(r'\(\?>(.+?)\)', r'(\1)@>', vim_regex) # this won't hurt although they are not the same - vim_regex = vim_regex.replace(r'\A', r'^') - vim_regex = vim_regex.replace(r'\z', r'$') - vim_regex = vim_regex.replace(r'\B', r'') + vim_regex = replace(vim_regex, r'\\A', r'^') + vim_regex = replace(vim_regex, r'\\z', r'$') + vim_regex = replace(vim_regex, r'\\B', r'') # word boundary - vim_regex = re.sub(r'\\b', r'(<|>)', vim_regex) + vim_regex = replace(vim_regex, r'\\b', r'(<|>)') # case-insensitive vim_regex = vim_regex.replace(r'(?i)', r'\c') @@ -358,19 +461,19 @@ def translateRegex(self, regex, is_perl=False): # \a bell (\x07) # \f form feed (\x0C) # \v vertical tab (\x0B) - vim_regex = vim_regex.replace(r'\a', r'%x07') - vim_regex = vim_regex.replace(r'\f', r'%x0C') - vim_regex = vim_regex.replace(r'\v', r'%x0B') + vim_regex = replace(vim_regex, r'\\a', r'%x07') + vim_regex = replace(vim_regex, r'\\f', r'%x0C') + vim_regex = replace(vim_regex, r'\\v', r'%x0B') # \123 octal character code (up to three digits) (when enabled) # \x7F hex character code (exactly two digits) - vim_regex = re.sub(r'\\(x[0-9A-Fa-f][0-9A-Fa-f])', r'%\1', vim_regex) + vim_regex = replace(vim_regex, r'\\(x[0-9A-Fa-f][0-9A-Fa-f])', r'%\1') # \x{10FFFF} any hex character code corresponding to a Unicode code point # \u007F hex character code (exactly four digits) # \u{7F} any hex character code corresponding to a Unicode code point # \U0000007F hex character code (exactly eight digits) # \U{7F} any hex character code corresponding to a Unicode code point - vim_regex = re.sub(r'\\([uU])', r'%\1', vim_regex) + vim_regex = replace(vim_regex, r'\\([uU])', r'%\1') vim_regex = re.sub(r'\[\[:ascii:\]\]', r'[\\x00-\\x7F]', vim_regex) vim_regex = re.sub(r'\[\[:word:\]\]', r'[0-9A-Za-z_]', vim_regex) @@ -399,7 +502,7 @@ def getStlCurDir(self): return escQuote(lfEncode(self._cmd_work_dir)) def supportsNameOnly(self): - return False + return True def cleanup(self): for exe in self._executor: @@ -426,6 +529,9 @@ def __init__(self): self._has_column = False self._orig_buffer = [] self._buf_number_dict = {} + self._pattern_changed = False + self._pattern_match_ids = [] + self._preview_match_ids = [] def _getExplClass(self): return RgExplorer @@ -433,16 +539,28 @@ def _getExplClass(self): def _defineMaps(self): lfCmd("call leaderf#Rg#Maps(%d)" % int("--heading" in self._arguments)) + def setStlMode(self, mode): + if mode == "FullPath": + mode = "WholeLine" + elif mode == "NameOnly": + mode = "Contents" + super(RgExplManager, self).setStlMode(mode) + def _getFileInfo(self, args): line = args[0] if "--heading" in self._arguments: + buffer_name_len = self._getExplorer().current_buffer_name_len buffer = args[1] cursor_line = args[2] if "-A" in self._arguments or "-B" in self._arguments or "-C" in self._arguments: if not re.match(r'^\d+[:-]', line): return (None, None) + if buffer_name_len > 0: + line_num = re.split("[:-]", line, 1)[0] + return (self._getExplorer().current_buffer_num, line_num) + for cur_line in reversed(buffer[:cursor_line]): if cur_line == self._getExplorer().getContextSeparator(): continue @@ -459,6 +577,10 @@ def _getFileInfo(self, args): if not re.match(r'^\d+:', line): return (None, None) + if buffer_name_len > 0: + line_num = line.split(":", 1)[0] + return (self._getExplorer().current_buffer_num, line_num) + for cur_line in reversed(buffer[:cursor_line]): if cur_line == self._getExplorer().getContextSeparator(): continue @@ -472,6 +594,11 @@ def _getFileInfo(self, args): file = os.path.join(self._getInstance().getCwd(), lfDecode(file)) line_num = line.split(':')[0] else: + buffer_name_len = self._getExplorer().current_buffer_name_len + if buffer_name_len > 0: + line_num = re.split("[:-]", line[buffer_name_len+1:], 1)[0] + return (self._getExplorer().current_buffer_num, line_num) + if "-A" in self._arguments or "-B" in self._arguments or "-C" in self._arguments: m = re.match(r'^(.+?)([:-])(\d+)\2', line) file, sep, line_num = m.group(1, 2, 3) @@ -486,6 +613,7 @@ def _getFileInfo(self, args): m = re.match(r'^(.+?)%s(\d+)%s' % (sep, sep), line) if m: file, line_num = m.group(1, 2) + if not re.search(r"\d+_'No_Name_(\d+)'", file): i = 1 while not os.path.exists(lfDecode(file)): @@ -494,8 +622,12 @@ def _getFileInfo(self, args): file, line_num = m.group(1, 3) else: m = re.match(r'^(.+?):(\d+):', line) + if m is None: + return (None, None) + file, line_num = m.group(1, 2) - if not re.search(r"\d+_'No_Name_(\d+)'", file): + match = re.search(r"\d+_'No_Name_(\d+)'", file) + if not match: if not os.path.isabs(file): file = os.path.join(self._getInstance().getCwd(), lfDecode(file)) i = 1 @@ -505,6 +637,9 @@ def _getFileInfo(self, args): file, line_num = m.group(1, 2) if not os.path.isabs(file): file = os.path.join(self._getInstance().getCwd(), lfDecode(file)) + else: + buf_number = match.group(1) + return (int(buf_number), line_num) file = os.path.normpath(lfEncode(file)) @@ -522,9 +657,8 @@ def _acceptSelection(self, *args, **kwargs): if file is None: return - match = re.search(r"\d+_'No_Name_(\d+)'", file) - if match: - buf_number = match.group(1) + if isinstance(file, int): + buf_number = file else: buf_number = -1 @@ -545,6 +679,8 @@ def _acceptSelection(self, *args, **kwargs): else: lfCmd("hide buffer +%s %s" % (line_num, buf_number)) lfCmd("norm! ^zv") + if self._getExplorer().getPatternRegex(): + lfCmd("call search('%s', 'zW', line('.'))" % escQuote(self._getExplorer().getPatternRegex()[0])) lfCmd("norm! zz") if "preview" not in kwargs: @@ -554,13 +690,14 @@ def _acceptSelection(self, *args, **kwargs): self._cursorline_dict[vim.current.window] = vim.current.window.options["cursorline"] lfCmd("setlocal cursorline") - except vim.error: - lfPrintTraceback() + except vim.error as e: # E37 + if 'E325' not in str(e).split(':'): + lfPrintTraceback() def setArguments(self, arguments): self._arguments = arguments self._match_path = "--match-path" in arguments - self._has_column = "--column" in lfEval("get(g:, 'Lf_RgConfig', [])") + self._has_column = "--column" in lfEval("get(g:, 'Lf_RgConfig', [])") or "--column" in self._arguments def _getDigest(self, line, mode): """ @@ -570,9 +707,12 @@ def _getDigest(self, line, mode): 1, return the name only 2, return the directory name """ - if self._match_path: + if mode == 0 or self._match_path: return line - else: + elif mode == 1: + if mode == 0: + return line + if self._getExplorer().displayMulti(): if line == self._getExplorer().getContextSeparator(): return "" @@ -593,6 +733,8 @@ def _getDigest(self, line, mode): return line.split(":", 3)[-1] else: return line.split(":", 2)[-1] + else: + return line.split(":", 1)[0] def _getDigestStartPos(self, line, mode): """ @@ -602,7 +744,7 @@ def _getDigestStartPos(self, line, mode): 1, return the start postion of name only 2, return the start postion of directory name """ - if self._match_path: + if mode == 0 or mode == 2 or self._match_path: return 0 else: if self._getExplorer().displayMulti(): @@ -655,47 +797,47 @@ def _afterEnter(self): if self._getInstance().getWinPos() == 'popup': if "--heading" in self._arguments: if "-A" in self._arguments or "-B" in self._arguments or "-C" in self._arguments: - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_rgFileName'', ''\(^\d\+[:-].*\)\@ 1 and (pattern[0] == '"' and pattern[-1] == '"' + or pattern[0] == "'" and pattern[-1] == "'"): + pattern = pattern[1:-1] + + self._cli.setPattern(pattern) + + if pattern: + kwargs['pattern'] = self._cli.pattern + + content = self._getExplorer().getContent(*args, **kwargs) + + # clear the buffer only when the content is not a list + self._getInstance().enterBuffer(win_pos, not isinstance(content, list)) + self._initial_count = self._getInstance().getInitialWinHeight() + + self._getInstance().setStlCategory(self._getExplorer().getStlCategory()) + self._setStlMode(the_mode="Live", **kwargs) + self._getInstance().setStlCwd(self._getExplorer().getStlCurDir()) + + if kwargs.get('bang', 0): + self._current_mode = 'NORMAL' + else: + self._current_mode = 'INPUT' + lfCmd("call leaderf#colorscheme#popup#hiMode('%s', '%s')" + % (self._getExplorer().getStlCategory(), self._current_mode)) + + self._getInstance().setPopupStl(self._current_mode) - path = os.path.abspath(path) - while path != root: - for name in markers: - if os.path.exists(os.path.join(path, name)): - return path - path = os.path.abspath(os.path.join(path, "..")) + self._getInstance().buffer.vars['Lf_category'] = self._getExplorer().getStlCategory() - for name in markers: - if os.path.exists(os.path.join(path, name)): - return path + if remember_last_status: + self._resume(kwargs.get('bang', 0)) + return + + self._start_time = time.time() + + self._read_content_exception = None + + self._getInstance().setStlResultsCount(0) + self._getInstance().setStlTotal(0) + self._getInstance().setStlRunning(False) + + self._callback = self._writeBuffer + self._content = [] + self._offset_in_content = 0 + + self._read_finished = 0 + + self._stop_reader_thread = False + self._reader_thread = threading.Thread(target=self._readContent, args=(content,)) + self._reader_thread.daemon = True + self._reader_thread.start() + # for the case of --input + self._previewFirstLine() - return "" + self.input() def startExplorer(self, win_pos, *args, **kwargs): arguments_dict = kwargs.get("arguments", {}) + if "--live" in arguments_dict: + for arg in ("--nameOnly", "--fullPath", "--fuzzy", "--regexMode"): + if arg in arguments_dict: + lfPrintError("error: argument --live: not allowed with argument %s" % arg) + return + if "--heading" in arguments_dict: kwargs["bang"] = 1 @@ -856,14 +1156,14 @@ def startExplorer(self, win_pos, *args, **kwargs): cur_buf_name = lfDecode(vim.current.buffer.name) fall_back = False if 'a' in mode: - working_dir = self._nearestAncestor(root_markers, self._orig_cwd) + working_dir = nearestAncestor(root_markers, self._orig_cwd) if working_dir: # there exists a root marker in nearest ancestor path chdir(working_dir) else: fall_back = True elif 'A' in mode: if cur_buf_name: - working_dir = self._nearestAncestor(root_markers, os.path.dirname(cur_buf_name)) + working_dir = nearestAncestor(root_markers, os.path.dirname(cur_buf_name)) else: working_dir = "" if working_dir: # there exists a root marker in nearest ancestor path @@ -881,7 +1181,10 @@ def startExplorer(self, win_pos, *args, **kwargs): if cur_buf_name and not os.path.dirname(cur_buf_name).startswith(self._orig_cwd): chdir(os.path.dirname(cur_buf_name)) - super(RgExplManager, self).startExplorer(win_pos, *args, **kwargs) + if "--live" in arguments_dict or ("--recall" in arguments_dict and "--live" in self._arguments): + self.startLiveGrep(win_pos, *args, **kwargs) + else: + super(RgExplManager, self).startExplorer(win_pos, *args, **kwargs) def deleteCurrentLine(self): instance = self._getInstance() @@ -907,8 +1210,54 @@ def deleteCurrentLine(self): else: lfCmd("setlocal nomodifiable") + self._previewResult(False) + + def _clearPreviewHighlights(self): + for i in self._preview_match_ids: + lfCmd("silent! call matchdelete(%d, %d)" % (i, self._preview_winid)) + + def _highlightInPreview(self): + if lfEval("has('nvim')") != '1': + try: + for i in self._getExplorer().getPatternRegex(): + lfCmd("""call win_execute(%d, "let matchid = matchadd('Lf_hl_rgHighlight', '%s', 9)")""" + % (self._preview_winid, re.sub(r'\\(?!")', r'\\\\', escQuote(i)))) + id = int(lfEval("matchid")) + self._preview_match_ids.append(id) + except vim.error: + pass + else: + cur_winid = lfEval("win_getid()") + lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) + if lfEval("win_getid()") != cur_winid: + try: + for i in self._getExplorer().getPatternRegex(): + id = int(lfEval("matchadd('Lf_hl_rgHighlight', '%s', 9)" % escQuote(i))) + self._preview_match_ids.append(id) + except vim.error: + pass + lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) + + def _createPopupPreview(self, title, source, line_num, jump_cmd=''): + """ + Args: + source: + if the type is int, it is a buffer number + if the type is str, it is a file name + + return False if use existing window, otherwise True + """ + + if (super(RgExplManager, self)._createPopupPreview(title, source, line_num, jump_cmd) + and lfEval("get(g:, 'Lf_RgHighlightInPreview', 1)") == '1'): + + self._highlightInPreview() + return True + + return False + def _previewInPopup(self, *args, **kwargs): - if len(args) == 0: + if len(args) == 0 or args[0] == '': return if args[0] == self._getExplorer().getContextSeparator(): @@ -918,9 +1267,8 @@ def _previewInPopup(self, *args, **kwargs): if file is None: return - match = re.search(r"\d+_'No_Name_(\d+)'", file) - if match: - source = int(match.group(1)) + if isinstance(file, int): + source = file else: if lfEval("bufloaded('%s')" % escQuote(file)) == '1': source = int(lfEval("bufadd('%s')" % escQuote(file))) @@ -929,36 +1277,15 @@ def _previewInPopup(self, *args, **kwargs): self._createPopupPreview("", source, line_num) - if lfEval("get(g:, 'Lf_RgHighlightInPreview', 1)") == '1': - if lfEval("has('nvim')") != '1': - try: - for i in self._getExplorer().getPatternRegex(): - lfCmd("""call win_execute(%d, "let matchid = matchadd('Lf_hl_rgHighlight', '%s', 9)")""" - % (self._preview_winid, re.sub(r'\\(?!")', r'\\\\', escQuote(i)))) - id = int(lfEval("matchid")) - self._match_ids.append(id) - except vim.error: - pass - else: - cur_winid = lfEval("win_getid()") - lfCmd("noautocmd call win_gotoid(%d)" % self._preview_winid) - try: - for i in self._getExplorer().getPatternRegex(): - id = int(lfEval("matchadd('Lf_hl_rgHighlight', '%s', 9)" % escQuote(i))) - self._match_ids.append(id) - except vim.error: - pass - lfCmd("noautocmd call win_gotoid(%s)" % cur_winid) - def outputToQflist(self, *args, **kwargs): items = self._getFormatedContents() - lfCmd("call setqflist(%s, 'r')" % json.dumps(items)) + lfCmd("call setqflist(%s, 'r')" % json.dumps(items, ensure_ascii=False)) lfCmd("echohl WarningMsg | redraw | echo ' Output result to quickfix list.' | echohl NONE") def outputToLoclist(self, *args, **kwargs): items = self._getFormatedContents() winnr = lfEval('bufwinnr(%s)' % self._cur_buffer.number) - lfCmd("call setloclist(%d, %s, 'r')" % (int(winnr), json.dumps(items))) + lfCmd("call setloclist(%d, %s, 'r')" % (int(winnr), json.dumps(items, ensure_ascii=False))) lfCmd("echohl WarningMsg | redraw | echo ' Output result to location list.' | echohl NONE") def _getFormatedContents(self): @@ -1019,13 +1346,13 @@ def replace(self): string = lfEval("input('Replace with: ')") flags = lfEval("input('flags: ', 'gc')") if "--heading" in self._arguments: - lfCmd('%d;$s/\(^\d\+[:-].\{-}\)\@<=%s/%s/%s' - % (self._getInstance().helpLength + 1, escQuote(pattern.replace('/', '\/')), - escQuote(string.replace('/', '\/')), escQuote(flags))) + lfCmd(r"""%d;$s/\%%(^\d\+[:-].\{-}\)\@<=%s/%s/%s""" + % (self._getInstance().helpLength + 1, pattern.replace('/', r'\/'), + string.replace('/', r'\/'), flags)) else: - lfCmd('%d;$s/\(^.\+\(:\d\+:\|-\d\+-\).\{-}\)\@<=%s/%s/%s' - % (self._getInstance().helpLength + 1, escQuote(pattern.replace('/', '\/')), - escQuote(string.replace('/', '\/')), escQuote(flags))) + lfCmd(r"""%d;$s/\%%(^.\+\%%(:\d\+:\|-\d\+-\).\{-}\)\@<=%s/%s/%s""" + % (self._getInstance().helpLength + 1, pattern.replace('/', r'\/'), + string.replace('/', r'\/'), flags)) lfCmd("call histdel('search', -1)") lfCmd("let @/ = histget('search', -1)") lfCmd("nohlsearch") @@ -1191,7 +1518,7 @@ def undo(self): lfCmd("undo") lfCmd("echohl WarningMsg | redraw | echo ' undo finished!' | echohl None") - def quit(self): + def confirm(self): if self._getInstance().buffer.options["modified"]: selection = int(lfEval("""confirm("buffer changed, apply changes or discard?", "&apply\n&discard")""")) if selection == 0: @@ -1210,10 +1537,99 @@ def quit(self): self._getInstance().buffer.options["modifiable"] = False self._getInstance().buffer.options["undolevels"] = -1 + def quit(self): + self.confirm() super(RgExplManager, self).quit() + lfCmd("silent! autocmd! Lf_Rg_ReplaceMode") + def accept(self, mode=''): + self.confirm() + super(RgExplManager, self).accept(mode) lfCmd("silent! autocmd! Lf_Rg_ReplaceMode") + def _writeBuffer(self): + if not self._cli.pattern: # e.g., when or is typed + return 100 + + if self._read_content_exception is not None: + raise self._read_content_exception[1] + + if self._read_finished > 0: + if self._read_finished == 1: + self._read_finished += 1 + self._getExplorer().setContent(self._content) + self._getInstance().setStlTotal(len(self._content)//self._getUnit()) + self._getInstance().setStlRunning(False) + + self._getInstance().setBuffer(self._content[:self._initial_count]) + self._previewResult(False) + + self._getInstance().setStlResultsCount(len(self._content)) + + if self._getInstance().getWinPos() not in ('popup', 'floatwin'): + lfCmd("redrawstatus") + + return 100 + else: + cur_len = len(self._content) + if time.time() - self._start_time > 0.1: + self._start_time = time.time() + self._getInstance().setStlTotal(cur_len//self._getUnit()) + self._getInstance().setStlRunning(True) + self._getInstance().setStlResultsCount(cur_len) + + if self._getInstance().getWinPos() not in ('popup', 'floatwin'): + lfCmd("redrawstatus") + + if self._pattern_changed or len(self._getInstance().buffer) < min(cur_len, self._initial_count): + self._pattern_changed = False + self._getInstance().setBuffer(self._content[:self._initial_count]) + if not self._getInstance().empty(): + self._previewResult(False) + + def _killThread(self, executors): + for exe in executors: + exe.killProcess() + + def _search(self, content, is_continue=False, step=0): + if "--live" not in self._arguments: + super(RgExplManager, self)._search(content, is_continue, step) + return + + if self._reader_thread and self._reader_thread.is_alive(): + self._stop_reader_thread = True + self._reader_thread.join() + + # kill process in a thread + kill_thread = threading.Thread(target=self._killThread, args=(self._getExplorer()._executor,)) + self._getExplorer()._executor = [] + kill_thread.daemon = True + kill_thread.start() + + if not self._cli.pattern: # e.g., when or is typed + self._getInstance().clearBuffer() + self._content = [] + self._getInstance().setStlResultsCount(0) + self._getInstance().setStlTotal(len(self._content)//self._getUnit()) + self._getInstance().setStlRunning(False) + self._getInstance().refreshPopupStatusline() + self._previewResult(False) + return + + self._clearPreviewHighlights() + self._stop_reader_thread = False + self._read_finished = 0 + self._content = [] + self._pattern_changed = True + content = self._getExplorer().getContent(arguments=self._arguments, pattern=self._cli.pattern) + self._reader_thread = threading.Thread(target=self._readContent, args=(content,)) + self._reader_thread.daemon = True + self._reader_thread.start() + + self._highlightMatch() + self._highlightInPreview() + + #***************************************************** # rgExplManager is a singleton #***************************************************** diff --git a/autoload/leaderf/python/leaderf/selfExpl.py b/autoload/leaderf/python/leaderf/selfExpl.py index 38a38ae8..debb1bd0 100644 --- a/autoload/leaderf/python/leaderf/selfExpl.py +++ b/autoload/leaderf/python/leaderf/selfExpl.py @@ -129,18 +129,18 @@ def _createHelp(self): def _afterEnter(self): super(SelfExplManager, self)._afterEnter() if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_selfIndex'', ''^\d\+'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_selfIndex'', ''^\d\+'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_selfDescription'', '' \zs".*"$'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_selfDescription'', '' \zs".*"$'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) else: - id = int(lfEval('''matchadd('Lf_hl_selfIndex', '^\d\+')''')) + id = int(lfEval(r'''matchadd('Lf_hl_selfIndex', '^\d\+')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_selfDescription', ' \zs".*"$')''')) + id = int(lfEval(r'''matchadd('Lf_hl_selfDescription', ' \zs".*"$')''')) self._match_ids.append(id) def _beforeExit(self): diff --git a/autoload/leaderf/python/leaderf/tagExpl.py b/autoload/leaderf/python/leaderf/tagExpl.py index 7cb72f40..ad9baaf1 100644 --- a/autoload/leaderf/python/leaderf/tagExpl.py +++ b/autoload/leaderf/python/leaderf/tagExpl.py @@ -87,8 +87,9 @@ def _acceptSelection(self, *args, **kwargs): lfDrop('', tagfile) else: lfCmd("hide edit %s" % escSpecial(tagfile)) - except vim.error: # E37 - lfPrintTraceback() + except vim.error as e: # E37 + if 'E325' not in str(e).split(':'): + lfPrintTraceback() if tagaddress[0] not in '/?': lfCmd(tagaddress) @@ -97,23 +98,23 @@ def _acceptSelection(self, *args, **kwargs): # In case there are mutiple matches. if len(res) > 1: - result = re.search('(?<=\t)line:\d+', res[1]) + result = re.search(r'(?<=\t)line:\d+', res[1]) if result: - line_nr = result.group(0).split(':')[1] - lfCmd(line_nr) + line_num = result.group(0).split(':')[1] + lfCmd(line_num) else: # for c, c++ keyword = "(class|enum|struct|union)" - result = re.search('(?<=\t)%s:\S+' % keyword, res[1]) + result = re.search(r'(?<=\t)%s:\S+' % keyword, res[1]) if result: tagfield = result.group(0).split(":") name = tagfield[0] value = tagfield[-1] - lfCmd("call search('\m%s\_s\+%s\_[^;{]*{', 'w')" % (name, value)) + lfCmd(r"call search('\m%s\_s\+%s\_[^;{]*{', 'w')" % (name, value)) - pattern = "\M" + tagaddress[1:-1] + pattern = r"\M" + tagaddress[1:-1] lfCmd("call search('%s', 'w')" % escQuote(pattern)) - if lfEval("search('\V%s', 'wc')" % escQuote(tagname)) == '0': + if lfEval(r"search('\V%s', 'wc')" % escQuote(tagname)) == '0': lfCmd("norm! ^") lfCmd("norm! zv") lfCmd("norm! zz") @@ -162,28 +163,28 @@ def _createHelp(self): def _afterEnter(self): super(TagExplManager, self)._afterEnter() if self._getInstance().getWinPos() == 'popup': - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_tagFile'', ''^.\{-}\t\zs.\{-}\ze\t'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_tagFile'', ''^.\{-}\t\zs.\{-}\ze\t'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_tagType'', '';"\t\zs[cdefFgmpstuv]\ze\(\t\|$\)'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_tagType'', '';"\t\zs[cdefFgmpstuv]\ze\(\t\|$\)'')')""" % self._getInstance().getPopupWinId()) id = int(lfEval("matchid")) self._match_ids.append(id) keyword = ["namespace", "class", "enum", "file", "function", "kind", "struct", "union"] for i in keyword: - lfCmd("""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_tagKeyword'', ''\(;"\t.\{-}\)\@<=%s:'')')""" + lfCmd(r"""call win_execute(%d, 'let matchid = matchadd(''Lf_hl_tagKeyword'', ''\(;"\t.\{-}\)\@<=%s:'')')""" % (self._getInstance().getPopupWinId(), i)) id = int(lfEval("matchid")) self._match_ids.append(id) else: - id = int(lfEval('''matchadd('Lf_hl_tagFile', '^.\{-}\t\zs.\{-}\ze\t')''')) + id = int(lfEval(r'''matchadd('Lf_hl_tagFile', '^.\{-}\t\zs.\{-}\ze\t')''')) self._match_ids.append(id) - id = int(lfEval('''matchadd('Lf_hl_tagType', ';"\t\zs[cdefFgmpstuv]\ze\(\t\|$\)')''')) + id = int(lfEval(r'''matchadd('Lf_hl_tagType', ';"\t\zs[cdefFgmpstuv]\ze\(\t\|$\)')''')) self._match_ids.append(id) keyword = ["namespace", "class", "enum", "file", "function", "kind", "struct", "union"] for i in keyword: - id = int(lfEval('''matchadd('Lf_hl_tagKeyword', '\(;"\t.\{-}\)\@<=%s:')''' % i)) + id = int(lfEval(r'''matchadd('Lf_hl_tagKeyword', '\(;"\t.\{-}\)\@<=%s:')''' % i)) self._match_ids.append(id) def _beforeExit(self): @@ -210,8 +211,9 @@ def _bangEnter(self): instance.window.options["cursorline"] = True def _previewInPopup(self, *args, **kwargs): - if len(args) == 0: + if len(args) == 0 or args[0] == '': return + line = args[0] # {tagname}{tagfile}{tagaddress}[;"{tagfield}..] tagname, tagfile, right = line.split('\t', 2) @@ -222,21 +224,21 @@ def _previewInPopup(self, *args, **kwargs): else: # In case there are mutiple matches. if len(res) > 1: - result = re.search('(?<=\t)line:\d+', res[1]) + result = re.search(r'(?<=\t)line:\d+', res[1]) if result: - line_nr = result.group(0).split(':')[1] - self._createPopupPreview("", tagfile, line_nr) + line_num = result.group(0).split(':')[1] + self._createPopupPreview("", tagfile, line_num) else: # for c, c++ keyword = "(class|enum|struct|union)" - result = re.search('(?<=\t)%s:\S+' % keyword, res[1]) + result = re.search(r'(?<=\t)%s:\S+' % keyword, res[1]) jump_cmd = 'exec "norm! gg"' if result: tagfield = result.group(0).split(":") name = tagfield[0] value = tagfield[-1] - jump_cmd += " | call search('\m%s\_s\+%s\_[^;{]*{', 'w')" % (name, value) + jump_cmd += r" | call search('\m%s\_s\+%s\_[^;{]*{', 'w')" % (name, value) - pattern = "\M" + tagaddress[1:-1] + pattern = r"\M" + tagaddress[1:-1] jump_cmd += " | call search('%s', 'w')" % escQuote(pattern) self._createPopupPreview("", tagfile, 0, jump_cmd) diff --git a/autoload/leaderf/python/leaderf/utils.py b/autoload/leaderf/python/leaderf/utils.py index 9bb4d662..6fe6ab2f 100644 --- a/autoload/leaderf/python/leaderf/utils.py +++ b/autoload/leaderf/python/leaderf/utils.py @@ -6,14 +6,22 @@ import re import os import os.path +import time import locale import traceback import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) +from functools import wraps + lfCmd = vim.command lfEval = vim.eval +# https://github.com/neovim/neovim/issues/8336 +if lfEval("has('nvim')") == '1': + lfChdir = vim.chdir +else: + lfChdir = os.chdir lf_encoding = lfEval("&encoding") @@ -133,7 +141,7 @@ def escQuote(str): return "" if str is None else str.replace("'","''") def escSpecial(str): - return re.sub('([%#$" ])', r"\\\1", str) + return re.sub('([()%#$" ])', r"\\\1", str) def equal(str1, str2, ignorecase=True): if ignorecase: @@ -158,7 +166,7 @@ def lfWinId(winnr, tab=None): def lfPrintError(error): if lfEval("get(g:, 'Lf_Exception', 0)") == '1': - raise error + raise Exception(error) else: error = lfEncode(str(repr(error))) lfCmd("echohl Error | redraw | echo '%s' | echohl None" % escQuote(error)) @@ -221,3 +229,1230 @@ def lfDrop(type, file_name, line_num=None): lfCmd("hide edit +%d %s" % (line_num, escSpecial(file_name))) else: lfCmd("hide edit %s" % escSpecial(file_name)) + + +def shrinkUser(path): + home = os.path.expanduser("~") + if path.lower().startswith(home): + return path.replace(home, "~", 1) + return path + +def nearestAncestor(markers, path): + """ + return the nearest ancestor path(including itself) of `path` that contains + one of files or directories in `markers`. + `markers` is a list of file or directory names. + """ + if os.name == 'nt': + # e.g. C:\\ + root = os.path.splitdrive(os.path.abspath(path))[0] + os.sep + else: + root = '/' + + path = os.path.abspath(path) + while path != root: + for name in markers: + if os.path.exists(os.path.join(path, name)): + return path + path = os.path.abspath(os.path.join(path, "..")) + + for name in markers: + if os.path.exists(os.path.join(path, name)): + return path + + return "" + +extension_ft = { + ".8th" : "8th", + ".aap" : "aap", + ".abap" : "abap", + ".abc" : "abc", + ".abl" : "abel", + ".wrm" : "acedb", + ".adb" : "ada", + ".ads" : "ada", + ".ada" : "ada", + ".gpr" : "ada", + ".tdf" : "ahdl", + ".aidl" : "aidl", + ".run" : "ampl", + ".ino" : "arduino", + ".pde" : "arduino", + ".a65" : "a65", + ".scpt" : "applescript", + ".am" : "elf", + ".aml" : "aml", + ".art" : "art", + ".asciidoc" : "asciidoc", + ".adoc" : "asciidoc", + ".asn" : "asn", + ".asn1" : "asn", + ".demo" : "maxima", + ".wxm" : "maxima", + ".mar" : "vmasm", + ".astro" : "astro", + ".atl" : "atlas", + ".as" : "atlas", + ".atom" : "xml", + ".au3" : "autoit", + ".ahk" : "autohotkey", + ".at" : "m4", + ".ave" : "ave", + ".awk" : "awk", + ".gawk" : "awk", + ".mch" : "b", + ".ref" : "b", + ".imp" : "b", + ".bass" : "bass", + ".vb" : "vb", + ".vbs" : "vb", + ".dsm" : "vb", + ".ctl" : "vb", + ".iba" : "ibasic", + ".ibi" : "ibasic", + ".fb" : "freebasic", + ".bat" : "dosbatch", + ".bc" : "bc", + ".bdf" : "bdf", + ".beancount" : "beancount", + ".bib" : "bib", + ".bst" : "bst", + ".bicep" : "bicep", + ".bl" : "blank", + ".bb" : "bitbake", + ".bbappend" : "bitbake", + ".bbclass" : "bitbake", + ".bsd" : "bsdl", + ".bsdl" : "bsdl", + ".bzl" : "bzl", + ".bazel" : "bzl", + ".BUILD" : "bzl", + ".lpc" : "lpc", + ".ulpc" : "lpc", + ".cairo" : "cairo", + ".capnp" : "capnp", + ".cs" : "cs", + ".csx" : "cs", + ".csdl" : "csdl", + ".cabal" : "cabal", + ".toc" : "cdrtoc", + ".chai" : "chaiscript", + ".chatito" : "chatito", + ".cdl" : "cdl", + ".recipe" : "conaryrecipe", + ".cpon" : "cpon", + ".crm" : "crm", + ".cyn" : "cynpp", + ".c" : "c", + ".cc" : "cpp", + ".cpp" : "cpp", + ".cxx" : "cpp", + ".c++" : "cpp", + ".c++m" : "cpp", + ".h" : "cpp", + ".hh" : "cpp", + ".hxx" : "cpp", + ".hpp" : "cpp", + ".ipp" : "cpp", + ".moc" : "cpp", + ".tcc" : "cpp", + ".inl" : "cpp", + ".C" : "cpp", + ".H" : "cpp", + ".cppm" : "cpp", + ".ccm" : "cpp", + ".cxxm" : "cpp", + ".chf" : "ch", + ".tlh" : "cpp", + ".css" : "css", + ".con" : "cterm", + ".chopro" : "chordpro", + ".crd" : "chordpro", + ".cho" : "chordpro", + ".crdpro" : "chordpro", + ".chordpro" : "chordpro", + ".dcl" : "clean", + ".icl" : "clean", + ".eni" : "cl", + ".clj" : "clojure", + ".cljs" : "clojure", + ".cljx" : "clojure", + ".cljc" : "clojure", + ".cmake" : "cmake", + ".cbl" : "cobol", + ".cob" : "cobol", + ".lib" : "cobol", + ".atg" : "coco", + ".cfm" : "cf", + ".cfi" : "cf", + ".cfc" : "cf", + ".cook" : "cook", + ".cql" : "cqlang", + ".cr" : "crystal", + ".csv" : "csv", + ".cu" : "cuda", + ".cuh" : "cuda", + ".cue" : "cue", + ".dcd" : "dcd", + ".exs" : "elixir", + ".eex" : "eelixir", + ".leex" : "eelixir", + ".elv" : "elvish", + ".lrc" : "lyrics", + ".quake" : "m3quake", + ".qc" : "c", + ".feature" : "cucumber", + ".csp" : "csp", + ".fdr" : "csp", + ".pld" : "cupl", + ".si" : "cuplsim", + ".dart" : "dart", + ".drt" : "dart", + ".dhall" : "dhall", + ".desc" : "desc", + ".desktop" : "desktop", + ".directory" : "desktop", + ".diff" : "diff", + ".rej" : "diff", + ".dot" : "dot", + ".gv" : "dot", + ".lid" : "dylanlid", + ".intr" : "dylanintr", + ".dylan" : "dylan", + ".def" : "def", + ".drac" : "dracula", + ".drc" : "dracula", + ".ds" : "datascript", + ".dtd" : "dtd", + ".dts" : "dts", + ".dtsi" : "dts", + ".ecd" : "ecd", + ".erl" : "erlang", + ".hrl" : "erlang", + ".yaws" : "erlang", + ".elm" : "elm", + ".lc" : "elsa", + ".esdl" : "esdl", + ".ec" : "esqlc", + ".EC" : "esqlc", + ".strl" : "esterel", + ".csc" : "csc", + ".exp" : "expect", + ".fal" : "falcon", + ".fan" : "fan", + ".fwt" : "fan", + ".factor" : "factor", + ".fnl" : "fennel", + ".fir" : "firrtl", + ".fish" : "fish", + ".fex" : "focexec", + ".focexec" : "focexec", + ".mas" : "master", + ".master" : "master", + ".ft" : "forth", + ".fth" : "forth", + ".frt" : "reva", + ".F" : "fortran", + ".FOR" : "fortran", + ".FPP" : "fortran", + ".FTN" : "fortran", + ".F77" : "fortran", + ".F90" : "fortran", + ".F95" : "fortran", + ".F03" : "fortran", + ".F08" : "fortran", + ".f" : "fortran", + ".for" : "fortran", + ".fortran" : "fortran", + ".fpp" : "fortran", + ".ftn" : "fortran", + ".f77" : "fortran", + ".f90" : "fortran", + ".f95" : "fortran", + ".f03" : "fortran", + ".f08" : "fortran", + ".fsl" : "framescript", + ".fc" : "func", + ".fusion" : "fusion", + ".fsh" : "fsh", + ".fsi" : "fsharp", + ".fsx" : "fsharp", + ".gdb" : "gdb", + ".mo" : "gdmo", + ".gdmo" : "gdmo", + ".gd" : "gdscript", + ".tscn" : "gdresource", + ".tres" : "gdresource", + ".gdshader" : "gdshader", + ".shader" : "gdshader", + ".ged" : "gedcom", + ".gmi" : "gemtext", + ".gemini" : "gemtext", + ".gift" : "gift", + ".gleam" : "gleam", + ".glsl" : "glsl", + ".gp" : "gp", + ".gts" : "typescript.glimmer", + ".gjs" : "javascript.glimmer", + ".gpi" : "gnuplot", + ".go" : "go", + ".gs" : "grads", + ".graphql" : "graphql", + ".graphqls" : "graphql", + ".gql" : "graphql", + ".gretl" : "gretl", + ".gradle" : "groovy", + ".groovy" : "groovy", + ".gsp" : "gsp", + ".gyp" : "gyp", + ".gypi" : "gyp", + ".hack" : "hack", + ".hackpartial" : "hack", + ".haml" : "haml", + ".hsm" : "hamster", + ".hbs" : "handlebars", + ".ha" : "hare", + ".hs" : "haskell", + ".hsc" : "haskell", + ".hsig" : "haskell", + ".lhs" : "lhaskell", + ".chs" : "chaskell", + ".ht" : "haste", + ".htpp" : "hastepreproc", + ".hcl" : "hcl", + ".vc" : "hercules", + ".ev" : "hercules", + ".sum" : "hercules", + ".errsum" : "hercules", + ".heex" : "heex", + ".hex" : "hex", + ".h32" : "hex", + ".hjson" : "hjson", + ".m3u" : "hlsplaylist", + ".m3u8" : "hlsplaylist", + ".hws" : "hollywood", + ".hoon" : "hoon", + ".htm" : "html", + ".html" : "html", + ".shtml" : "html", + ".stm" : "html", + ".cshtml" : "html", + ".cshtml" : "html", + ".erb" : "eruby", + ".rhtml" : "eruby", + ".tmpl" : "template", + ".hb" : "hb", + ".htt" : "httest", + ".htb" : "httest", + ".icn" : "icon", + ".odl" : "msidl", + ".mof" : "msidl", + ".inf" : "inform", + ".INF" : "inform", + ".ii" : "initng", + ".4gl" : "fgl", + ".4gh" : "fgl", + ".m4gl" : "fgl", + ".ini" : "dosini", + ".iss" : "iss", + ".ijs" : "j", + ".jal" : "jal", + ".JAL" : "jal", + ".jpl" : "jam", + ".jpr" : "jam", + ".java" : "java", + ".jav" : "java", + ".jj" : "javacc", + ".jjt" : "javacc", + ".js" : "javascript", + ".jsm" : "javascript", + ".javascript" : "javascript", + ".es" : "javascript", + ".mjs" : "javascript", + ".cjs" : "javascript", + ".jsx" : "javascriptreact", + ".jsp" : "jsp", + ".properties" : "jproperties", + ".clp" : "jess", + ".jgr" : "jgraph", + ".jov" : "jovial", + ".j73" : "jovial", + ".jovial" : "jovial", + ".jq" : "jq", + ".json5" : "json5", + ".ipynb" : "json", + ".jsonc" : "jsonc", + ".json" : "json", + ".jsonp" : "json", + ".webmanifest" : "json", + ".jsonnet" : "jsonnet", + ".libsonnet" : "jsonnet", + ".jl" : "julia", + ".kdl" : "kdl", + ".kix" : "kix", + ".k" : "kwt", + ".kv" : "kivy", + ".kt" : "kotlin", + ".ktm" : "kotlin", + ".kts" : "kotlin", + ".ks" : "kscript", + ".ace" : "lace", + ".ACE" : "lace", + ".latte" : "latte", + ".lte" : "latte", + ".ldif" : "ldif", + ".ld" : "ld", + ".lean" : "lean", + ".ldg" : "ledger", + ".ledger" : "ledger", + ".journal" : "ledger", + ".less" : "less", + ".lex" : "lex", + ".l" : "lex", + ".lxx" : "lex", + ".ll" : "lifelines", + ".ly" : "lilypond", + ".ily" : "lilypond", + ".lsp" : "lisp", + ".lisp" : "lisp", + ".asd" : "lisp", + ".el" : "lisp", + ".cl" : "lisp", + ".L" : "lisp", + ".liquid" : "liquid", + ".lite" : "lite", + ".lt" : "lite", + ".livemd" : "livebook", + ".lgt" : "logtalk", + ".lot" : "lotos", + ".lotos" : "lotos", + ".lou" : "lout", + ".lout" : "lout", + ".lua" : "lua", + ".luau" : "luau", + ".rockspec" : "lua", + ".lss" : "lss", + ".mgp" : "mgp", + ".eml" : "mail", + ".mk" : "make", + ".mak" : "make", + ".dsp" : "make", + ".ist" : "ist", + ".mst" : "ist", + ".page" : "mallard", + ".man" : "man", + ".mv" : "maple", + ".mpl" : "maple", + ".mws" : "maple", + ".map" : "map", + ".markdown" : "markdown", + ".mdown" : "markdown", + ".mkd" : "markdown", + ".mkdn" : "markdown", + ".mdwn" : "markdown", + ".md" : "markdown", + ".mason" : "mason", + ".mhtml" : "mason", + ".comp" : "mason", + ".nb" : "mma", + ".mel" : "mel", + ".hgrc" : "cfg", + ".mmd" : "mermaid", + ".mmdc" : "mermaid", + ".mermaid" : "mermaid", + ".wrap" : "dosini", + ".mf" : "mf", + ".mp" : "mp", + ".mgl" : "mgl", + ".mix" : "mix", + ".mixal" : "mix", + ".mmp" : "mmp", + ".m2" : "modula2", + ".DEF" : "modula2", + ".mi" : "modula2", + ".lm3" : "modula3", + ".isc" : "monk", + ".monk" : "monk", + ".ssc" : "monk", + ".tsc" : "monk", + ".moo" : "moo", + ".moon" : "moonscript", + ".move" : "move", + ".mpd" : "xml", + ".s19" : "srec", + ".s28" : "srec", + ".s37" : "srec", + ".mot" : "srec", + ".srec" : "srec", + ".msql" : "msql", + ".mysql" : "mysql", + ".mu" : "mupad", + ".mush" : "mush", + ".n1ql" : "n1ql", + ".nql" : "n1ql", + ".nanorc" : "nanorc", + ".nginx" : "nginx", + ".nim" : "nim", + ".nims" : "nim", + ".nimble" : "nim", + ".ninja" : "ninja", + ".nix" : "nix", + ".ncf" : "ncf", + ".tr" : "nroff", + ".nr" : "nroff", + ".roff" : "nroff", + ".tmac" : "nroff", + ".mom" : "nroff", + ".nqc" : "nqc", + ".nse" : "lua", + ".nsi" : "nsis", + ".nsh" : "nsis", + ".obl" : "obse", + ".obse" : "obse", + ".oblivion" : "obse", + ".obscript" : "obse", + ".ml" : "ocaml", + ".mli" : "ocaml", + ".mll" : "ocaml", + ".mly" : "ocaml", + ".mlt" : "ocaml", + ".mlp" : "ocaml", + ".mlip" : "ocaml", + ".occ" : "occam", + ".odin" : "odin", + ".xom" : "omnimark", + ".xin" : "omnimark", + ".opam" : "opam", + ".or" : "openroad", + ".scad" : "openscad", + ".ora" : "ora", + ".org" : "org", + ".org_archive" : "org", + ".nmconnection": "confini", + ".papp" : "papp", + ".pxml" : "papp", + ".pxsl" : "papp", + ".pas" : "pascal", + ".dpr" : "pascal", + ".lpr" : "pascal", + ".fpc" : "fpcmake", + ".filter" : "poefilter", + ".pdf" : "pdf", + ".pcmk" : "pcmk", + ".plx" : "perl", + ".al" : "perl", + ".psgi" : "perl", + ".pod" : "pod", + ".php" : "php", + ".phtml" : "php", + ".ctp" : "php", + ".phpt" : "php", + ".theme" : "php", + ".pike" : "pike", + ".pmod" : "pike", + ".cmod" : "cmod", + ".rcp" : "pilrc", + ".pli" : "pli", + ".pl1" : "pli", + ".plm" : "plm", + ".p36" : "plm", + ".pac" : "plm", + ".pls" : "plsql", + ".plsql" : "plsql", + ".plp" : "plp", + ".po" : "po", + ".pot" : "po", + ".pony" : "pony", + ".ps" : "postscr", + ".pfa" : "postscr", + ".afm" : "postscr", + ".eps" : "postscr", + ".epsf" : "postscr", + ".epsi" : "postscr", + ".ai" : "postscr", + ".ppd" : "ppd", + ".pov" : "pov", + ".ps1" : "ps1", + ".psd1" : "ps1", + ".psm1" : "ps1", + ".pssc" : "ps1", + ".ps1xml" : "ps1xml", + ".cdxml" : "xml", + ".psc1" : "xml", + ".prisma" : "prisma", + ".g" : "pccts", + ".it" : "ppwiz", + ".ih" : "ppwiz", + ".pug" : "pug", + ".epp" : "epuppet", + ".obj" : "obj", + ".pc" : "proc", + ".action" : "privoxy", + ".psf" : "psf", + ".pdb" : "prolog", + ".pml" : "promela", + ".psl" : "psl", + ".proto" : "proto", + ".pbtxt" : "pbtxt", + ".pk" : "poke", + ".arr" : "pyret", + ".pyx" : "pyrex", + ".pxd" : "pyrex", + ".py" : "python", + ".pyw" : "python", + ".ptl" : "python", + ".pyi" : "python", + ".ql" : "ql", + ".qll" : "ql", + ".qmd" : "quarto", + ".rkt" : "racket", + ".rktd" : "racket", + ".rktl" : "racket", + ".rad" : "radiance", + ".mat" : "radiance", + ".pm6" : "raku", + ".p6" : "raku", + ".t6" : "raku", + ".pod6" : "raku", + ".raku" : "raku", + ".rakumod" : "raku", + ".rakudoc" : "raku", + ".rakutest" : "raku", + ".rib" : "rib", + ".rego" : "rego", + ".rex" : "rexx", + ".orx" : "rexx", + ".rxo" : "rexx", + ".rxj" : "rexx", + ".jrexx" : "rexx", + ".rexxj" : "rexx", + ".rexx" : "rexx", + ".testGroup" : "rexx", + ".testUnit" : "rexx", + ".rd" : "rhelp", + ".Rd" : "rhelp", + ".Rnw" : "rnoweb", + ".rnw" : "rnoweb", + ".Snw" : "rnoweb", + ".snw" : "rnoweb", + ".Rmd" : "rmd", + ".rmd" : "rmd", + ".Smd" : "rmd", + ".smd" : "rmd", + ".rss" : "xml", + ".Rrst" : "rrst", + ".rrst" : "rrst", + ".Srst" : "rrst", + ".srst" : "rrst", + ".remind" : "remind", + ".rem" : "remind", + ".res" : "rescript", + ".resi" : "rescript", + ".rnc" : "rnc", + ".rng" : "rng", + ".rpgle" : "rpgle", + ".rpgleinc" : "rpgle", + ".rpl" : "rpl", + ".robot" : "robot", + ".resource" : "robot", + ".ron" : "ron", + ".rsc" : "routeros", + ".x" : "rpcgen", + ".rst" : "rst", + ".rtf" : "rtf", + ".rb" : "ruby", + ".rbw" : "ruby", + ".gemspec" : "ruby", + ".rbs" : "rbs", + ".ru" : "ruby", + ".builder" : "ruby", + ".rxml" : "ruby", + ".rjs" : "ruby", + ".rant" : "ruby", + ".rake" : "ruby", + ".rs" : "rust", + ".sl" : "slang", + ".sage" : "sage", + ".sas" : "sas", + ".sass" : "sass", + ".sa" : "sather", + ".scala" : "scala", + ".sbt" : "sbt", + ".quark" : "supercollider", + ".sci" : "scilab", + ".sce" : "scilab", + ".scss" : "scss", + ".sd" : "sd", + ".sdl" : "sdl", + ".pr" : "sdl", + ".sed" : "sed", + ".srt" : "srt", + ".ass" : "ssa", + ".ssa" : "ssa", + ".svelte" : "svelte", + ".siv" : "sieve", + ".sieve" : "sieve", + ".zig" : "zig", + ".zir" : "zir", + ".zsh" : "zsh", + ".scm" : "scheme", + ".ss" : "scheme", + ".sld" : "scheme", + ".sexp" : "sexplib", + ".sim" : "simula", + ".sin" : "sinda", + ".s85" : "sinda", + ".sst" : "sisu", + ".ssm" : "sisu", + ".ssi" : "sisu", + "._sst" : "sisu", + ".il" : "skill", + ".ils" : "skill", + ".cdf" : "skill", + ".cdc" : "cdc", + ".score" : "slrnsc", + ".smali" : "smali", + ".st" : "st", + ".tpl" : "smarty", + ".smt" : "smith", + ".smith" : "smith", + ".smithy" : "smithy", + ".sno" : "snobol4", + ".spt" : "snobol4", + ".mib" : "mib", + ".my" : "mib", + ".hog" : "hog", + ".sol" : "solidity", + ".rq" : "sparql", + ".sparql" : "sparql", + ".spec" : "spec", + ".speedup" : "spup", + ".spdata" : "spup", + ".spd" : "spup", + ".ice" : "slice", + ".sln" : "solution", + ".slnf" : "json", + ".sp" : "spice", + ".spice" : "spice", + ".spy" : "spyce", + ".spi" : "spyce", + ".tyb" : "sql", + ".tyc" : "sql", + ".pkb" : "sql", + ".pks" : "sql", + ".sqlj" : "sqlj", + ".prql" : "prql", + ".sqr" : "sqr", + ".sqi" : "sqr", + ".nut" : "squirrel", + ".ipd" : "starlark", + ".star" : "starlark", + ".starlark" : "starlark", + ".ovpn" : "openvpn", + ".ado" : "stata", + ".do" : "stata", + ".imata" : "stata", + ".mata" : "stata", + ".hlp" : "smcl", + ".ihlp" : "smcl", + ".smcl" : "smcl", + ".stp" : "stp", + ".sml" : "sml", + ".cm" : "voscm", + ".swift" : "swift", + ".sdc" : "sdc", + ".svg" : "svg", + ".sface" : "surface", + ".td" : "tablegen", + ".tak" : "tak", + ".tal" : "tal", + ".task" : "taskedit", + ".tcl" : "tcl", + ".tm" : "tcl", + ".tk" : "tcl", + ".itcl" : "tcl", + ".itk" : "tcl", + ".jacl" : "tcl", + ".tl" : "teal", + ".tli" : "tli", + ".slt" : "tsalt", + ".ti" : "terminfo", + ".tfvars" : "terraform-vars", + ".latex" : "tex", + ".sty" : "tex", + ".dtx" : "tex", + ".ltx" : "tex", + ".bbl" : "tex", + ".mkii" : "context", + ".mkiv" : "context", + ".mkvi" : "context", + ".mkxl" : "context", + ".mklx" : "context", + ".texinfo" : "texinfo", + ".texi" : "texinfo", + ".txi" : "texinfo", + ".thrift" : "thrift", + ".tla" : "tla", + ".toml" : "toml", + ".tpp" : "tpp", + ".treetop" : "treetop", + ".tssgm" : "tssgm", + ".tssop" : "tssop", + ".tsscl" : "tsscl", + ".tsv" : "tsv", + ".twig" : "twig", + ".mts" : "typescript", + ".cts" : "typescript", + ".tsx" : "typescriptreact", + ".uit" : "uil", + ".uil" : "uil", + ".ungram" : "ungrammar", + ".uc" : "uc", + ".vala" : "vala", + ".vdf" : "vdf", + ".vdmpp" : "vdmpp", + ".vpp" : "vdmpp", + ".vdmrt" : "vdmrt", + ".vdmsl" : "vdmsl", + ".vdm" : "vdmsl", + ".vr" : "vera", + ".vri" : "vera", + ".vrh" : "vera", + ".va" : "verilogams", + ".vams" : "verilogams", + ".sv" : "systemverilog", + ".svh" : "systemverilog", + ".tape" : "vhs", + ".hdl" : "vhdl", + ".vhd" : "vhdl", + ".vhdl" : "vhdl", + ".vbe" : "vhdl", + ".vst" : "vhdl", + ".vho" : "vhdl", + ".vim" : "vim", + ".vba" : "vim", + ".sba" : "vb", + ".wrl" : "vrml", + ".vroom" : "vroom", + ".vue" : "vue", + ".wat" : "wat", + ".wast" : "wast", + ".wit" : "wit", + ".wm" : "webmacro", + ".wml" : "wml", + ".wbt" : "winbatch", + ".wsml" : "wsml", + ".wpl" : "xml", + ".xhtml" : "xhtml", + ".xht" : "xhtml", + ".xpm2" : "xpm2", + ".xs" : "xs", + ".ad" : "xdefaults", + ".msc" : "xmath", + ".msf" : "xmath", + ".xmi" : "xml", + ".csproj" : "xml", + ".fsproj" : "xml", + ".vbproj" : "xml", + ".ui" : "xml", + ".tpm" : "xml", + ".wsdl" : "xml", + ".wdl" : "wdl", + ".xlf" : "xml", + ".xliff" : "xml", + ".xul" : "xml", + ".xq" : "xquery", + ".xql" : "xquery", + ".xqm" : "xquery", + ".xquery" : "xquery", + ".xqy" : "xquery", + ".xsd" : "xsd", + ".xsl" : "xslt", + ".xslt" : "xslt", + ".yy" : "yacc", + ".yxx" : "yacc", + ".yaml" : "yaml", + ".yml" : "yaml", + ".raml" : "raml", + ".yang" : "yang", + ".yuck" : "yuck", + ".zu" : "zimbu", + ".zut" : "zimbutempl", + ".z8a" : "z8a", + ".text" : "text", + ".usda" : "usd", + ".usd" : "usd", + ".blp" : "blueprint", + ".dm1" : "maxima", + ".dm2" : "maxima", + ".dm3" : "maxima", + ".dmt" : "maxima", + ".dockerfile" : "dockerfile", + ".Dockerfile" : "dockerfile", + ".edf" : "edif", + ".edif" : "edif", + ".edo" : "edif", + ".hs-boot" : "haskell", + ".json-patch" : "json", + ".sub" : "krl", + ".l++" : "lex", + ".opl" : "opl", + ".-sst" : "sisu", + ".-sst.meta" : "sisu", + ".wsf" : "wsh", + ".wsc" : "wsh", + ".y++" : "yacc", + ".ebuild" : "sh", + ".sh" : "sh", + ".bash" : "sh", + ".env" : "sh", + ".eclass" : "sh", + ".ksh" : "ksh", + ".csh" : "csh", + ".tcsh" : "tcsh", + ".xml" : "xml", + ".make" : "make", + ".csproj.user" : "xml", + ".fsproj.user" : "xml", + ".vbproj.user" : "xml", + ".cmake.in" : "cmake", + ".t.html" : "tilde", + ".html.m4" : "htmlm4", + ".upstream.dat" : "upstreamdat", + ".upstream.log" : "upstreamlog", + ".upstreaminstall.log": "upstreaminstalllog", + ".usserver.log" : "usserverlog", + ".usw2kagt.log" : "usw2kagtlog", + ".mli.cppo" : "ocaml", + ".ml.cppo" : "ocaml", + ".opam.template" : "opam", + ".sst.meta" : "sisu", + "._sst.meta" : "sisu", + ".swift.gyb" : "swiftgyb", +} + +name_ft = { + "a2psrc" : "a2ps", + ".a2psrc" : "a2ps", + "build.xml" : "ant", + ".htaccess" : "apache", + "makefile" : "make", + "Makefile" : "make", + "GNUmakefile" : "make", + "makefile.am" : "automake", + "Makefile.am" : "automake", + "GNUmakefile.am" : "automake", + ".asoundrc" : "alsaconf", + "apt.conf" : "aptconf", + ".arch-inventory" : "arch", + "=tagging-method" : "arch", + "maxima-init.mac" : "maxima", + "named.root" : "bindzone", + "WORKSPACE" : "bzl", + "WORKSPACE.bzlmod" : "bzl", + "BUILD" : "bzl", + ".busted" : "lua", + "calendar" : "calendar", + ".cdrdao" : "cdrdaoconf", + "cfengine.conf" : "cfengine", + "changelog.Debian" : "debchangelog", + "changelog.dch" : "debchangelog", + "NEWS.Debian" : "debchangelog", + "NEWS.dch" : "debchangelog", + ".clangd" : "yaml", + ".clang-format" : "yaml", + ".clang-tidy" : "yaml", + "CMakeLists.txt" : "cmake", + "configure.in" : "config", + "configure.ac" : "config", + "Containerfile" : "dockerfile", + "Dockerfile" : "dockerfile", + "dockerfile" : "dockerfile", + "mix.lock" : "elixir", + "lynx.cfg" : "lynx", + "cm3.cfg" : "m3quake", + "m3makefile" : "m3build", + "m3overrides" : "m3build", + "denyhosts.conf" : "denyhosts", + "dict.conf" : "dictconf", + ".dictrc" : "dictconf", + ".dir_colors" : "dircolors", + ".dircolors" : "dircolors", + "jbuild" : "dune", + "dune" : "dune", + "dune-project" : "dune", + "dune-workspace" : "dune", + ".editorconfig" : "editorconfig", + "elinks.conf" : "elinks", + "filter-rules" : "elmfilt", + "exim.conf" : "exim", + "exports" : "exports", + ".fetchmailrc" : "fetchmail", + "auto.master" : "conf", + "fstab" : "fstab", + "mtab" : "fstab", + ".gdbinit" : "gdb", + "gdbinit" : "gdb", + ".gdbearlyinit" : "gdb", + "gdbearlyinit" : "gdb", + "lltxxxxx.txt" : "gedcom", + "COMMIT_EDITMSG" : "gitcommit", + "MERGE_MSG" : "gitcommit", + "TAG_EDITMSG" : "gitcommit", + "NOTES_EDITMSG" : "gitcommit", + "EDIT_DESCRIPTION" : "gitcommit", + ".gitconfig" : "gitconfig", + ".gitmodules" : "gitconfig", + ".gitattributes" : "gitattributes", + ".gitignore" : "gitignore", + "git-rebase-todo" : "gitrebase", + "gkrellmrc" : "gkrellmrc", + ".gprc" : "gp", + "gnashrc" : "gnash", + ".gnashrc" : "gnash", + "gnashpluginrc" : "gnash", + ".gnashpluginrc" : "gnash", + "gitolite.conf" : "gitolite", + ".gitolite.rc" : "perl", + "example.gitolite.rc": "perl", + ".gnuplot" : "gnuplot", + "Gopkg.lock" : "toml", + "go.work" : "gowork", + "Jenkinsfile" : "groovy", + ".gtkrc" : "gtkrc", + "gtkrc" : "gtkrc", + "cabal.project" : "cabalproject", + "cabal.config" : "cabalconfig", + "go.sum" : "gosum", + "go.work.sum" : "gosum", + ".indent.pro" : "indent", + "indentrc" : "indent", + "upstream.dat" : "upstreamdat", + "fdrupstream.log" : "upstreamlog", + "upstream.log" : "upstreamlog", + "upstreaminstall.log": "upstreaminstalllog", + "usserver.log" : "usserverlog", + "usw2kagt.log" : "usw2kagtlog", + "ipf.conf" : "ipfilter", + "ipf6.conf" : "ipfilter", + "ipf.rules" : "ipfilter", + "inittab" : "inittab", + ".prettierrc" : "json", + ".firebaserc" : "json", + ".stylelintrc" : "json", + ".babelrc" : "jsonc", + ".eslintrc" : "jsonc", + ".jsfmtrc" : "jsonc", + ".jshintrc" : "jsonc", + ".hintrc" : "jsonc", + ".swrc" : "jsonc", + "Kconfig" : "kconfig", + "Kconfig.debug" : "kconfig", + ".latexmkrc" : "perl", + "latexmkrc" : "perl", + "lftp.conf" : "lftp", + ".lftprc" : "lftp", + "lilo.conf" : "lilo", + ".emacs" : "lisp", + ".sawfishrc" : "lisp", + "sbclrc" : "lisp", + ".sbclrc" : "lisp", + ".luacheckrc" : "lua", + ".letter" : "mail", + ".followup" : "mail", + ".article" : "mail", + ".mailcap" : "mailcap", + "mailcap" : "mailcap", + "man.config" : "manconf", + "meson.build" : "meson", + "meson_options.txt" : "meson", + "mplayer.conf" : "mplayerconf", + "mrxvtrc" : "mrxvtrc", + ".mrxvtrc" : "mrxvtrc", + "tclsh.rc" : "tcl", + "Neomuttrc" : "neomuttrc", + ".netrc" : "netrc", + "npmrc" : "dosini", + ".npmrc" : "dosini", + "env.nu" : "nu", + "config.nu" : "nu", + ".ocamlinit" : "ocaml", + "octave.conf" : "octave", + ".octaverc" : "octave", + "octaverc" : "octave", + "opam" : "opam", + "pf.conf" : "pf", + "mpv.conf" : "confini", + "pam_env.conf" : "pamenv", + ".pam_environment" : "pamenv", + ".pinerc" : "pine", + "pinerc" : "pine", + ".pinercex" : "pine", + "pinercex" : "pine", + "Pipfile" : "toml", + "Pipfile.lock" : "json", + "main.cf" : "pfmain", + "main.cf.proto" : "pfmain", + ".povrayrc" : "povini", + "Puppetfile" : "ruby", + ".procmail" : "procmail", + ".procmailrc" : "procmail", + ".pythonstartup" : "python", + ".pythonrc" : "python", + "SConstruct" : "python", + "qmldir" : "qmldir", + ".ratpoisonrc" : "ratpoison", + "ratpoisonrc" : "ratpoison", + ".inputrc" : "readline", + "inputrc" : "readline", + ".Rprofile" : "r", + "Rprofile" : "r", + "Rprofile.site" : "r", + ".reminders" : "remind", + "resolv.conf" : "resolv", + "robots.txt" : "robots", + ".irbrc" : "ruby", + "irbrc" : "ruby", + "Gemfile" : "ruby", + "rantfile" : "ruby", + "Rantfile" : "ruby", + "rakefile" : "ruby", + "Rakefile" : "ruby", + "Cargo.lock" : "toml", + "smb.conf" : "samba", + "sendmail.cf" : "sm", + "catalog" : "catalog", + ".zprofile" : "zsh", + ".zfbfmarks" : "zsh", + ".zshrc" : "zsh", + ".zshenv" : "zsh", + ".zlogin" : "zsh", + ".zlogout" : "zsh", + ".zcompdump" : "zsh", + ".screenrc" : "screen", + "screenrc" : "screen", + ".slrnrc" : "slrnrc", + "snort.conf" : "hog", + "vision.conf" : "hog", + "squid.conf" : "squid", + "ssh_config" : "sshconfig", + "sshd_config" : "sshdconfig", + "sudoers.tmp" : "sudoers", + "tags" : "tags", + "pending.data" : "taskdata", + "completed.data" : "taskdata", + "undo.data" : "taskdata", + ".tclshrc" : "tcl", + ".wishrc" : "tcl", + "texmf.cnf" : "texmf", + ".tidyrc" : "tidy", + "tidyrc" : "tidy", + "tidy.conf" : "tidy", + ".tfrc" : "tf", + "tfrc" : "tf", + "trustees.conf" : "trustees", + "Vagrantfile" : "ruby", + ".vimrc" : "vim", + "vimrc" : "vim", + "gvimrc" : "vim", + "_vimrc" : "vim", + ".exrc" : "vim", + "_exrc" : "vim", + ".viminfo" : "viminfo", + "_viminfo" : "viminfo", + "vgrindefs" : "vgrindefs", + ".wgetrc" : "wget", + "wgetrc" : "wget", + ".wget2rc" : "wget2", + "wget2rc" : "wget2", + "wvdial.conf" : "wvdial", + ".wvdialrc" : "wvdial", + ".cvsrc" : "cvsrc", + ".Xdefaults" : "xdefaults", + ".Xpdefaults" : "xdefaults", + ".Xresources" : "xdefaults", + "xdm-config" : "xdefaults", + "fglrxrc" : "xml", + "README" : "text", + "LICENSE" : "text", + "COPYING" : "text", + "AUTHORS" : "text", + ".bashrc" : "sh", + "bashrc" : "sh", + "bash.bashrc" : "sh", + ".bash_profile" : "sh", + ".bash-profile" : "sh", + ".bash_logout" : "sh", + ".bash-logout" : "sh", + ".bash_aliases" : "sh", + ".bash-aliases" : "sh", + ".profile" : "sh", + "bash-fc-" : "sh", + "bash-fc." : "sh", + ".kshrc" : "ksh", + ".tcshrc" : "tcsh", + "tcsh.tcshrc" : "tcsh", + "tcsh.login" : "tcsh", + ".login" : "csh", + ".cshrc" : "csh", + "csh.cshrc" : "csh", + "csh.login" : "csh", + "csh.logout" : "csh", + ".alias" : "csh", + ".kshrc" : "ksh", + ".zshrc" : "zsh", +} + +def getExtension(path): + basename = os.path.basename(path) + if basename in name_ft: + return name_ft[basename] + else: + root, ext = os.path.splitext(basename) + if ext == '': + return None + + _, ext2 = os.path.splitext(root) + if ext2 != '': + extension = ext2 + ext + if extension in extension_ft: + return extension_ft[extension] + + if ext == '.txt' and os.path.dirname(path) == lfEval("expand('$VIMRUNTIME/doc')"): + return "help" + + return extension_ft.get(ext, None) + +def ignoreEvent(events): + def wrapper(func): + @wraps(func) + def deco(self, *args, **kwargs): + try: + saved_eventignore = vim.options['eventignore'] + vim.options['eventignore'] = events + + return func(self, *args, **kwargs) + finally: + vim.options['eventignore'] = saved_eventignore + return deco + return wrapper + +def printTime(func): + @wraps(func) + def deco(self, *args, **kwargs): + try: + start = time.time() + return func(self, *args, **kwargs) + finally: + print(func.__name__, time.time() - start) + + return deco + +def tabmove(): + 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") diff --git a/autoload/lfMru.vim b/autoload/lfMru.vim index 9728462a..98888087 100644 --- a/autoload/lfMru.vim +++ b/autoload/lfMru.vim @@ -13,11 +13,11 @@ if !isdirectory(g:Lf_CacheDirectory) call mkdir(g:Lf_CacheDirectory, "p") endif -if !isdirectory(g:Lf_CacheDirectory . '/.LfCache') - call mkdir(g:Lf_CacheDirectory . '/.LfCache', "p") +if !isdirectory(g:Lf_CacheDirectory . '/LeaderF') + call mkdir(g:Lf_CacheDirectory . '/LeaderF', "p") endif -let g:Lf_MruCacheFileName = g:Lf_CacheDirectory . '/.LfCache/tempMru' +let g:Lf_MruCacheFileName = g:Lf_CacheDirectory . '/LeaderF/frecency' if !filereadable(g:Lf_MruCacheFileName) call writefile([], g:Lf_MruCacheFileName) @@ -31,13 +31,26 @@ function! lfMru#record(name) if a:name == '' || !filereadable(a:name) || strpart(a:name, 0, 2) == '\\' return endif + let file_list = readfile(g:Lf_MruCacheFileName) - if empty(file_list) - call writefile([a:name], g:Lf_MruCacheFileName) - elseif a:name != file_list[0] - call filter(file_list, 'v:val != a:name') - call writefile([a:name] + file_list, g:Lf_MruCacheFileName) + let found = 0 + let i = 0 + for item in file_list + let t = split(item, ' ') + if (len(t) > 2) && (t[2] ==# a:name) + let found = 1 + let t[1] += 1 + let file_list[i] = printf("%s %s %s", localtime(), t[1], t[2]) + break + endif + let i += 1 + endfor + + if found == 0 + call add(file_list, printf("%s 1 %s", localtime(), a:name)) endif + + call writefile(file_list, g:Lf_MruCacheFileName) endfunction function! lfMru#recordBuffer(bufNum) diff --git a/doc/leaderf.txt b/doc/leaderf.txt index fd161cec..e2695906 100644 --- a/doc/leaderf.txt +++ b/doc/leaderf.txt @@ -59,11 +59,11 @@ g:Lf_WindowPosition *g:Lf_WindowPosition* Setting this option can change the position of the LeaderF window. The value can be 'fullScreen', 'top', 'bottom', 'left', 'right'. 'fullScreen' - the LeaderF window take up the full screen - 'top' - the LeaderF window is at the top of the screen. + 'top' - the LeaderF window is at the top of the screen. 'bottom' - the LeaderF window is at the bottom of the screen. - 'left' - the LeaderF window is at the left of the screen. - 'right' - the LeaderF window is at the right of the screen. - 'popup' - the LeaderF window is a popup window or floating window. + 'left' - the LeaderF window is at the left of the screen. + 'right' - the LeaderF window is at the right of the screen. + 'popup' - the LeaderF window is a popup window or floating window. Default value is 'bottom'. g:Lf_WindowHeight *g:Lf_WindowHeight* @@ -76,13 +76,13 @@ g:Lf_WindowHeight *g:Lf_WindowHeight* Default value is 0.5. g:Lf_TabpagePosition *g:Lf_TabpagePosition* - Specify where to put the newly opened tab page. + Specify where to put the newly opened tabpage. The value can be 0, 1, 2, ... - 0 - make the newly opened tab page the first one. - 1 - put the newly opened tab page before the current one. - 2 - put the newly opened tab page after the current one. - 3 - make the newly opened tab page the last one. - Default value is 2. + 0 - make the newly opened tabpage the first one. + 1 - put the newly opened tabpage before the current one. + 2 - put the newly opened tabpage after the current one. + 3 - make the newly opened tabpage the last one. + Default value is 3. g:Lf_ShowRelativePath *g:Lf_ShowRelativePath* Whether to show the relative path in the LeaderF window. @@ -108,7 +108,9 @@ g:Lf_CacheDirectory *g:Lf_CacheDirectory* e.g. > let g:Lf_CacheDirectory = '/root' < - Default value is $HOME. + On Windows, default value is '%APPDATA%'. + On Linux, default value is '$XDG_CACHE_HOME' if $XDG_CACHE_HOME is defined, + otherwise, default value is '$HOME/.cache'. g:Lf_NeedCacheTime *g:Lf_NeedCacheTime* This option set a threshold, if the time of indexing files is greater than @@ -416,21 +418,25 @@ g:Lf_PreviewResult *g:Lf_PreviewResult* This is a dictionary, indicates whether to enable the preview of the result. Default value is: > let g:Lf_PreviewResult = { - \ 'File': 0, - \ 'Buffer': 0, - \ 'Mru': 0, - \ 'Tag': 0, + \ 'File': 1, + \ 'Buffer': 1, + \ 'Mru': 1, + \ 'Tag': 1, \ 'BufTag': 1, \ 'Function': 1, - \ 'Line': 0, - \ 'Colorscheme': 0, - \ 'Rg': 0, - \ 'Gtags': 0 + \ 'Line': 1, + \ 'Colorscheme': 1, + \ 'Rg': 1, + \ 'Gtags': 1 \} < e.g., if you want to disable the preview of BufTag explorer: > let g:Lf_PreviewResult = { 'BufTag': 0 } < + You can also specify `--auto-preview` or `--no-auto-preview` for the specific + command to override this option. + e.g., `Leaderf file --no-auto-preview` + g:Lf_RememberLastSearch *g:Lf_RememberLastSearch* This option specifies whether to remember the last search. If the value is 1, the search string you typed during last search is still there when @@ -445,6 +451,8 @@ g:Lf_UseCache *g:Lf_UseCache* Default value is 1. g:Lf_NormalMap *g:Lf_NormalMap* + This option is deprecated in favor of |g:Lf_NormalCommandMap|. + Use this option to customize the mappings in normal mode. The mapping with the key `"_"` applies to all categories. Also, `"_"` is overridden by the mapping of each category. @@ -498,9 +506,9 @@ g:Lf_WorkingDirectoryMode *g:Lf_WorkingDirectoryMode* g:Lf_WorkingDirectory *g:Lf_WorkingDirectory* Set LeaderF's working directory. It will ignore the |g:Lf_RootMarkers| and - |g:Lf_WorkingDirectoryMode| option set. - e.g., > - let g:Lf_WorkingDirectory = finddir('.git', '.;') + |g:Lf_WorkingDirectoryMode| option set. + e.g., > + let g:Lf_WorkingDirectory = finddir('.git', '.;') < g:Lf_CommandMap *g:Lf_CommandMap* Use this option to customize the mappings inside LeaderF's @@ -509,6 +517,41 @@ g:Lf_CommandMap *g:Lf_CommandMap* change the default command to and : > let g:Lf_CommandMap = {'': [''], '': ['', '']} < + Note: The key of the dictionary is the old command, the value of the + dictionary is the new command. + +g:Lf_NormalCommandMap *g:Lf_NormalCommandMap* + Use this option to customize the mappings in normal mode. + The mapping with the key `"*"` applies to all categories. + Also, `"*"` is overridden by the mapping of each category. + + Note: The key of the dictionary is the old command, the value of the + dictionary is the new command. + + e.g., > + let g:Lf_NormalCommandMap = { + \ "*": { + \ "": "", + \ "": "" + \ }, + \ "File": { + \ "q": "", + \ "a": "", + \ "": "", + \ }, + \ "Buffer": {}, + \ "Mru": {}, + \ "Tag": {}, + \ "BufTag": {}, + \ "Function": {}, + \ "Line": {}, + \ "History":{}, + \ "Help": {}, + \ "Rg": {}, + \ "Gtags": {}, + \ "Colorscheme": {} + \} + g:Lf_HideHelp *g:Lf_HideHelp* To specify whether to show the hint "Press for help" in normal mode. You can set the value to 1 to hide the hint. @@ -583,7 +626,7 @@ g:Lf_RgConfig *g:Lf_RgConfig* Specify a list of ripgrep configurations. For example, > let g:Lf_RgConfig = [ \ "--max-columns=150", - \ "--type-add web:*.{html,css,js}*", + \ '--type-add "web:*.{html,css,js}*"', \ "--glob=!git/*", \ "--hidden" \ ] @@ -622,6 +665,18 @@ g:Lf_NoChdir *g:Lf_NoChdir* to `dir`. Default value is 1. +g:Lf_Global *g:Lf_Global* + Use this option to customize the global executable to use. e.g., > + let g:Lf_Global = "/bin/global" +< + Default value is "global". + +g:Lf_Gtags *g:Lf_Gtags* + Use this option to customize the global executable to use. e.g., > + let g:Lf_Gtags = "/bin/gtags" +< + Default value is "gtags". + g:Lf_GtagsAutoGenerate *g:Lf_GtagsAutoGenerate* If the value is 1 and there is a rootmark defined by |g:Lf_RootMarkers| under the project root directory, gtags files will be generated @@ -630,12 +685,12 @@ g:Lf_GtagsAutoGenerate *g:Lf_GtagsAutoGenerate* generated manually by using `Leaderf gtags --update` . Default value is 0. -g:Lf_GtagsGutentags *g:Lf_GtagsGutentags* - if you use https://github.com/ludovicchabant/vim-gutentags to generate - gtags; Firstly, you should let g:Lf_GtagsAutoGenerate = 0 and let g:Lf_GtagsGutentags = 1. - Then, you should config gutentags like this: g:Lf_CacheDirectory = - expand('~') and g:gutentags_cache_dir = expand(g:Lf_CacheDirectory.'/.LfCache/gtags') - Default value is 0 +g:Lf_GtagsGutentags *g:Lf_GtagsGutentags* + if you use https://github.com/ludovicchabant/vim-gutentags to generate + gtags; Firstly, you should let g:Lf_GtagsAutoGenerate = 0 and let g:Lf_GtagsGutentags = 1. + Then, you should config gutentags like this: g:Lf_CacheDirectory = expand('~') + and g:gutentags_cache_dir = expand(g:Lf_CacheDirectory.'/LeaderF/gtags') + Default value is 0 g:Lf_GtagsAutoUpdate *g:Lf_GtagsAutoUpdate* If the value is 1 and the gtags database already exists, gtags database @@ -729,9 +784,13 @@ g:Lf_IgnoreCurrentBufferName *g:Lf_IgnoreCurrentBufferName* g:Lf_PreviewInPopup *g:Lf_PreviewInPopup* This option specifies whether to preview the result in a popup window. - Default value is 0. + Default value is 1. g:Lf_PreviewHorizontalPosition *g:Lf_PreviewHorizontalPosition* + [[This option is deprecated. Use |g:Lf_PreviewPosition| instead.]] + [[This option is deprecated. Use |g:Lf_PreviewPosition| instead.]] + [[This option is deprecated. Use |g:Lf_PreviewPosition| instead.]] + Specify where to locate the preview window horizontally. The value can be one of the following: 'left': the preview window is on the left side of the screen. @@ -742,11 +801,17 @@ g:Lf_PreviewHorizontalPosition *g:Lf_PreviewHorizontalPosition* Default value is 'right'. g:Lf_PreviewPopupWidth *g:Lf_PreviewPopupWidth* - Specify the width of preview popup window, the value should be a non-negative - integer. If the value is 0, the width is half of the screen's width. + Specify the width of preview window, the value should be a non-negative + integer. If the value is 0, the width of preview window is the same as the + width of the main LeaderF window. Default value is 0. +g:Lf_PreviewScrollStepSize *g:Lf_PreviewScrollStepSize* + Specify how many rows will be scrolled at once in the preview window. + + Default value is 1. + g:Lf_PopupWidth *g:Lf_PopupWidth* Specify the width of the popup window or floating window when LeaderF is in popup mode. `Popup mode` is when |g:Lf_WindowPosition| is 'popup' or '--popup' @@ -801,14 +866,28 @@ g:Lf_PopupShowStatusline *g:Lf_PopupShowStatusline* Default value is 1. g:Lf_PopupPreviewPosition *g:Lf_PopupPreviewPosition* - Specify where to locate the preview window when LeaderF is in popup mode. + Specify where to place the preview window when LeaderF is in popup mode. `Popup mode` is when |g:Lf_WindowPosition| is 'popup' or '--popup' is in the options list of `Leaderf` command. The value can be one of the following: 'top': the preview window is on the top of the main LeaderF window. 'bottom': the preview window is at the bottom of the main LeaderF window. - 'cursor': the preview window is at the cursor position in the main LeaderF - window. + 'left': the preview window is on the left of the main LeaderF window. + 'right': the preview window is on the right of the main LeaderF window. + + Default value is 'right'. + +g:Lf_PreviewPosition *g:Lf_PreviewPosition* + Specify where to place the preview window when LeaderF is in non-popup mode. + + The value can be one of the following: + 'top': the preview window is on the top of the main LeaderF window. + 'topleft': the preview window is on the topleft of the main LeaderF window. + 'topright': the preview window is on the topright of the main LeaderF window. + 'right': the preview window is on the right of the main LeaderF window. + 'bottom': the preview window is at the bottom of the main LeaderF window. + (only available when LeaderF window is on the top) + 'cursor': the preview window is at the cursorline of the main LeaderF window. Default value is 'top'. @@ -867,6 +946,33 @@ g:Lf_PopupShowFoldcolumn *g:Lf_PopupShowFoldcolumn* Default value is 1. +g:Lf_PopupAutoAdjustHeight *g:Lf_PopupAutoAdjustHeight* + Specifies whether to adjust the height of popup window automatically. + + Default value is 1. + +g:Lf_PopupShowBorder *g:Lf_PopupShowBorder* + Specifies whether to show borders for popup window. + + Default value is 1. + +g:Lf_PopupBorders *g:Lf_PopupBorders* + Specifies a list of characters, defining the character to use for the + top/right/bottom/left border, followed by the character to use for the + topleft/topright/botright/botleft corner. + Example: > + let g:Lf_PopupBorders = ["─","│","─","│","╭","╮","╯","╰"] + let g:Lf_PopupBorders = ["─","│","─","│","┌","┐","┘","└"] + let g:Lf_PopupBorders = ["━","┃","━","┃","┏","┓","┛","┗"] + let g:Lf_PopupBorders = ["═","║","═","║","╔","╗","╝","╚"] +< + For some terminals, the non-ascii characters can not be displayed + properly for borders, see https://github.com/neovim/neovim/issues/17996. + To have a good display for non-ascii characters, perhaps you need to set + the value of |ambiwidth| as "single". + + Default value is ["─","│","─","│","╭","╮","╯","╰"]. + g:Lf_AutoResize *g:Lf_AutoResize* Auto resize the LeaderF window height automatically. @@ -960,6 +1066,172 @@ g:Lf_SpinSymbols *g:Lf_SpinSymbols* For Linux platform, the default value is ['△', '▲', '▷', '▶', '▽', '▼', '◁', '◀'], for Windows and MacOS, the default value is ['🌘', '🌗', '🌖', '🌕', '🌔', '🌓', '🌒', '🌑'] +g:Lf_FileActions *g:Lf_FileActions* + Specify the command to handle the user seleted file instead the default + vim edit command. + + e.g.: > + " _vimrc or .vimrc adding the following code + let g:Lf_FileActions = { '.pdf': '!start' + \,'.doc':'!start' + \,'.docx':'!start' + \,'.odt':'!start' + \,'.xlsx':'!start' + \,'.jpg':'!start' + \} +< + then if the user select "da\db\c.pdf", leaderf will execute command as following: > + :!start da\db\c.pdf +< + to open "da\db\c.pdf" with pdfreader in Windows OS. + +g:Lf_MruEnable *g:Lf_MruEnable* + Enable `Leaderf mru` or not. + Default value is 1. + +g:Lf_MruEnableFrecency *g:Lf_MruEnableFrecency* + This option specifies whether `Leaderf mru` uses frecency algorithm. + `--frecency` can override this option. + Default value is 0. + +g:Lf_QuickSelect *g:Lf_QuickSelect* + Enable or disable quick-select mode. If enabled, the characters [0-9] are not + treated as literal characters, but are used to select the [0-9]th entry. 0 + means the 10th entry. + NOTE: Appending '--quick-select' to the arguments list of the `Leaderf` command + will override this option. + + Default value is 0. + +g:Lf_QuickSelectAction *g:Lf_QuickSelectAction* + This option specifies what action to be taken when quick-select mode is enabled + and an entry is selected. + + The value can be one of the following: + '': do nothing. + 'c': open the selected entry in the current window. + 'h': open the selected entry in a horizontally split window. + 'v': open the selected entry in a vertically split window. + 't': open the selected entry in a new tabpage. + + Default value is 'c'. + +g:Lf_SpacesAfterIcon *g:Lf_SpacesAfterIcon* + Specifies the spaces after the icon if |g:Lf_ShowDevIcons| is 1. + + Default value is ' '. + +g:Lf_EnableCircularScroll *g:Lf_EnableCircularScroll* + Enable circular scroll in the result window. + + Default value is 0. + +g:Lf_GitCommands *g:Lf_GitCommands* + Define a list of commands you may want to use frequently. + The list is as follows: > + let g:Lf_GitCommands = [ + \ {"Leaderf git diff": "fuzzy search and view the diffs"}, + \ {"Leaderf git diff --cached": "fuzzy search and view `git diff --cached`"}, + \ {"Leaderf git diff HEAD": "fuzzy search and view `git diff HEAD`"}, + \ {"Leaderf git diff --side-by-side": "fuzzy search and view the side-by-side diffs"}, + \ ] +< + Open the commands panel using `:Leaderf git` . + +g:Lf_GitFolderIcons *g:Lf_GitFolderIcons* + To customize the folder icons in tree panel. + Default value is { 'open': '', 'closed': '' }. + +g:Lf_GitAddIcon *g:Lf_GitAddIcon* + To customize the icon of added file in tree panel. + Default value is ''. + +g:Lf_GitCopyIcon *g:Lf_GitCopyIcon* + To customize the icon of copied file in tree panel. + Default value is ''. + +g:Lf_GitDelIcon *g:Lf_GitDelIcon* + To customize the icon of deleted file in tree panel. + Default value is ''. + +g:Lf_GitModifyIcon *g:Lf_GitModifyIcon* + To customize the icon of modified file in tree panel. + Default value is ''. + +g:Lf_GitRenameIcon *g:Lf_GitRenameIcon* + To customize the icon of renamed file in tree panel. + Default value is ''. + +g:Lf_GitNavigationPanelHeight *g:Lf_GitNavigationPanelHeight* + To customize the height of navigation(tree) panel. + Default value is &lines * 0.3. + +g:Lf_GitNavigationPanelWidth *g:Lf_GitNavigationPanelWidth* + To customize the width of navigation(tree) panel. + Default value is 44. + +g:Lf_GitContextNum *g:Lf_GitContextNum* + Show diffs with g:Lf_GitContextNum lines of context. + Default value is 6. + +g:Lf_GitDiffViewMode *g:Lf_GitDiffViewMode* + Show diffs in a side by side view or in a unified view. + Value can be: 'side-by-side' or 'unified'. + Default value is 'unified'. + +g:Lf_GitHightlightLineNumber *g:Lf_GitHightlightLineNumber* + Specify whether to highlight the line number when diff view is in + 'unified' mode. If enabled, the line numbers of changed lines are also + highlighted with 'DiffAdd' or 'DiffDelete'. + Default value is 1. + +g:Lf_GitWorkingDirectoryMode *g:Lf_GitWorkingDirectoryMode* + Specify the working directory when launch Leaderf git command. The value + can be: + c - the nearest ancestor directory of current working directory that + contains '.git'. + f - the nearest ancestor directory of current file that contains '.git'. + Fall back to 'c' if no such ancestor directory found. + + Default value is 'f'. + +g:Lf_GitKeyMap *g:Lf_GitKeyMap* + Customize the shortcut keys. + Default value is : > + let g:Lf_GitKeyMap = { + \ 'previous_change': '[c', + \ 'next_change': ']c', + \ 'edit_file': '', + \ } +< + it means you can use [c to jump backwards to the previous start of a change + and use ]c to jump forwards to the next start of a change. + +g:Lf_GitInlineBlameEnable *g:Lf_GitInlineBlameEnable* + Whether to enable inline blame or not by default. + + Default value is 0. + +g:Lf_GitBlameTimeFormat *g:Lf_GitBlameTimeFormat* + Specify a format string to represent the date and time, the format string + is the same as used by strftime(), for example, '%Y-%m-%d %H:%M:%S'. + + Default value is "". + +g:Lf_CocCommands *g:Lf_CocCommands* + Define a list of commands you may want to use frequently. + The list is as follows: > + let g:Lf_CocCommands = [ + \ {"Leaderf! coc definitions --auto-jump": "run CocAction('jumpDefinition')"}, + \ {"Leaderf! coc references --auto-jump": "run CocAction('jumpReferences')"}, + \ {"Leaderf! coc references --auto-jump --exclude-declaration": "run CocAction('jumpUsed')"}, + \ {"Leaderf! coc declarations --auto-jump": "run CocAction('jumpDeclaration')"}, + \ {"Leaderf! coc implementations --auto-jump": "run CocAction('jumpImplementation')"}, + \ {"Leaderf! coc typeDefinitions --auto-jump": "run CocAction('jumpTypeDefinition')"}, + \ ] +< + Open the commands panel using `:Leaderf coc` . + ============================================================================== USAGE *leaderf-usage* @@ -1035,6 +1307,29 @@ USAGE *leaderf-usage* :LeaderfRgRecall *LeaderfRgRecall* Recall last search of rg. +:LeaderfGit *LeaderfGit* + List some frequently used commands of Leaderf git. + +:LeaderfGitSplitDiff *LeaderfGitSplitDiff* + Show the diffs of current file in a vertical split window. + It is a shortcut of `:Leaderf git diff --current-file --side-by-side` + +:LeaderfGitNavigationOpen *LeaderfGitNavigationOpen* + Open the navigation panel. + +:LeaderfGitInlineBlameEnable *LeaderfGitInlineBlameEnable* + Enable inline blame. This command is a shortcut of `:Leaderf git blame --inline` + +:LeaderfGitInlineBlameDisable *LeaderfGitInlineBlameDisable* + Disable inline blame. + +:LeaderfGitInlineBlameToggle *LeaderfGitInlineBlameToggle* + Toggle inline blame. + +:LeaderfGitInlineBlameUpdate *LeaderfGitInlineBlameUpdate* + If the file is updated in the git repository, we need to use this command + to update the inline blame. + Some handy maps for `Leaderf rg`: | Map | Description diff --git a/install.bat b/install.bat index 495cc796..47d8f3c4 100644 --- a/install.bat +++ b/install.bat @@ -1,4 +1,8 @@ @echo off +set pyFlag=0 +where /Q py +if %ERRORLEVEL% NEQ 0 set pyFlag=1 + if /i "%1" equ "--reverse" ( cd autoload\leaderf\fuzzyMatch_C rd /s /q build @@ -9,33 +13,33 @@ if /i "%1" equ "--reverse" ( echo ====================================== goto end ) -echo Begin to compile C extension of Python2 ... +echo Beginning to compile C extension of Python2 ... cd autoload\leaderf\fuzzyMatch_C -py -2 setup.py build -if %errorlevel% neq 0 goto second -pushd build\lib*2.* -xcopy /y *.pyd ..\..\..\python\ + +if %pyFlag% EQU 0 ( + py -2 setup.py build --build-lib ..\python +) else ( + python2 setup.py build --build-lib ..\python +) if %errorlevel% equ 0 ( echo= echo =============================================== echo C extension of Python2 installed sucessfully! echo =============================================== ) -popd -:second echo= -echo Begin to compile C extension of Python3 ... -py -3 setup.py build -if %errorlevel% neq 0 goto end -pushd build\lib*3.* -xcopy /y *.pyd ..\..\..\python\ +echo Beginning to compile C extension of Python3 ... +if %pyFlag% EQU 0 ( + py -3 setup.py build --build-lib ..\python +) else ( + python3 setup.py build --build-lib ..\python +) if %errorlevel% equ 0 ( echo= echo =============================================== echo C extension of Python3 installed sucessfully! echo =============================================== ) -popd :end diff --git a/install.sh b/install.sh index 43f75ec5..64f2e91b 100755 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ then rm -rf build rm -f ../python/*.so ../python/*.dll echo ======================================== - echo ^_^ C extension uninstalled sucessfully! + echo ^_^ C extension uninstalled successfully! echo ======================================== exit 0 fi @@ -15,20 +15,22 @@ no_python=true cd autoload/leaderf/fuzzyMatch_C +if command -v gcc > /dev/null 2>&1; then + export CC=gcc +elif command -v clang > /dev/null 2>&1; then + export CC=clang +fi + if command -v python2 > /dev/null 2>&1; then no_python=false - echo "Begin to compile C extension of Python2 ..." - python2 setup.py build + echo "Beginning to compile C extension of Python2 ..." + python2 setup.py build --build-lib ../python if [ $? -eq 0 ] then - cp build/lib*2.*/* ../python - if [ $? -eq 0 ] - then - echo - echo ================================================= - echo ^_^ C extension of Python2 installed sucessfully! - echo ================================================= - fi + echo + echo ================================================= + echo ^_^ C extension of Python2 installed successfully! + echo ================================================= fi fi @@ -36,18 +38,14 @@ if command -v python3 > /dev/null 2>&1; then $no_python || echo no_python=false - echo "Begin to compile C extension of Python3 ..." - python3 setup.py build + echo "Beginning to compile C extension of Python3 ..." + python3 setup.py build --build-lib ../python if [ $? -eq 0 ] then - cp build/lib*3.*/* ../python - if [ $? -eq 0 ] - then - echo - echo ================================================= - echo ^_^ C extension of Python3 installed sucessfully! - echo ================================================= - fi + echo + echo ================================================= + echo ^_^ C extension of Python3 installed successfully! + echo ================================================= fi fi diff --git a/plugin/leaderf.vim b/plugin/leaderf.vim index 891be636..4a13e8a0 100644 --- a/plugin/leaderf.vim +++ b/plugin/leaderf.vim @@ -32,11 +32,23 @@ endfunction call s:InitVar('g:Lf_ShortcutF', 'f') call s:InitVar('g:Lf_ShortcutB', 'b') call s:InitVar('g:Lf_WindowPosition', 'bottom') -call s:InitVar('g:Lf_CacheDirectory', $HOME) call s:InitVar('g:Lf_MruBufnrs', []) call s:InitVar('g:Lf_PythonExtensions', {}) call s:InitVar('g:Lf_PreviewWindowID', {}) +if has('win32') || has('win64') + let s:cache_dir = $APPDATA + if s:cache_dir == '' + let s:cache_dir = $HOME + endif +else + let s:cache_dir = $XDG_CACHE_HOME + if s:cache_dir == '' + let s:cache_dir = $HOME . '/.cache' + endif +endif +call s:InitVar('g:Lf_CacheDirectory', s:cache_dir) + function! g:LfNoErrMsgMatch(expr, pat) try return match(a:expr, a:pat) @@ -107,10 +119,12 @@ function! s:Normalize(filename) endif endfunction -augroup LeaderF_Mru - autocmd BufAdd,BufEnter,BufWritePost * call lfMru#record(s:Normalize(expand(':p'))) | - \ call lfMru#recordBuffer(expand('')) -augroup END +if get(g:, 'Lf_MruEnable', 1) == 1 + augroup LeaderF_Mru + autocmd BufEnter,BufWritePost * call lfMru#record(s:Normalize(expand(':p'))) | + \ call lfMru#recordBuffer(expand('')) + augroup END +endif augroup LeaderF_Gtags autocmd! @@ -122,6 +136,13 @@ augroup LeaderF_Gtags endif augroup END +if get(g:, 'Lf_GitInlineBlameEnable', 0) == 1 + augroup Lf_Git_Blame + autocmd! BufRead * silent call leaderf#Git#StartInlineBlame() + autocmd! BufWinEnter * silent call leaderf#Git#StartInlineBlame() + augroup END +endif + noremap LeaderfFileTop :Leaderf file --top noremap LeaderfFileBottom :Leaderf file --bottom noremap LeaderfFileLeft :Leaderf file --left @@ -152,7 +173,7 @@ noremap LeaderfRgBangCwordRegexNoBoundary :=leaderf#Rg#startCm noremap LeaderfRgBangCwordRegexBoundary :=leaderf#Rg#startCmdline(0, 1, 1, 1) noremap LeaderfRgWORDLiteralNoBoundary :=leaderf#Rg#startCmdline(1, 0, 0, 0) -noremap LeaderfRgWORDLiteralBoundary :=leaderf#Rg#startCmdline(1, 0, 0, 1) +noremap LeaderfRgWORDLiteralBoundary :=leaderf#Rg#startCmdline(1, 0, 0, 0) noremap LeaderfRgWORDRegexNoBoundary :=leaderf#Rg#startCmdline(1, 0, 1, 0) noremap LeaderfRgWORDRegexBoundary :=leaderf#Rg#startCmdline(1, 0, 1, 1) @@ -176,8 +197,8 @@ vnoremap LeaderfGtagsReference :=leaderf#Gtags#startCmd vnoremap LeaderfGtagsSymbol :=leaderf#Gtags#startCmdline(2, 1, 's') vnoremap LeaderfGtagsGrep :=leaderf#Gtags#startCmdline(2, 1, 'g') -command! -bar -nargs=? -complete=dir LeaderfFile Leaderf file -command! -bar -nargs=? -complete=dir LeaderfFileFullScreen Leaderf file --fullScreen +command! -bar -nargs=* -complete=dir LeaderfFile Leaderf file +command! -bar -nargs=* -complete=dir LeaderfFileFullScreen Leaderf file --fullScreen command! -bar -nargs=1 LeaderfFilePattern Leaderf file --input command! -bar -nargs=0 LeaderfFileCword Leaderf file --cword @@ -243,6 +264,14 @@ command! -bar -nargs=0 LeaderfWindow Leaderf window command! -bar -nargs=0 LeaderfQuickFix Leaderf quickfix command! -bar -nargs=0 LeaderfLocList Leaderf loclist +command! -bar -nargs=0 LeaderfGit Leaderf git +command! -bar -nargs=0 LeaderfGitSplitDiff Leaderf git diff --current-file --side-by-side +command! -bar -nargs=0 LeaderfGitNavigationOpen call leaderf#Git#OpenNavigationPanel() +command! -bar -nargs=0 LeaderfGitInlineBlameEnable Leaderf git blame --inline +command! -bar -nargs=0 LeaderfGitInlineBlameDisable call leaderf#Git#DisableInlineBlame() +command! -bar -nargs=0 LeaderfGitInlineBlameToggle call leaderf#Git#ToggleInlineBlame() +command! -bar -nargs=0 LeaderfGitInlineBlameUpdate call leaderf#Git#RestartInlineBlame() + try if g:Lf_ShortcutF != "" exec 'nnoremap ' g:Lf_ShortcutF ':LeaderfFile' diff --git a/syntax/leaderf.vim b/syntax/leaderf.vim index f7bf89ec..9f885323 100644 --- a/syntax/leaderf.vim +++ b/syntax/leaderf.vim @@ -29,101 +29,5 @@ endif let b:current_syntax = "leaderf" -function! g:LfDefineDefaultColors() abort - highlight def link Lf_hl_popup_cursor Cursor - highlight def Lf_hl_cursorline guifg=Yellow guibg=NONE gui=NONE ctermfg=226 ctermbg=NONE cterm=NONE - - " the color of matching character - highlight def Lf_hl_match guifg=SpringGreen guibg=NONE gui=bold ctermfg=85 ctermbg=NONE cterm=bold - - " the color of matching character in `And mode` - highlight def Lf_hl_match0 guifg=SpringGreen guibg=NONE gui=bold ctermfg=85 ctermbg=NONE cterm=bold - highlight def Lf_hl_match1 guifg=#fe8019 guibg=NONE gui=bold ctermfg=208 ctermbg=NONE cterm=bold - highlight def Lf_hl_match2 guifg=#3ff5d1 guibg=NONE gui=bold ctermfg=50 ctermbg=NONE cterm=bold - highlight def Lf_hl_match3 guifg=#ff7272 guibg=NONE gui=bold ctermfg=203 ctermbg=NONE cterm=bold - highlight def Lf_hl_match4 guifg=#43b9f0 guibg=NONE gui=bold ctermfg=74 ctermbg=NONE cterm=bold - - " the color of matching character in nameOnly mode when ';' is typed - highlight def Lf_hl_matchRefine gui=bold guifg=Magenta cterm=bold ctermfg=201 - - " the color of help in normal mode when is pressed - highlight def link Lf_hl_help Comment - highlight def link Lf_hl_helpCmd Identifier - - " the color when select multiple lines - highlight def Lf_hl_selection guifg=Black guibg=#a5eb84 gui=NONE ctermfg=Black ctermbg=156 cterm=NONE - - " the color of `Leaderf buffer` - highlight def link Lf_hl_bufNumber Constant - highlight def link Lf_hl_bufIndicators Statement - highlight def link Lf_hl_bufModified String - highlight def link Lf_hl_bufNomodifiable Comment - highlight def link Lf_hl_bufDirname Directory - - " the color of `Leaderf tag` - highlight def link Lf_hl_tagFile Directory - highlight def link Lf_hl_tagType Type - highlight def link Lf_hl_tagKeyword Keyword - - " the color of `Leaderf bufTag` - highlight def link Lf_hl_buftagKind Title - highlight def link Lf_hl_buftagScopeType Keyword - highlight def link Lf_hl_buftagScope Type - highlight def link Lf_hl_buftagDirname Directory - highlight def link Lf_hl_buftagLineNum Constant - highlight def link Lf_hl_buftagCode Comment - - " the color of `Leaderf function` - highlight def link Lf_hl_funcKind Title - highlight def link Lf_hl_funcReturnType Type - highlight def link Lf_hl_funcScope Keyword - highlight def link Lf_hl_funcName Function - highlight def link Lf_hl_funcDirname Directory - highlight def link Lf_hl_funcLineNum Constant - - " the color of `Leaderf line` - highlight def link Lf_hl_lineLocation Comment - - " the color of `Leaderf self` - highlight def link Lf_hl_selfIndex Constant - highlight def link Lf_hl_selfDescription Comment - - " the color of `Leaderf help` - highlight def link Lf_hl_helpTagfile Comment - - " the color of `Leaderf rg` - highlight def link Lf_hl_rgFileName Directory - highlight def link Lf_hl_rgLineNumber Constant - " the color of line number if '-A' or '-B' or '-C' is in the options list - " of `Leaderf rg` - highlight def link Lf_hl_rgLineNumber2 Folded - " the color of column number if '--column' in g:Lf_RgConfig - highlight def link Lf_hl_rgColumnNumber Constant - highlight def Lf_hl_rgHighlight guifg=#000000 guibg=#cccc66 gui=NONE ctermfg=16 ctermbg=185 cterm=NONE - - " the color of `Leaderf gtags` - highlight def link Lf_hl_gtagsFileName Directory - highlight def link Lf_hl_gtagsLineNumber Constant - highlight def Lf_hl_gtagsHighlight guifg=#000000 guibg=#cccc66 gui=NONE ctermfg=16 ctermbg=185 cterm=NONE - - highlight def link Lf_hl_previewTitle Statusline - - highlight def link Lf_hl_winNumber Constant - highlight def link Lf_hl_winIndicators Statement - highlight def link Lf_hl_winModified String - highlight def link Lf_hl_winNomodifiable Comment - highlight def link Lf_hl_winDirname Directory - - highlight def link Lf_hl_quickfixFileName Directory - highlight def link Lf_hl_quickfixLineNumber Constant - highlight def link Lf_hl_quickfixColumnNumber Constant - - highlight def link Lf_hl_loclistFileName Directory - highlight def link Lf_hl_loclistLineNumber Constant - highlight def link Lf_hl_loclistColumnNumber Constant - - highlight def link Lf_hl_jumpsTitle Title - highlight def link Lf_hl_jumpsNumber Number - highlight def link Lf_hl_jumpsLineCol String - highlight def link Lf_hl_jumpsIndicator Type -endfunction +" define the highlight groups +" new highlight groups will be defined in autoload/leaderf/colorscheme/popup/default.vim