├── LICENSE ├── README.md ├── doc ├── poptools.txt └── tags ├── lib ├── funcs.vim └── unused.vim ├── plugin └── vim_poptools.vim └── vim_poptools.gif /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Ubaldo Tiberi. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-poptools 2 | 3 | Exploit popups as much as you can! 4 | 5 |

6 | 7 |

8 | 9 |

10 | * Vim-poptools * 11 |

12 | 13 | Poptools aims to scale your productivity by conveniently using popups for a 14 | multitude of tasks, from finding files and directories, to setting your 15 | favorite colorscheme. 16 | Once a list of results is slammed into a popup menu, you can filter it in an 17 | fuzzy or exact fashion. 18 | 19 | Poptools is more essential compared to similar plugins such as [fzf][0], 20 | [fuzzyy][1] or [scope][2] and differently from them, external programs are 21 | called _synchronously_, although things may change in the future. :) 22 | 23 | Nevertheless, I personally like the interface and how it displays all the 24 | results at once. Additionally, I find the opportunity of saving the last 25 | search very handy. The configuration is also fairly straightforward. 26 | 27 | ### Commands 28 | 29 | The following is what you can search and show in popups. 30 | The commands are self-explanatory: 31 | 32 | ``` 33 | :PoptoolsFindFile 34 | :PoptoolsFindFileInPath # Takes into account the setting of :h 'path'. 35 | :PoptoolsFindDir # Search from the current directory downwards 36 | :PoptoolsBuffers 37 | :PoptoolsRecentFiles 38 | :PoptoolsCmdHistory 39 | :PoptoolsKill # When something goes wrong, you can clear all the Poptools popups 40 | :PoptoolsColorscheme # The displayed colors depends on the value of :h 'background' 41 | :PoptoolsGrepInBuffer # Find pattern in the current buffer 42 | :PoptoolsGrep # External grep. Grep command is displayed. 43 | :PoptoolsVimgrep # Vimgrep, show results in the quickfix-list instead of a popup. 44 | :PoptoolsLastSearch # Show the last search results 45 | ``` 46 | 47 | ... and if you are curious, the following is how I mapped them in my `.vimrc`: 48 | 49 | ``` 50 | nnoremap PoptoolsFindFile 51 | nnoremap l PoptoolsLastSearch 52 | nnoremap PoptoolsBuffers 53 | nnoremap o PoptoolsRecentFiles 54 | ``` 55 | ## Configuration 56 | 57 | If you don't like the default behavior, there is room for some customization. 58 | The process is very easy. All you have to do is to set some entries in 59 | the `g:poptools_config` dictionary. 60 | 61 | However, keep in mind that you may also change the plugin behavior by through 62 | Vim the options `:h 'wildignore'`, `:h 'wildoptions'` and `:h 'path'`. 63 | 64 | ### Preview window 65 | 66 | You may not want the preview window in every case. For example, you want it 67 | when you _grep_ but not when you open recent files. If that is the case, do as 68 | it follows 69 | 70 | ``` 71 | g:poptools_config = {} 72 | g:poptools_config['preview_grep'] = true 73 | g:poptools_config['preview_recent_files'] = false, 74 | ``` 75 | 76 | ### Syntax highlight in the preview window 77 | 78 | Syntax highlight in the preview window can be handy, but it may slow down the 79 | user experience. You can avoid using syntax highlight in the preview window by 80 | setting `g:poptools_config['preview_syntax'] = false`. This is useful in case 81 | you are encountering troubles when using the preview window. The match are 82 | still highlighted. 83 | 84 | 85 | ### To fuzzy or not to fuzzy? 86 | 87 | You can filter the results either in a fuzzy or in an exact fashion. You choose 88 | it by setting `g:poptools_config['fuzzy_search']` to `true` or to `false`. 89 | 90 | It follows an example of configuration: 91 | 92 | ``` 93 | g:poptools_config = {} 94 | g:poptools_config['preview_syntax'] = false 95 | g:poptools_config['preview_recent_files'] = false 96 | g:poptools_config['fuzzy_search'] = false 97 | ``` 98 | To see the whole list of keys allowed in the `g:poptools_config` dictionary, 99 | take a look at `:h poptools.txt`. 100 | 101 | ## Some notes on files/patterns search 102 | 103 | ### `PoptoolsFindFile` and `PoptoolsFindInPath` 104 | 105 | These commands take into account the setting of `:h 'wildignore'`, 106 | `:h 'wildoptions'` and `:h 'path'` options, so if you want to include/exclude 107 | some search path, you must adjust such options. 108 | 109 | By default, hidden files are excluded. If you want to find them, then you must 110 | add `.` at the beginning of the search pattern, e.g. use `.git*` to get e.g. 111 | `.gitignore`. 112 | 113 | Hidden files are searched in non-hidden folders. To find files in a hidden 114 | folder, you must first `cd` into such a folder. For example, `cd ~/.vim` 115 | followed by `PopupFindFiles` will search files inside the `.vim` folder. 116 | 117 | ### `PoptoolsGrep` and `PoptoolsVimgrep` 118 | 119 | They use the internal `:h vimgrep` and the external `:h grep`. However, The 120 | user interface is the same. The results appear both in the quickfix-list and in 121 | the popup. 122 | 123 | By default, the option `'grepprg'` is set, as it follows: 124 | 125 | ``` 126 | # Windows 127 | &grepprg = 128 | 'powershell -NoProfile -ExecutionPolicy Bypass -Command ' 129 | .. '"& {Set-Location -LiteralPath ''' .. search_dir .. '''; findstr /C:''' 130 | .. what .. ''' /N /S ''' .. items .. '''}"' 131 | 132 | # *nix 133 | &grepprg = 'grep -nrH --include="{items}" "{what}" {search_dir}' 134 | ``` 135 | 136 | where the values of `{what}`,`{files}` and `{search_dir}` are replaced by 137 | user input. 138 | 139 | You can also configure your own `'grepprg'` command through the keys `grep_cmd_win` 140 | and `grep_cmd_nix` of the `g:poptools_config` dictionary and you can use the 141 | placeholders `{search_dir}, {items}` and `{what}`. For example, you could set 142 | the following: 143 | 144 | ``` 145 | g:poptools_config['grep_cmd_win'] = 146 | 'powershell -NoProfile -ExecutionPolicy Bypass -Command ' 147 | .. '"& { findstr /C:''' .. what .. ''' /N /S ''' 148 | .. fnamemodify($'{search_dir}\{items}', ':p') .. ''' }"' 149 | ``` 150 | 151 | If you define your own `'grepprg'`, then the elements in the quickfix shall 152 | contain the buffer number, the line number and the text. You can verify it by 153 | running `:echo getqflist()`. The returned dictionaries must have the keys 154 | `bufnr`, `lnum` and `text`. If that is not the case, then you need to set 155 | `'grepformat'` adequately. See `:h 'grepformat'` for more info. 156 | 157 | ### `PopupFindDir` 158 | 159 | To find hidden folders with `PopupFindDir` command, just add a `.` in front of 160 | the search pattern, e.g. `.git*`. That will return e.g. `.git/, .github/`, 161 | etc. 162 | 163 | 164 | [0]: https://github.com/junegunn/fzf.vim 165 | [1]: https://github.com/Donaldttt/fuzzyy 166 | [2]: https://github.com/girishji/scope.vim 167 | -------------------------------------------------------------------------------- /doc/poptools.txt: -------------------------------------------------------------------------------- 1 | *poptools.txt* 2 | 3 | Author: ubaldot (ubaldo DOT tiberi AT gmail DOT com) 4 | For Vim version 9.0 and above 5 | 6 | ============================================================================== 7 | CONTENTS *poptools-contents* 8 | 9 | 1. Introduction ......... |poptools-introduction| 10 | 2. Requirements ......... |poptools-requirements| 11 | 3. Commands ............. |poptools-commands| 12 | 4. Grep and Vimgrep ..... |poptools-grep| 13 | 5. Configuration ........ |poptools-configuration| 14 | 6. License .............. |poptools-license| 15 | 16 | ============================================================================== 17 | INTRODUCTION *poptools-introduction* 18 | 19 | Poptools aims to scale your productivity by conveniently using popups for a 20 | multitude of tasks, from finding files and directories, to setting your 21 | favorite colorscheme. 22 | 23 | Once a list of results is slammed into a popup menu, you can filter it in a 24 | fuzzy or in an exact way. 25 | 26 | Poptools is more essential compared to similar plugins, but differently 27 | from them, external programs are called _synchronously_, although things may 28 | change in the future. :) 29 | 30 | Nevertheless, I personally like the interface and how it displays all the 31 | results at once. Additionally, I find the opportunity of saving the last 32 | search very handy. The configuration is also fairly straightforward. 33 | 34 | ============================================================================== 35 | REQUIREMENTS *poptools-requirements* 36 | 37 | Vim 9.0 is required. 38 | 39 | ============================================================================== 40 | COMMANDS *poptools-commands* 41 | 42 | It follows the list of available commads: > 43 | 44 | :PoptoolsFindFile 45 | :PoptoolsFindFileInPath # Takes into account the setting of :h 'path'. 46 | :PoptoolsFindDir # Search from the current directory downwards 47 | :PoptoolsBuffers 48 | :PoptoolsRecentFiles 49 | :PoptoolsCmdHistory 50 | # When something goes wrong, you can clear all the Poptools popups 51 | :PoptoolsKill 52 | # The displayed colors depends on the value of :h 'background' 53 | :PoptoolsColorscheme 54 | :PoptoolsGrepInBuffer # Find pattern in the current buffer 55 | :PoptoolsGrep # External grep. Grep command is displayed. 56 | # Vimgrep, show results in the quickfix-list instead of a popup. 57 | :PoptoolsVimgrep 58 | :PoptoolsLastSearch # Show the last search results 59 | < 60 | 61 | ============================================================================== 62 | GREP AND VIMGREP *poptools-grep* *poptools-vimgrep* 63 | 64 | `PopupToolsVimGrep` uses |vimgrep| whereas `PopupToolsGrep` uses |grep|. 65 | However, the user interface is the same. 66 | The results appear both in the |quickfix-list| and in the popup window. 67 | 68 | By default, |grepprg| is set,as it follows: 69 | 70 | > 71 | # Windows 72 | 'powershell -NoProfile -ExecutionPolicy Bypass -Command ' 73 | .. '"& { findstr /C:''' .. what .. ''' /N /S ''' 74 | .. fnamemodify($'{search_dir}\{items}', ':p') .. ''' }"' 75 | 76 | # *nix 77 | &grepprg = 'grep -nrH --include="{items}" "{what}" {search_dir}' 78 | < 79 | 80 | where the values of `{what}`,`{files}` and `{search_dir}` are replaced by 81 | user input values. 82 | 83 | You can configure your own |grepprg| command through the keys 84 | `grep_cmd_win` and `grep_cmd_nix` of the |g:poptools_config| dictionary and 85 | you can use the placeholders `{search_dir}, {items}` and `{what}`. See below 86 | for more info. 87 | 88 | The grep command sent to the shell can be retrieved through |:messages|. See 89 | below for more info. 90 | 91 | TIP: Once you have performed a search, all the results can be accessed at any 92 | time through `:PopupToolsLastSearch` without the need of performing a new 93 | search. 94 | 95 | ============================================================================== 96 | CONFIGURATION *poptools-configuration* *g:poptools_config* 97 | 98 | The plug configuration is done through `g:poptools_config` dictionary. 99 | Be sure to create the empty dictionary `g:poptools_config = {}` before 100 | filling it with he various keys. 101 | 102 | Available keys: 103 | "preview_file" Set to `true` if you want a preview window when 104 | searching for files. 105 | Default: `false`. 106 | "preview_file_in_path" Set to `true` if you want a preview window when 107 | searching for files in path. 108 | Default: `false`. 109 | "preview_recent_files" Set to `true` if you want a preview window when 110 | searching for recently opened files. 111 | Default: `false`. 112 | "preview_buffers" Set to `true` if you want a preview window when 113 | searching for buffers. 114 | Default: `false`. 115 | "preview_grep" Set to `true` if you want a preview window when 116 | using poptools grep functions. 117 | Default: `false`. 118 | "preview_vimgrep" Set to `true` if you want a preview window when 119 | using poptools vimgrep functions. 120 | Default: `false`. 121 | "fuzzy_search" Set to `true` if you want a fuzzy filtering of the 122 | results, or to `false` if you want to filter based 123 | on exact match. Default: `true`. 124 | "preview_syntax" Set to `true` if you want syntax highlighting in the 125 | preview window. Note that syntax highlighting may 126 | slow down the user-experience. Default: `true`. 127 | "grep_inc_search" Set to `false` if you don't want to incrementally 128 | search in the current buffer when using `grep` 129 | and `vimgrep`. Default: `true`. 130 | "grep_cmd_win" Value of |grepprg| for Windows. 131 | Default: 132 | > 133 | 'powershell -NoProfile -ExecutionPolicy Bypass -Command ' 134 | .. '"& { findstr /C:''' .. what .. ''' /N /S ''' 135 | .. fnamemodify($'{search_dir}\{items}', ':p') .. ''' }"' 136 | < 137 | 138 | "grep_cmd_nix" Value of |grepprg| for *nix systems. 139 | Default: 140 | > 141 | grep -nrH --include="{items}" "{what}" {search_dir} 142 | < 143 | If you define your own |grepprg|, then the elements in the quickfix shall 144 | contain the buffer number, the line number and the text. You can verify it by 145 | running `:echo getqflist()`. The returned dictionaries must have the keys 146 | `bufnr`, `lnum` and `text`. If that is not the case, then you need to set 147 | |grepformat| adequately. See |grepformat| for more info. 148 | 149 | ============================================================================== 150 | LICENSE *poptools-license* 151 | 152 | BSD 3-Clause License 153 | 154 | Copyright (c) 2025, Ubaldo Tiberi. 155 | All rights reserved. 156 | 157 | Redistribution and use in source and binary forms, with or without 158 | modification, are permitted provided that the following conditions are met: 159 | 160 | * Redistributions of source code must retain the above copyright notice, this 161 | list of conditions and the following disclaimer. 162 | 163 | * Redistributions in binary form must reproduce the above copyright notice, 164 | this list of conditions and the following disclaimer in the documentation 165 | and/or other materials provided with the distribution. 166 | 167 | * Neither the name of the copyright holder nor the names of its 168 | contributors may be used to endorse or promote products derived from 169 | this software without specific prior written permission. 170 | 171 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 172 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 173 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 174 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 175 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 176 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 177 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 178 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 179 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 180 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 181 | -------------------------------------------------------------------------------- /doc/tags: -------------------------------------------------------------------------------- 1 | g:poptools_config poptools.txt /*g:poptools_config* 2 | poptools-commands poptools.txt /*poptools-commands* 3 | poptools-configuration poptools.txt /*poptools-configuration* 4 | poptools-contents poptools.txt /*poptools-contents* 5 | poptools-grep poptools.txt /*poptools-grep* 6 | poptools-introduction poptools.txt /*poptools-introduction* 7 | poptools-license poptools.txt /*poptools-license* 8 | poptools-requirements poptools.txt /*poptools-requirements* 9 | poptools-vimgrep poptools.txt /*poptools-vimgrep* 10 | poptools.txt poptools.txt /*poptools.txt* 11 | -------------------------------------------------------------------------------- /lib/funcs.vim: -------------------------------------------------------------------------------- 1 | vim9script 2 | 3 | # TODO Exclude 'wildignore' paths in Grep (it uses an external program) 4 | 5 | # This must be persistent across different calls and therefore we explicitly 6 | # assign a number 7 | var last_results = [] 8 | var last_title = '' 9 | var last_search_type = '' 10 | var last_what = '' 11 | 12 | var main_id: number 13 | var prompt_id: number 14 | var preview_id: number 15 | 16 | var prompt_cursor: string 17 | var prompt_sign: string 18 | var prompt_text: string 19 | 20 | # User defined settings through g:poptools_config 21 | var fuzzy_search: bool 22 | var preview_syntax: bool 23 | 24 | var what: string 25 | var items: string 26 | var search_dir: string 27 | 28 | var grep_inc_search: bool 29 | # Hide cursor when operating in the popups 30 | var gui_cursor: list> 31 | 32 | def Echoerr(msg: string) 33 | echohl ErrorMsg | echom $"[poptools] {msg}" | echohl None 34 | enddef 35 | 36 | def Echowarn(msg: string) 37 | echohl WarningMsg | echom $"[poptools] {msg}" | echohl None 38 | enddef 39 | 40 | def InitScriptLocalVars() 41 | # Set script-local variables 42 | 43 | main_id = -1 44 | prompt_id = -1 45 | preview_id = -1 46 | 47 | prompt_cursor = '▏' 48 | prompt_sign = '> ' 49 | prompt_text = '' 50 | 51 | what = '' 52 | items = '' 53 | search_dir = '' 54 | 55 | if exists('g:poptools_config') && has_key(g:poptools_config, 56 | 'preview_syntax') 57 | preview_syntax = g:poptools_config['preview_syntax'] 58 | else 59 | preview_syntax = true 60 | endif 61 | 62 | if exists('g:poptools_config') && has_key(g:poptools_config, 63 | 'grep_inc_search') 64 | grep_inc_search = g:poptools_config['grep_inc_search'] 65 | else 66 | grep_inc_search = true 67 | endif 68 | 69 | if exists('g:poptools_config') && has_key(g:poptools_config, 'fuzzy_search') 70 | fuzzy_search = g:poptools_config['fuzzy_search'] 71 | else 72 | fuzzy_search = true 73 | endif 74 | 75 | if empty(prop_type_get('PopupToolsMatched')) 76 | prop_type_add('PopupToolsMatched', {highlight: 'WarningMsg'}) 77 | endif 78 | enddef 79 | 80 | def RestoreCursor() 81 | set t_ve& 82 | if hlget("Cursor")[0]->get('cleared', false) 83 | hlset(gui_cursor) 84 | endif 85 | enddef 86 | 87 | # ----- Callback functions ------------------------ 88 | def PopupCallbackGrep(id: number, idx: number) 89 | if idx > 0 90 | popup_close(prompt_id, -1) 91 | if preview_id != -1 92 | popup_close(preview_id, -1) 93 | endif 94 | 95 | var selection = getbufline(winbufnr(main_id), idx)[0] 96 | # grep return format is 'file.xyz:76: ...' 97 | # You must extract the filename and the line number. 98 | # However, the name is not full, and you must reconstruct. The easiest 99 | # way is to fetch it from the popup title 100 | # 101 | # OBS! You could use split(selection, ':') to separate filename from line 102 | # number, but what if a filename is 'foo:bar'? 103 | var filename = selection->matchstr('^.*\ze:\d') 104 | var line = selection->matchstr('^.\{-}:\zs\d*\ze:') 105 | 106 | var path = split(popup_getoptions(id).title)[0] 107 | try 108 | if getcwd() == path 109 | exe $'edit {path}/{filename}' 110 | else 111 | exe $'edit {filename}' 112 | endif 113 | catch 114 | ClosePopups() 115 | Echoerr($'Cannot open {filename}') 116 | endtry 117 | 118 | cursor(str2nr(line), 1) 119 | RestoreCursor() 120 | endif 121 | enddef 122 | 123 | def PopupCallbackFileBuffer(id: number, idx: number) 124 | if idx > 0 125 | popup_close(prompt_id, -1) 126 | if preview_id != -1 127 | popup_close(preview_id, -1) 128 | endif 129 | echo "" 130 | var selection = getbufline(winbufnr(main_id), idx)[0] 131 | exe $'edit {selection}' 132 | RestoreCursor() 133 | endif 134 | enddef 135 | 136 | def PopupCallbackHistory(id: number, idx: number) 137 | if idx > 0 138 | popup_close(prompt_id, -1) 139 | if preview_id != -1 140 | popup_close(preview_id, -1) 141 | endif 142 | var cmd = getbufline(winbufnr(main_id), idx)[0] 143 | feedkeys(cmd) 144 | RestoreCursor() 145 | endif 146 | enddef 147 | 148 | def PopupCallbackDir(id: number, idx: number) 149 | if idx > 0 150 | var dir = getbufline(winbufnr(main_id), idx)[0] 151 | exe $'cd {dir}' 152 | pwd 153 | popup_close(prompt_id, -1) 154 | RestoreCursor() 155 | endif 156 | enddef 157 | 158 | def PopupCallbackColorscheme(id: number, idx: number) 159 | if idx > 0 160 | var scheme = getbufline(winbufnr(main_id), idx)[0] 161 | noa exe $'colorscheme {scheme}' 162 | popup_close(prompt_id, -1) 163 | RestoreCursor() 164 | endif 165 | enddef 166 | 167 | def UpdateFilePreview(search_type: string) 168 | # You may use external programs to count the lines if 'readfile()' is too 169 | # slow, e.g. 170 | # var file_length = has('win32') ? str2nr(system('...')) : 171 | # str2nr(system($'wc 172 | # -l {filename}')->matchstr('\s*\zs\d*')) 173 | # var buf_lines = has('win32') 174 | # ? systemlist($'powershell -c "Get-Content {filename} | Select-Object 175 | # -Skip 176 | # ({firstline} - 1) -First ({lastline} - {firstline} + 1)"') 177 | # : systemlist($'sed -n "{firstline},{lastline}p" {filename}') 178 | # 179 | # For the syntax highlight, you may use the 'GetFiletypeByFilename()' 180 | # function, which is now in unused.vim 181 | # 182 | # Parse the highlighted line on the main popup 183 | var idx = line('.', main_id) 184 | 185 | # This 'if' is needed because the filter is called on anyways 186 | if idx > 0 187 | # The lines of main_id may be of the form 'filenames' 188 | # or 'filenames:lines:text' 189 | var filename = index(['grep', 'vimgrep'], search_type) != -1 190 | ? getbufline(winbufnr(main_id), idx)[0]->matchstr('^.*\ze:\d') 191 | : getbufline(winbufnr(main_id), idx)[0] 192 | 193 | var line_nr = index(['grep', 'vimgrep'], search_type) != -1 194 | ? str2nr(getbufline(winbufnr(main_id), idx)[0]->matchstr(':\zs\d*\ze:')) 195 | : popup_getpos(main_id).core_height / 2 196 | 197 | # We split the fullname so that we can show it nicely in the popup. 198 | # However, when showing the preview or during the callback, it is safer to 199 | # have the fullname. The path is in the popup title. 200 | # In case of Buffers or Recent files, that is not needed 201 | if index(['file', 'file_in_path', 'grep', 'vimgrep'], search_type) != -1 202 | var path = split(popup_getoptions(main_id).title)[0] 203 | if getcwd() == path 204 | filename = $'{path}/{filename}' 205 | endif 206 | endif 207 | 208 | var file_content = [] 209 | if bufexists(filename) 210 | # The quickfix list load the buffers, but they have no content yet 211 | if empty(getqflist()) 212 | file_content = getbufline(filename, 1, '$') 213 | else 214 | file_content = readfile($'{filename}') 215 | endif 216 | elseif filereadable($'{expand(filename)}') 217 | file_content = readfile($'{expand(filename)}') 218 | else 219 | file_content = ["Can't preview the file!"] 220 | endif 221 | 222 | # Set options 223 | win_execute(preview_id, $'setlocal number') 224 | # TODO: the wrap thing may be selectable through g:poptools_config? 225 | # win_execute(preview_id, '&wrap = false') 226 | 227 | # clean the preview 228 | popup_settext(preview_id, repeat([""], popup_getpos(main_id).core_height)) 229 | # populate the preview 230 | setwinvar(preview_id, 'buf_lines', file_content) 231 | win_execute(preview_id, 'append(0, w:buf_lines)') 232 | # Unfold stuff 233 | win_execute(preview_id, 'norm! zR') 234 | 235 | # Syntax highlight if it creates problems, disable it. It is not 236 | # bulletproof 237 | if preview_syntax 238 | # set 'synmaxcol' for avoiding crashing if some readable file has 239 | # embedded figures. 240 | # Figure generate lines with >80000 columns and the internal engine 241 | # to figure out the syntax will fail. 242 | var old_synmaxcol = &synmaxcol 243 | &synmaxcol = 300 244 | var buf_extension = $'{fnamemodify(filename, ":e")}' 245 | var found_filetypedetect_cmd = 246 | autocmd_get({group: 'filetypedetect'}) 247 | ->filter($'v:val.pattern =~ "*\\.{buf_extension}$"') 248 | var set_filetype_cmd = '' 249 | if empty(found_filetypedetect_cmd) 250 | if index([$"{$HOME}/.vimrc", $"{$HOME}/.gvimrc"], expand(filename)) != -1 251 | set_filetype_cmd = '&filetype = "vim"' 252 | else 253 | set_filetype_cmd = '&filetype = ""' 254 | endif 255 | else 256 | set_filetype_cmd = found_filetypedetect_cmd[0].cmd 257 | endif 258 | win_execute(preview_id, set_filetype_cmd) 259 | &synmaxcol = old_synmaxcol 260 | endif 261 | 262 | # Highlight grep matches 263 | if !empty(what) 264 | win_execute(preview_id, $'normal! {line_nr}gg') 265 | win_execute(preview_id, 'setlocal cursorline') 266 | win_execute(preview_id, $'match Search /{what}/') 267 | endif 268 | 269 | # Set preview ID title 270 | var preview_id_opts = popup_getoptions(preview_id) 271 | preview_id_opts.title = $' {fnamemodify(filename, ':t')} ' 272 | popup_setoptions(preview_id, preview_id_opts) 273 | endif 274 | enddef 275 | 276 | export def ClosePopups() 277 | # This function tear down everything 278 | if preview_id != -1 279 | popup_close(preview_id, -1) 280 | endif 281 | popup_close(main_id, -1) 282 | popup_close(prompt_id, -1) 283 | RestoreCursor() 284 | prop_type_delete('PopupToolsMatched') 285 | enddef 286 | 287 | def PopupFilter(id: number, 288 | key: string, 289 | results: list, 290 | search_type: string, 291 | current_colorscheme: string, 292 | current_background: string, 293 | ): bool 294 | 295 | # Save for last search 296 | if index(['file', 'file_in_path', 'grep', 'vimgrep'], search_type) != -1 297 | last_results = getbufline(winbufnr(main_id), 1, '$') 298 | last_title = popup_getoptions(main_id).title 299 | last_search_type = search_type 300 | last_what = what 301 | endif 302 | 303 | var maxheight = popup_getoptions(main_id).maxheight 304 | 305 | if key == "\" 306 | if search_type == 'colorscheme' 307 | exe $'colorscheme {current_colorscheme}' 308 | endif 309 | ClosePopups() 310 | return true 311 | endif 312 | 313 | # For debugging 314 | # echo 'Pressed key: ' .. key 315 | echo '' 316 | var preview_update = true 317 | # You never know what the user can type... Let's use a try-catch 318 | try 319 | if key == "\" 320 | popup_close(main_id, getcurpos(main_id)[1]) 321 | ClosePopups() 322 | elseif index(["\", "\"], key) != -1 323 | win_execute(main_id, 'normal! ' .. maxheight .. "\") 324 | elseif index(["\", "\"], key) != -1 325 | win_execute(main_id, 'normal! ' .. maxheight .. "\") 326 | elseif key == "\" 327 | win_execute(main_id, "normal! gg") 328 | elseif key == "\" 329 | win_execute(main_id, "normal! G") 330 | elseif index(["\", "\", "\", "\"], key) 331 | != -1 332 | var ln = getcurpos(main_id)[1] 333 | win_execute(main_id, "normal! j") 334 | if ln == getcurpos(main_id)[1] 335 | win_execute(main_id, "normal! gg") 336 | endif 337 | elseif index(["\", "\", "\", "\"], key) != 338 | -1 339 | var ln = getcurpos(main_id)[1] 340 | win_execute(main_id, "normal! k") 341 | if ln == getcurpos(main_id)[1] 342 | win_execute(main_id, "normal! G") 343 | endif 344 | # Scroll preview window 345 | elseif preview_id != -1 && key == "\" 346 | win_execute(preview_id, "normal! \") 347 | preview_update = false 348 | elseif preview_id != -1 && key == "\" 349 | win_execute(preview_id, "normal! \") 350 | preview_update = false 351 | elseif preview_id != -1 && key == "\" 352 | # Echowarn("FOO") 353 | win_execute(preview_id, "normal! \") 354 | preview_update = false 355 | elseif preview_id != -1 && key == "\" 356 | win_execute(preview_id, "normal! \") 357 | preview_update = false 358 | # The real deal: take a single, printable character 359 | elseif key =~ '^\p$' || keytrans(key) ==# "" || key == "\" 360 | if key =~ '^\p$' 361 | prompt_text ..= key 362 | elseif keytrans(key) ==# "" 363 | if len(prompt_text) > 0 364 | prompt_text = prompt_text[: -2] 365 | endif 366 | elseif key == "\" 367 | prompt_text = "" 368 | endif 369 | 370 | popup_settext(prompt_id, $'{prompt_sign}{prompt_text}{prompt_cursor}') 371 | 372 | # What you pass to popup_settext(main_id, ...) is a list of strings with 373 | # text properties attached, e.g. 374 | # 375 | # [ 376 | # { "text": "filename.txt", 377 | # "props": [ {"col": 2, "length": 1, "type": "PopupToolsMatched"}, 378 | # ... ] 379 | # }, 380 | # { "text": "another_file.txt", 381 | # "props": [ {"col": 1, "length": 1, "type": "PopupToolsMatched"}, 382 | # ... ] 383 | # }, 384 | # ... 385 | # ] 386 | # 387 | var filtered_results_full = [] 388 | var filtered_results: list> 389 | 390 | if !empty(prompt_text) 391 | if fuzzy_search 392 | filtered_results_full = results->matchfuzzypos(prompt_text) 393 | var pos = filtered_results_full[1] 394 | filtered_results = filtered_results_full[0] 395 | ->map((ii, match) => ({ 396 | text: match, 397 | props: pos[ii]->copy()->map((_, col) => ({ 398 | col: col + 1, 399 | length: 1, 400 | type: 'PopupToolsMatched' 401 | }))})) 402 | else 403 | filtered_results_full = copy(results) 404 | ->map((_, text) => matchstrpos(text, 405 | \ '\V' .. $"{escape(prompt_text, '\')}")) 406 | ->map((idx, match_info) => [results[idx], match_info[1], 407 | match_info[2]]) 408 | 409 | filtered_results = copy(filtered_results_full) 410 | ->map((_, val) => ({ 411 | text: val[0], 412 | props: val[1] >= 0 && val[2] >= 0 413 | ? [{ 414 | type: 'PopupToolsMatched', 415 | col: val[1] + 1, 416 | end_col: val[2] + 1 417 | }] 418 | : [] 419 | })) 420 | ->filter("!empty(v:val.props)") 421 | endif 422 | endif 423 | 424 | var opts = popup_getoptions(prompt_id) 425 | var num_hits = !empty(filtered_results) 426 | ? len(filtered_results) 427 | : len(results) 428 | var base_title = trim(opts.title->matchstr('.*\ze(')) 429 | opts.title = $' {base_title} ({num_hits}) ' 430 | popup_setoptions(prompt_id, opts) 431 | 432 | if !empty(prompt_text) 433 | popup_settext(main_id, filtered_results) 434 | else 435 | popup_settext(main_id, results) 436 | endif 437 | else 438 | Echowarn('Unknown key') 439 | endif 440 | catch 441 | ClosePopups() 442 | Echoerr('Internal error') 443 | endtry 444 | 445 | if preview_id != -1 && preview_update 446 | UpdateFilePreview(search_type) 447 | endif 448 | 449 | if search_type == 'colorscheme' 450 | ShowColorscheme(current_background) 451 | endif 452 | 453 | return true 454 | enddef 455 | 456 | def ShowColorscheme(current_background: string) 457 | # Circular selection 458 | var idx = line('.', main_id) % (line('$', main_id) + 1) 459 | # TODO: check 460 | # I need this check because when user makes a selection with this 461 | # function is called anyways and idx will be 0 462 | if idx > 0 463 | var scheme = getbufline(winbufnr(main_id), idx)[0] 464 | exe $'colorscheme {scheme}' 465 | &background = current_background 466 | hi link PopupSelected PmenuSel 467 | endif 468 | enddef 469 | 470 | def ShowPromptPopup(results: list, 471 | search_type: string) 472 | # This is the UI thing 473 | var main_id_core_line = popup_getpos(main_id).core_line 474 | var main_id_core_col = popup_getpos(main_id).core_col 475 | var main_id_core_width = popup_getpos(main_id).core_width 476 | 477 | var base_title = $'{search_type}:' 478 | var opts = { 479 | minwidth: main_id_core_width, 480 | maxwidth: main_id_core_width, 481 | line: main_id_core_line - 3, 482 | col: main_id_core_col - 1, 483 | borderchars: ['─', '│', '─', '│', '╭', '╮', '╯', '╰'], 484 | border: [1, 1, 0, 1], 485 | mapping: 0, 486 | scrollbar: 0, 487 | wrap: 0, 488 | drag: 0, 489 | } 490 | 491 | # Filter 492 | var current_colorscheme = execute('colorscheme')->substitute('\n', '', 'g') 493 | var current_background = &background 494 | opts.filter = (id, key) => PopupFilter(id, key, results, search_type, 495 | current_colorscheme, current_background) 496 | 497 | var num_hits = len(getbufline(winbufnr(main_id), 1, "$")) 498 | if empty(what) 499 | opts.title = $' {base_title} ({num_hits}) ' 500 | else 501 | opts.title = $' {base_title} "{what}" ({num_hits}) ' 502 | endif 503 | 504 | prompt_text = "" 505 | prompt_id = popup_create([prompt_sign .. prompt_cursor], opts) 506 | 507 | enddef 508 | 509 | # ----- MAIN ----- 510 | def ShowPopup(results: list, 511 | search_type: string, 512 | title: string = '') 513 | # This function is regarded as main function. It is called once 514 | # the 'results' list is ready. 515 | # Clean up the command line to avoid 'Press Enter' otherwise the popups will 516 | # not show up 517 | redraw 518 | 519 | # For some reason you get ^@ (=newline) 520 | var current_colorscheme = execute('colorscheme')->substitute('\n', '', 'g') 521 | var current_background = &background 522 | hi link PopupSelected PmenuSel 523 | 524 | # hide cursor 525 | set t_ve= 526 | gui_cursor = hlget("Cursor") 527 | hlset([{name: 'Cursor', cleared: true}]) 528 | 529 | # Assuming no-preview 530 | var popup_width = (&columns * 2) / 3 531 | var popup_height = &lines / 2 532 | 533 | # Standard options 534 | var opts = { 535 | pos: 'center', 536 | border: [1, 1, 1, 1], 537 | borderchars: ['─', '│', '─', '│', '├', '┤', '╯', '╰'], 538 | maxheight: popup_height, 539 | minheight: popup_height, 540 | minwidth: popup_width, 541 | maxwidth: popup_width, 542 | scrollbar: 0, 543 | cursorline: 1, 544 | mapping: 0, 545 | wrap: 0, 546 | drag: 0, 547 | } 548 | 549 | if !empty(title) 550 | opts.title = title 551 | endif 552 | 553 | main_id = popup_create(results, opts) 554 | 555 | # Preview handling 556 | var show_preview = false 557 | if search_type == 'file' 558 | if exists('g:poptools_config') && has_key(g:poptools_config, 559 | 'preview_file') 560 | show_preview = g:poptools_config['preview_file'] 561 | endif 562 | elseif search_type == 'file_in_path' 563 | if exists('g:poptools_config') 564 | && has_key(g:poptools_config, 'preview_file_in_path') 565 | show_preview = g:poptools_config['preview_file_in_path'] 566 | endif 567 | elseif search_type == 'recent_files' 568 | if exists('g:poptools_config') 569 | && has_key(g:poptools_config, 'preview_recent_files') 570 | show_preview = g:poptools_config['preview_recent_files'] 571 | endif 572 | elseif search_type == 'buffer' 573 | if exists('g:poptools_config') 574 | && has_key(g:poptools_config, 'preview_buffers') 575 | show_preview = g:poptools_config['preview_buffers'] 576 | endif 577 | elseif search_type == 'grep' 578 | if exists('g:poptools_config') && has_key(g:poptools_config, 579 | 'preview_grep') 580 | show_preview = g:poptools_config['preview_grep'] 581 | endif 582 | elseif search_type == 'vimgrep' 583 | if exists('g:poptools_config') && has_key(g:poptools_config, 584 | 'preview_vimgrep') 585 | show_preview = g:poptools_config['preview_vimgrep'] 586 | endif 587 | endif 588 | 589 | if show_preview 590 | # Some geometry: we want more room in case of preview 591 | popup_width = (&columns * 9) / 10 592 | popup_height = (&lines * 7) / 10 593 | var left_margin = 8 594 | 595 | # Adjustments for the main popup 596 | opts.pos = 'topleft' 597 | opts.minwidth = float2nr(0.4 * popup_width) 598 | opts.maxwidth = float2nr(0.4 * popup_width) 599 | opts.maxheight = popup_height 600 | opts.minheight = popup_height 601 | opts.line = 6 602 | # opts.col = &columns / 2 - opts.minwidth - 1 603 | opts.col = left_margin 604 | 605 | # Adjustments for the preview popup 606 | var preview_opts = copy(opts) 607 | preview_opts.pos = 'topleft' 608 | preview_opts.line = opts.line - 2 609 | preview_opts.col = left_margin + opts.maxwidth + 2 610 | preview_opts.minwidth = float2nr(0.6 * popup_width) - 1 611 | preview_opts.maxwidth = float2nr(0.6 * popup_width) - 1 612 | preview_opts.maxheight = opts.minheight + 2 613 | preview_opts.minheight = opts.minheight + 2 614 | preview_opts.cursorline = 0 615 | preview_opts.scrollbar = 1 616 | 617 | preview_opts.borderchars = ['─', '│', '─', '│', '╭', '╮', '╯', '╰'] 618 | preview_id = popup_create("Something went wrong." 619 | .. "Run :call popup_clear() to close.", preview_opts) 620 | 621 | UpdateFilePreview(search_type) 622 | endif 623 | 624 | if search_type == 'colorscheme' 625 | var init_highlight_location = index(results, current_colorscheme) 626 | win_execute(main_id, $'norm {init_highlight_location }j') 627 | endif 628 | 629 | # Callback switch for main_id 630 | var PopupCallback: func 631 | if index(['file', 'file_in_path', 'recent_files', 'buffer'], 632 | \ search_type) != -1 633 | PopupCallback = PopupCallbackFileBuffer 634 | elseif search_type == 'dir' 635 | PopupCallback = PopupCallbackDir 636 | elseif search_type == 'history' 637 | PopupCallback = PopupCallbackHistory 638 | elseif index(['grep', 'vimgrep'], search_type) != -1 639 | PopupCallback = PopupCallbackGrep 640 | elseif search_type == 'colorscheme' 641 | PopupCallback = PopupCallbackColorscheme 642 | endif 643 | 644 | opts.callback = PopupCallback 645 | popup_setoptions(main_id, opts) 646 | 647 | ShowPromptPopup(results, search_type) 648 | enddef 649 | 650 | # ---- API ------------ 651 | # The following functions are associated to commands in the plugin file. 652 | # They are used to generate the 'results' list to pass to ShowPopup() 653 | export def FindFile(search_type: string) 654 | InitScriptLocalVars() 655 | # Guard 656 | if (search_type == 'file' || search_type == 'file_in_path') 657 | \ && getcwd() == expand('~') 658 | Echoerr("You are in your home folder. Too many results.") 659 | return 660 | endif 661 | 662 | # Main 663 | what = input($"current folder: '{fnamemodify(getcwd(), ':~')}'\nFile name to 664 | \ search ('enter' for all): ") 665 | var hidden = what[0] == '.' ? '' : '*' 666 | 667 | search_dir = '.' 668 | if (search_type == 'file') 669 | var current_wildmenu = &wildmenu 670 | set nowildmenu 671 | search_dir = input($"\nin which folder (you can also use 'tab'): ", 672 | './**', 'dir') 673 | if empty(search_dir) || search_dir == './' 674 | search_dir = getcwd() 675 | endif 676 | &wildmenu = current_wildmenu 677 | endif 678 | var results = getcompletion($'{search_dir}/{hidden}{what}', 679 | \ search_type, true) 680 | # echo "\n[poptools] If the search takes too long hit CTRL-C few times and 681 | # try to 682 | # \ narrow down your search." 683 | 684 | if empty(results) 685 | echo $"'{what}' pattern not found!" 686 | else 687 | # OBS: the title MUST have filepath followed by \s because it is used to 688 | # reconstruct the full path filename 689 | var title = $" {fnamemodify(getcwd(), ':~')} - Files '{what}': " 690 | if empty(what) 691 | title = $" {fnamemodify(getcwd(), ':~')}: " 692 | endif 693 | 694 | results ->filter('v:val !~ "\/$"') 695 | ->filter((_, val) => filereadable(expand(val))) 696 | ->map((_, val) => fnamemodify(val, ':.')) 697 | ShowPopup(results, search_type, title) 698 | endif 699 | enddef 700 | 701 | export def FindDir() 702 | InitScriptLocalVars() 703 | if getcwd() == expand('~') 704 | Echoerr("You are in your home folder. Too many results.") 705 | return 706 | endif 707 | 708 | what = input($"current folder: '{fnamemodify(getcwd(), ':~')}'\n 709 | \Folder name to search ('enter' for all): ") 710 | var hidden = what[0] == '.' ? '' : '*' 711 | 712 | var results = getcompletion($'**/{hidden}{what}', 'dir', true) 713 | 714 | if empty(results) 715 | echo $"'{what}' pattern not found!" 716 | else 717 | # OBS: the title MUST have filepath followed by \s because it is used to 718 | # reconstruct the full path filename 719 | var title = $" {fnamemodify(getcwd(), ':~')} - Directories '{what}': " 720 | if empty(what) 721 | title = $" {fnamemodify(getcwd(), ':~')}: " 722 | endif 723 | ShowPopup(results, 'dir', title) 724 | endif 725 | enddef 726 | 727 | def Qf2Results(): list 728 | var qf_results = getqflist() 729 | var results = qf_results 730 | ->mapnew((_, val) => ($'{fnamemodify(bufname(val.bufnr), ':p')}' 731 | .. $':{val.lnum}:{val.text}')) 732 | return results 733 | enddef 734 | 735 | export def Vimgrep() 736 | InitScriptLocalVars() 737 | # Guard 738 | if getcwd() == expand('~') 739 | Echoerr("You are in your home folder. Too many results.") 740 | return 741 | endif 742 | 743 | # Main 744 | if grep_inc_search 745 | GrepInBufferHighlight() 746 | endif 747 | what = input($"current folder: '{fnamemodify(getcwd(), ':~')}'\n 748 | \String to find: ") 749 | GrepInBufferHighlightClear() 750 | if empty(what) 751 | return 752 | endif 753 | 754 | items = input($"\nin which files ('empty' for current file, 755 | \ '*' for all files): ", '*.') 756 | if empty(items) 757 | items = '%' 758 | search_dir = '' 759 | else 760 | # vimgrep does not like non-escaped spaces. We also have to remove the 761 | # final \ or / in case the user type a folder with a final / or \, 762 | # e.g. '~\Saved Games\' shall be replaced with '~\Saved\ Games' 763 | var current_wildmenu = &wildmenu 764 | set nowildmenu 765 | search_dir = input($"\nin which folder(s) (you can use 'tab'): ", './**', 766 | 'dir') 767 | ->escape(' ') 768 | ->substitute('\v(\\|/)$', '', '') 769 | &wildmenu = current_wildmenu 770 | endif 771 | 772 | var vimgrep_options = input("\nVimgrep options (g = every match, " 773 | .. "f = fuzzy): ", 'g') 774 | if empty(vimgrep_options) 775 | return 776 | endif 777 | 778 | # 'j' is to avoid jumping on the first match 779 | var cmd = $'vimgrep /{what}/j{vimgrep_options} {search_dir}/{items}' 780 | exe cmd 781 | var results = Qf2Results()->mapnew((_, val) => fnamemodify(val, ':.')) 782 | var title = $" {fnamemodify(getcwd(), ':~')}: " 783 | ShowPopup(results, 'vimgrep', title) 784 | enddef 785 | 786 | # These two functions are used to mimic the in_search feature for 787 | # GrepInBuffer() function. 788 | var match_id = 0 789 | def GrepInBufferHighlight() 790 | augroup SEARCH_HI | autocmd! 791 | autocmd CmdlineChanged @ { 792 | if match_id > 0 793 | matchdelete(match_id) 794 | endif 795 | # cursor(1, 1) 796 | var line_nr = search(getcmdline(), 'w') 797 | var pattern = getcmdline() 798 | # Highlight only the current line 799 | var what_to_match = $'\%{line_nr}l{pattern}' 800 | # var what_to_match = $'{pattern}' 801 | match_id = matchadd('IncSearch', what_to_match) 802 | # Highlight all the matches instead or the current line 803 | # match_id = matchadd('Search', getcmdline()) 804 | redraw 805 | } 806 | autocmd CmdlineLeave @ { 807 | if match_id > 0 808 | matchdelete(match_id) 809 | match_id = 0 810 | endif 811 | } 812 | augroup END 813 | enddef 814 | 815 | def GrepInBufferHighlightClear() 816 | if exists("#SEARCH_HI") 817 | autocmd! SEARCH_HI 818 | augroup! SEARCH_HI 819 | endif 820 | if match_id > 0 821 | matchdelete(match_id) 822 | endif 823 | enddef 824 | 825 | export def GrepInBuffer(what_user: string = '') 826 | InitScriptLocalVars() 827 | # The format is like grep, i.e. filename:linenumber: 828 | if empty(what_user) 829 | GrepInBufferHighlight() 830 | what = input("Find in current buffer: ") 831 | GrepInBufferHighlightClear() 832 | if empty(what) 833 | return 834 | endif 835 | else 836 | what = what_user 837 | endif 838 | 839 | var initial_pos = getcursorcharpos() 840 | cursor(1, 1) 841 | var curr_line = line('.') 842 | var results = [] 843 | while curr_line != 0 844 | curr_line = search(what, 'W') 845 | add(results, $'{expand("%:.")}:{curr_line}:') 846 | endwhile 847 | remove(results, -1) 848 | setcursorcharpos(initial_pos[1], initial_pos[2], initial_pos[3]) 849 | 850 | var title = $" {fnamemodify(getcwd(), ':~')}: " 851 | ShowPopup(results, 'grep', title) 852 | enddef 853 | 854 | 855 | export def Grep() 856 | InitScriptLocalVars() 857 | # Guard 858 | if getcwd() == expand('~') 859 | Echoerr("You are in your home folder. Too many results.") 860 | return 861 | endif 862 | 863 | # Main 864 | if grep_inc_search 865 | GrepInBufferHighlight() 866 | endif 867 | what = input($"current folder: '{fnamemodify(getcwd(), ':~')}'\n 868 | \String to find: ") 869 | 870 | GrepInBufferHighlightClear() 871 | if empty(what) 872 | return 873 | endif 874 | 875 | items = input($"\nin which files ('*' for all files): ", '*.') 876 | var current_wildmenu = &wildmenu 877 | set nowildmenu 878 | search_dir = input($"\nin which folder (you can use 'tab'): ", 879 | \ './', 'dir')->substitute('\v(\\|/)$', '', '') 880 | if empty(search_dir) || search_dir == './' 881 | search_dir = getcwd() 882 | endif 883 | &wildmenu = current_wildmenu 884 | 885 | # Windows default 886 | var cmd_win_default = 'powershell -NoProfile -ExecutionPolicy Bypass ' 887 | .. '-Command "& { findstr /C:''' .. what .. ''' /N /S ''' 888 | .. fnamemodify($'{search_dir}\{items}', ':p') .. ''' }"' 889 | 890 | # *nix default 891 | var cmd_nix_default = $'grep -nrH --include="{items}" "{what}" {search_dir}' 892 | 893 | var cmd_win = cmd_win_default 894 | if exists('g:poptools_config') && has_key(g:poptools_config, 'grep_cmd_win') 895 | var search_dir_escaped = escape(search_dir, '\') 896 | cmd_win = g:poptools_config['grep_cmd_win'] 897 | ->substitute("{search_dir}", search_dir_escaped, 'g') 898 | ->substitute("{items}", items, 'g') 899 | ->substitute("{what}", what, 'g') 900 | endif 901 | 902 | var cmd_nix = cmd_nix_default 903 | if exists('g:poptools_config') && has_key(g:poptools_config, 'grep_cmd_nix') 904 | cmd_nix = g:poptools_config['grep_cmd_nix'] 905 | ->substitute("{search_dir}", search_dir, 'g') 906 | ->substitute("{items}", items, 'g') 907 | ->substitute("{what}", what, 'g') 908 | endif 909 | 910 | # clean up the command-line: needed! 911 | redraw 912 | 913 | # Get results 914 | var saved_grepprg = &grepprg 915 | if has('win32') 916 | &grepprg = cmd_win 917 | grep! 918 | echom getqflist({'title': 0}).title 919 | else 920 | &grepprg = cmd_nix 921 | grep! 922 | echom getqflist({'title': 0}).title 923 | endif 924 | &grepprg = saved_grepprg 925 | 926 | var qf_results = getqflist() 927 | var results = Qf2Results()->mapnew((_, val) => fnamemodify(val, ':.')) 928 | var title = $" {fnamemodify(getcwd(), ':~')}: " 929 | ShowPopup(results, 'grep', title) 930 | enddef 931 | 932 | export def Buffers() 933 | InitScriptLocalVars() 934 | var results = getcompletion('', 'buffer', true) 935 | ->map((_, val) => fnamemodify(val, ':.')) 936 | # var title = " Buffers: " 937 | ShowPopup(results, 'buffer') 938 | enddef 939 | 940 | export def Colorscheme() 941 | InitScriptLocalVars() 942 | hi link PopupSelected PmenuSel 943 | var results = getcompletion('', 'color', true) 944 | ShowPopup(results, 'colorscheme') 945 | enddef 946 | 947 | export def RecentFiles() 948 | InitScriptLocalVars() 949 | var results = copy(v:oldfiles) 950 | ->filter((_, val) => filereadable(expand(val))) 951 | ->map((_, val) => fnamemodify(val, ':.')) 952 | ShowPopup(results, 'recent_files') 953 | enddef 954 | 955 | export def CmdHistory() 956 | InitScriptLocalVars() 957 | var results = split(execute('history :'), '\n') 958 | for ii in range(0, len(results) - 1) 959 | results[ii] = substitute(results[ii], '\v^\>?\s*\d*\s*(\w*)', ':\1', 'g') 960 | endfor 961 | ShowPopup(reverse(results[1 : ]), 'history') 962 | enddef 963 | 964 | export def LastSearch() 965 | if empty(last_results) 966 | Echoerr('No last search results available!') 967 | else 968 | ShowPopup(last_results, last_search_type, last_title) 969 | endif 970 | enddef 971 | -------------------------------------------------------------------------------- /lib/unused.vim: -------------------------------------------------------------------------------- 1 | vim9script 2 | 3 | # --- NOT USED but useful 4 | def GetFiletypeByFilename(fname: string): string 5 | # NOT USED 6 | # Pretend to load a buffer and detect its filetype manually 7 | # Used in UpdatePreviewAlternative() 8 | 9 | # just return &filetype if buffer was already loaded 10 | if bufloaded(fname) 11 | return getbufvar(fname, '&filetype') 12 | endif 13 | 14 | new 15 | try 16 | # the `file' command never reads file data from disk 17 | # (but may read path/directory contents) 18 | # however the detection will become less accurate 19 | # as some types cannot be recognized for empty files 20 | noautocmd silent! execute 'file' fnameescape(fname) 21 | filetype detect 22 | return &filetype 23 | finally 24 | bwipeout! 25 | endtry 26 | return '' 27 | enddef 28 | 29 | def UpdatePreviewAlternative(main_id: number, preview_id: number, search_type: string) 30 | # NOT USED 31 | # Alternative way for setting the filetype in the preview window. 32 | # It works with GetFiletypeByFilename() and it is slower 33 | var idx = line('.', main_id) 34 | var highlighted_line = getbufline(winbufnr(main_id), idx)[0] 35 | var buf_lines = readfile(expand(highlighted_line), '', popup_height) 36 | 37 | var buf_filetype = GetFiletypeByFilename(highlighted_line) 38 | 39 | # Clean the preview 40 | popup_settext(preview_id, repeat([""], popup_height)) 41 | # populate the preview 42 | # TODO readfile gives me folded content 43 | popup_settext(preview_id, buf_lines) 44 | # Syntax highlight 45 | win_execute(preview_id, $'&filetype = "{buf_filetype}"') 46 | 47 | # Set preview ID title 48 | var preview_id_opts = popup_getoptions(preview_id) 49 | preview_id_opts.title = $' {highlighted_line} ' 50 | popup_setoptions(preview_id, preview_id_opts) 51 | enddef 52 | # ------------ END NOT USED ----------- 53 | -------------------------------------------------------------------------------- /plugin/vim_poptools.vim: -------------------------------------------------------------------------------- 1 | vim9script noclear 2 | 3 | # Slam stuff in a popup 4 | # Maintainer: Ubaldo Tiberi 5 | # License: BSD3-Clause 6 | # 7 | # The architecture is fairly easy 8 | # 1. API functions are used to create a "results" var 9 | # 2. Such a "results" variable is placed into a popup 10 | # 3. Depending on the type of search, the way the results are displayed into 11 | # popups and what the callback function should do may change. 12 | 13 | if !has('vim9script') || v:version < 900 14 | # Needs Vim version 9.0 and above 15 | echo "You need at least Vim 9.0" 16 | finish 17 | endif 18 | 19 | if exists('g:vim_poptools_loaded') 20 | finish 21 | endif 22 | g:vim_poptools_loaded = true 23 | 24 | import autoload "../lib/funcs.vim" 25 | 26 | command! PoptoolsFindFile funcs.FindFile('file') 27 | command! PoptoolsFindFileInPath funcs.FindFile('file_in_path') 28 | command! PoptoolsFindDir funcs.FindDir() 29 | command! PoptoolsBuffers funcs.Buffers() 30 | command! PoptoolsRecentFiles funcs.RecentFiles() 31 | command! PoptoolsCmdHistory funcs.CmdHistory() 32 | command! PoptoolsGrep funcs.Grep() 33 | command! -nargs=? PoptoolsGrepInBuffer funcs.GrepInBuffer() 34 | command! PoptoolsVimgrep funcs.Vimgrep() 35 | command! PoptoolsColorscheme funcs.Colorscheme() 36 | command! PoptoolsLastSearch funcs.LastSearch() 37 | command! PoptoolsKill funcs.ClosePopups() 38 | -------------------------------------------------------------------------------- /vim_poptools.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ubaldot/vim-poptools/9378c10bf5fc6a9d6eba1f49fb9b44e6a176ba86/vim_poptools.gif --------------------------------------------------------------------------------