├── .dockerignore ├── .gitignore ├── .luacheckrc ├── .rubocop.yml ├── .travis.yml ├── .vintrc.yaml ├── Gemfile ├── Gemfile.lock ├── Makefile ├── README.md ├── autoload ├── esearch.vim ├── esearch │ ├── adapter │ │ ├── ack.vim │ │ ├── ag.vim │ │ ├── base.vim │ │ ├── git.vim │ │ ├── gogrep.vim │ │ ├── grep.vim │ │ ├── parse │ │ │ ├── lua.vim │ │ │ └── viml.vim │ │ ├── pt.vim │ │ ├── rg.vim │ │ └── semgrep.vim │ ├── async.vim │ ├── backend │ │ ├── nvim.vim │ │ ├── system.vim │ │ └── vim8.vim │ ├── buf.vim │ ├── cache │ │ ├── expiring.vim │ │ └── lru.vim │ ├── cmdline.vim │ ├── compat │ │ ├── filemanager │ │ │ ├── base.vim │ │ │ ├── defx.vim │ │ │ ├── dirvish.vim │ │ │ ├── fern.vim │ │ │ ├── nerdtree.vim │ │ │ └── netranger.vim │ │ └── visual_multi.vim │ ├── config.vim │ ├── debug.vim │ ├── emphasis.vim │ ├── ftdetect.vim │ ├── git.vim │ ├── has.vim │ ├── highlight.vim │ ├── keymap.vim │ ├── let.vim │ ├── log.vim │ ├── middleware │ │ ├── adapter.vim │ │ ├── cwd.vim │ │ ├── deprecations.vim │ │ ├── exec.vim │ │ ├── filemanager.vim │ │ ├── id.vim │ │ ├── input.vim │ │ ├── map.vim │ │ ├── name.vim │ │ ├── paths.vim │ │ ├── prewarm.vim │ │ ├── remember.vim │ │ ├── splice_pattern.vim │ │ └── warnings.vim │ ├── middleware_stack.vim │ ├── operator.vim │ ├── options.vim │ ├── out │ │ ├── qflist.vim │ │ ├── win.vim │ │ └── win │ │ │ ├── appearance │ │ │ ├── annotations.vim │ │ │ ├── ctx_syntax.vim │ │ │ ├── cursor_linenr.vim │ │ │ └── matches.vim │ │ │ ├── diff.vim │ │ │ ├── fold.vim │ │ │ ├── header.vim │ │ │ ├── jumps.vim │ │ │ ├── matches.vim │ │ │ ├── modifiable.vim │ │ │ ├── modifiable │ │ │ └── cmdline.vim │ │ │ ├── open.vim │ │ │ ├── preview │ │ │ ├── floating.vim │ │ │ └── split.vim │ │ │ ├── render │ │ │ ├── lua.vim │ │ │ └── viml.vim │ │ │ ├── repo │ │ │ └── ctx.vim │ │ │ ├── textobj.vim │ │ │ ├── update.vim │ │ │ └── view_data.vim │ ├── pattern.vim │ ├── pattern │ │ ├── literal2vim.vim │ │ ├── pcre2vim.vim │ │ ├── vim2literal.vim │ │ └── vim2pcre.vim │ ├── polyfill.vim │ ├── prefill.vim │ ├── preview.vim │ ├── preview │ │ ├── buf.vim │ │ ├── nvim │ │ │ ├── opener.vim │ │ │ └── popup.vim │ │ ├── opener_base.vim │ │ ├── popup_base.vim │ │ ├── shape.vim │ │ └── vim │ │ │ ├── opener.vim │ │ │ └── popup.vim │ ├── repeat.vim │ ├── shell.vim │ ├── stderr.vim │ ├── ui.vim │ ├── ui │ │ ├── app.vim │ │ ├── complete │ │ │ ├── base.vim │ │ │ ├── filetypes.vim │ │ │ ├── paths.vim │ │ │ └── search.vim │ │ ├── controllers │ │ │ ├── filetype_input.vim │ │ │ ├── menu.vim │ │ │ ├── path_input.vim │ │ │ ├── search_input.vim │ │ │ └── selection.vim │ │ ├── menu │ │ │ ├── after_entry.vim │ │ │ ├── before_entry.vim │ │ │ ├── case_entry.vim │ │ │ ├── context_entry.vim │ │ │ ├── filetype_entry.vim │ │ │ ├── menu.vim │ │ │ ├── path_entry.vim │ │ │ ├── regex_entry.vim │ │ │ ├── textobj_entry.vim │ │ │ └── unsigned_int_entry.vim │ │ └── prompt │ │ │ ├── case.vim │ │ │ ├── configurations.vim │ │ │ ├── current_pattern.vim │ │ │ ├── filetype.vim │ │ │ ├── path.vim │ │ │ ├── regex.vim │ │ │ ├── search.vim │ │ │ └── textobj.vim │ ├── undotree.vim │ ├── unicode.vim │ ├── util.vim │ ├── win.vim │ ├── writer.vim │ └── xargs.vim ├── health │ └── esearch.vim ├── unite │ └── sources │ │ └── outline │ │ └── defaults │ │ └── esearch.vim └── vital │ ├── _esearch.vim │ ├── _esearch │ ├── Async │ │ ├── Later.vim │ │ └── Promise.vim │ ├── Data │ │ ├── Dict.vim │ │ ├── List.vim │ │ ├── OrderedSet.vim │ │ └── String.vim │ ├── Mapping.vim │ ├── Prelude.vim │ ├── System │ │ └── Filepath.vim │ ├── Text │ │ ├── Lexer.vim │ │ └── Parser.vim │ └── Vim │ │ ├── Buffer.vim │ │ ├── BufferManager.vim │ │ ├── Guard.vim │ │ ├── Highlight.vim │ │ ├── Message.vim │ │ └── Type.vim │ ├── esearch.vim │ └── esearch.vital ├── doc └── esearch.txt ├── lua └── esearch │ ├── nvim.lua │ ├── nvim │ ├── appearance │ │ ├── annotations.lua │ │ ├── cursor_linenr.lua │ │ ├── matches.lua │ │ └── ui.lua │ ├── parse.lua │ ├── render.lua │ └── util.lua │ ├── shared │ ├── adapter │ │ └── parse.lua │ ├── outline.lua │ └── util.lua │ ├── vim.lua │ └── vim │ ├── parse.lua │ ├── render.lua │ └── util.lua ├── plugin └── esearch.vim ├── spec ├── api.vader ├── appearance.vader ├── buf.vader ├── diff.vader ├── docs.vader ├── filemanager.vader ├── gogrep.vader ├── helper.vader ├── input_assisting.vader ├── jumps.vader ├── keymap.vader ├── known_issues.rb ├── let.vader ├── lib │ ├── debug_spec.rb │ ├── dump_editor_state_on_error_formatter_spec.rb │ ├── known_issues_spec.rb │ ├── viml_value │ │ ├── lexer_spec.rb │ │ ├── load_spec.rb │ │ ├── parser_spec.rb │ │ └── serialization_helpers_spec.rb │ └── viml_value_spec.rb ├── middleware.vader ├── modifiable.vader ├── modifiable_cmdline.vader ├── multiple_files_undo.vader ├── plugin │ ├── adapter_spec.rb │ ├── backend │ │ ├── paths_spec.rb │ │ └── patterns_spec.rb │ ├── commandline │ │ ├── input_spec.rb │ │ └── menu_spec.rb │ ├── compatibility │ │ └── visual_multi_spec.rb │ ├── live_update_spec.rb │ ├── shared_examples │ │ └── abortable_backend.rb │ └── window │ │ ├── modifiable │ │ └── commandline │ │ │ └── substitute_spec.rb │ │ └── open_spec.rb ├── preview.vader ├── semgrep.vader ├── single_file_undo.vader ├── spec_helper.rb ├── support │ ├── api │ │ ├── esearch │ │ │ ├── configuration.rb │ │ │ ├── core.rb │ │ │ ├── facade.rb │ │ │ ├── platform.rb │ │ │ ├── quick_fix.rb │ │ │ ├── stubbed_output.rb │ │ │ ├── window.rb │ │ │ └── window │ │ │ │ ├── entries_parser.rb │ │ │ │ ├── entry.rb │ │ │ │ ├── header_parser.rb │ │ │ │ ├── missing_entry.rb │ │ │ │ └── parser.rb │ │ ├── mixins │ │ │ ├── become_truthy_within_timeout.rb │ │ │ ├── rollback_state.rb │ │ │ └── throttling.rb │ │ └── visual_multi.rb │ ├── cache_store.rb │ ├── clean_caller.rb │ ├── client.rb │ ├── configuration.rb │ ├── decorator_base.rb │ ├── fixtures │ │ ├── lazy_directory.rb │ │ ├── lazy_file.rb │ │ └── lazy_swap_file.rb │ ├── helpers │ │ ├── commandline.rb │ │ ├── file_system.rb │ │ ├── modifiable.rb │ │ ├── modifiable │ │ │ ├── columnwise.rb │ │ │ └── commandline.rb │ │ ├── open.rb │ │ ├── output.rb │ │ ├── pattern │ │ │ └── convert_from_vim.rb │ │ ├── report_editor_state_on_error.rb │ │ ├── running_processes.rb │ │ ├── shell.rb │ │ ├── strings.rb │ │ ├── undotree.rb │ │ ├── vim.rb │ │ ├── viml_value.rb │ │ └── visual_multi.rb │ ├── inflections.rb │ ├── known_issues.rb │ ├── lib │ │ ├── debug.rb │ │ ├── dump_editor_state_on_error_formatter.rb │ │ ├── editor.rb │ │ ├── editor │ │ │ └── read │ │ │ │ ├── base.rb │ │ │ │ ├── batched.rb │ │ │ │ ├── batched │ │ │ │ ├── batch.rb │ │ │ │ └── container.rb │ │ │ │ └── eager.rb │ │ ├── viml_value.rb │ │ └── viml_value │ │ │ ├── .gitignore │ │ │ ├── ast.rb │ │ │ ├── lexer.rl │ │ │ ├── parser.y │ │ │ ├── serializable │ │ │ ├── expression.rb │ │ │ ├── function_call.rb │ │ │ └── identifier.rb │ │ │ ├── serialization_helpers.rb │ │ │ ├── tree_builder.rb │ │ │ ├── types │ │ │ ├── dict_recursive_ref.rb │ │ │ ├── funcref.rb │ │ │ ├── list_recursive_ref.rb │ │ │ └── none.rb │ │ │ └── visitors │ │ │ ├── to_ruby.rb │ │ │ ├── to_vim.rb │ │ │ └── to_vim7.rb │ ├── platform_check.rb │ ├── profiling │ │ ├── .gitignore │ │ ├── generate_html_files.sh │ │ └── profile.vim │ ├── scripts │ │ ├── search_in_infinite_random_stdin.sh │ │ └── sort_search_results.sh │ ├── setup │ │ ├── Dockerfile │ │ ├── entrypoint.sh │ │ ├── site.yml │ │ └── vars │ │ │ ├── darwin.yml │ │ │ └── debian.yml │ ├── subscriptions.rb │ ├── viml │ │ ├── autoload │ │ │ └── esearch │ │ │ │ └── out │ │ │ │ └── stubbed.vim │ │ ├── vader.vim │ │ └── vimrunner.vim │ └── vimrunner_spy.rb ├── ui.vader ├── unit │ ├── buf_spec.rb │ ├── context_manager │ │ └── let_spec.rb │ ├── ftdetect_spec.rb │ ├── pattern │ │ ├── pcre2vim_spec.rb │ │ ├── vim2literal_spec.rb │ │ └── vim2pcre_spec.rb │ ├── shell │ │ ├── fnameescape_spec.rb │ │ └── split_spec.rb │ └── util │ │ ├── clip_spec.rb │ │ ├── escape_kind_spec.rb │ │ └── has_upper_spec.rb ├── write.vader ├── write_conflicts.vader └── xargs.vader └── syntax ├── es_ctx_c.vim ├── es_ctx_css.vim ├── es_ctx_dockerfile.vim ├── es_ctx_generic.vim ├── es_ctx_go.vim ├── es_ctx_groovy.vim ├── es_ctx_haskell.vim ├── es_ctx_hcl.vim ├── es_ctx_html.vim ├── es_ctx_java.vim ├── es_ctx_javascript.vim ├── es_ctx_javascriptreact.vim ├── es_ctx_json.vim ├── es_ctx_lisp.vim ├── es_ctx_php.vim ├── es_ctx_python.vim ├── es_ctx_ruby.vim ├── es_ctx_scala.vim ├── es_ctx_sh.vim ├── es_ctx_toml.vim ├── es_ctx_typescript.vim ├── es_ctx_typescriptreact.vim ├── es_ctx_vim.vim ├── es_ctx_xml.vim ├── es_ctx_yaml.vim ├── esearch.vim └── esearch_test.vim /.dockerignore: -------------------------------------------------------------------------------- 1 | /spec/support/vim_plugins 2 | /spec/support/bin 3 | !/spec/support/bin/.keep 4 | /.git 5 | 6 | # screenshots 7 | /*.png 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /spec/support/vim_plugins 2 | /spec/fixtures 3 | !/spec/fixtures/.keep 4 | /spec/support/bin 5 | /failed_specs.txt 6 | /.pry_history 7 | /*.log 8 | 9 | # screenshots 10 | /*.png 11 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | -- 211 Unused local variable 2 | -- 113 Accessing an undefined global variable. 3 | -- 212 Unused argument. 4 | -- 213 Unused loop variable. (disable for ipairs) 5 | 6 | ignore = {"113/esearch", "113/vim", "211/_.*", "212/_.*", "213/_"} 7 | -------------------------------------------------------------------------------- /.vintrc.yaml: -------------------------------------------------------------------------------- 1 | cmdargs: 2 | severity: style_problem 3 | color: true 4 | env: 5 | neovim: true 6 | 7 | policies: 8 | ProhibitImplicitScopeVariable: 9 | enabled: false 10 | # TODO 11 | ProhibitAutocmdWithNoGroup: 12 | enabled: false 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | ruby '2.7.0' 4 | 5 | gem 'activesupport' 6 | gem 'parallel_split_test' 7 | gem 'parallel_tests' 8 | gem 'racc' 9 | gem 'rspec' 10 | gem 'rubocop' 11 | gem 'vimrunner' 12 | 13 | group :development do 14 | gem 'pry' 15 | gem 'solargraph' 16 | end 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKER_RUN = docker run --rm -v $$PWD:/app -it esearch 2 | 3 | all: testing-image serializer 4 | 5 | .PHONY: login 6 | login: 7 | $(DOCKER_RUN) bash 8 | 9 | .PHONY: setup-host 10 | setup-host: 11 | ansible-playbook spec/support/setup/site.yml 12 | 13 | .PHONY: setup-testing-image 14 | setup-testing-image: 15 | docker build -t esearch -f spec/support/setup/Dockerfile . 16 | 17 | .PHONY: setup-serializer 18 | setup-serializer: spec/support/lib/viml_value/lexer.rb spec/support/lib/viml_value/parser.rb 19 | 20 | %.rb: %.rl 21 | $(DOCKER_RUN) ragel -e -L -F0 -R -o $@ $< 22 | 23 | %.rb: %.y 24 | $(DOCKER_RUN) racc --output-file=$@ $< 25 | -------------------------------------------------------------------------------- /autoload/esearch.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#init(...) abort 2 | call esearch#util#doautocmd('User eseach_init_pre') 3 | call esearch#config#eager() 4 | 5 | let esearch = extend(extend(copy(g:esearch), {'remember': 0}), copy(get(a:, 1, {}))) 6 | try 7 | for l:Middleware in esearch.middleware.list 8 | let esearch = Middleware(esearch) 9 | endfor 10 | catch /^Cancel$/ 11 | return 12 | endtry 13 | 14 | return esearch#out#{esearch.out}#init(esearch) 15 | endfu 16 | 17 | fu! esearch#prefill(...) abort 18 | return esearch#operator#expr('esearch#prefill_op', get(a:, 1, {})) 19 | endfu 20 | 21 | fu! esearch#exec(...) abort 22 | return esearch#operator#expr('esearch#exec_op', get(a:, 1, {})) 23 | endfu 24 | 25 | fu! esearch#prefill_op(wise) abort 26 | call esearch#init(extend({'prefill': ['region'], 'region': a:wise}, get(esearch#operator#args(), 0, {}))) 27 | endfu 28 | 29 | fu! esearch#exec_op(wise) abort 30 | call esearch#init(extend({'prefill': ['region'], 'region': a:wise, 'force_exec': 1}, get(esearch#operator#args(), 0, {}))) 31 | endfu 32 | 33 | " DEPRECATED 34 | fu! esearch#map(lhs, rhs) abort 35 | let g:esearch = get(g:, 'esearch', {}) 36 | let g:esearch = extend(g:esearch, {'pending_warnings': []}, 'keep') 37 | 38 | if a:rhs ==# 'esearch' 39 | call esearch#util#deprecate('esearch#map, use map {keys} (esearch)') 40 | call esearch#keymap#set('n', a:lhs, '(esearch)', {'silent': 1}) 41 | elseif a:rhs ==# 'esearch-word-under-cursor' 42 | call esearch#util#deprecate("esearch#map with 'esearch-word-under-cursor', use map {keys} (operator-esearch-prefill)iw") 43 | call esearch#keymap#set('n', a:lhs, '(esearch-operator)iw', {'silent': 1}) 44 | else 45 | call esearch#util#deprecate('esearch#map, see :help esearch-mappings') 46 | endif 47 | endfu 48 | 49 | if !exists('g:esearch#env') 50 | let g:esearch#env = 0 " prod 51 | endif 52 | -------------------------------------------------------------------------------- /autoload/esearch/adapter/ack.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#adapter#ack#new() abort 2 | return copy(s:Ack) 3 | endfu 4 | 5 | let s:Ack = esearch#adapter#base#import() 6 | if exists('g:esearch#adapter#ack#bin') 7 | call esearch#util#deprecate('g:esearch#adapter#ack#options. Please, use g:esearch.adapters.ack.bin') 8 | let s:Ack.bin = g:esearch#adapter#ack#bin 9 | else 10 | let s:Ack.bin = 'ack' 11 | endif 12 | if exists('g:esearch#adapter#ack#options') 13 | call esearch#util#deprecate('g:esearch#adapter#ack#options. Please, use g:esearch.adapters.ack.options') 14 | let s:Ack.options = g:esearch#adapter#ack#options 15 | else 16 | let s:Ack.options = '--follow' 17 | endif 18 | let s:Ack.mandatory_options = '--nogroup --nocolor --noheading --with-filename --nobreak' 19 | call extend(s:Ack, { 20 | \ 'bool2regex': ['literal', 'pcre'], 21 | \ 'regex': { 22 | \ 'literal': {'icon': '', 'option': '--literal'}, 23 | \ 'pcre': {'icon': 'r', 'option': ''}, 24 | \ }, 25 | \ 'bool2textobj': ['none', 'word'], 26 | \ 'textobj': { 27 | \ 'none': {'icon': '', 'option': ''}, 28 | \ 'word': {'icon': 'w', 'option': '--word-regexp'}, 29 | \ }, 30 | \ 'bool2case': ['ignore', 'sensitive'], 31 | \ 'case': { 32 | \ 'ignore': {'icon': '', 'option': '--ignore-case'}, 33 | \ 'sensitive': {'icon': 's', 'option': '--no-smart-case'}, 34 | \ 'smart': {'icon': 'S', 'option': '--smart-case'}, 35 | \ } 36 | \}) 37 | 38 | " ack --help-types 39 | let s:Ack.filetypes = split('actionscript ada asm asp aspx batch cc cfmx clojure cmake coffeescript cpp csharp css dart delphi elisp elixir erlang fortran go groovy gsp haskell hh hpp html jade java js json jsp kotlin less lisp lua make markdown matlab objc objcpp ocaml perl perltest php plone pod python rake rr rst ruby rust sass scala scheme shell smalltalk smarty sql stylus svg swift tcl tex ts ttml vb verilog vhdl vim xml yaml') 40 | 41 | fu! s:Ack.filetypes2args(filetypes) abort dict 42 | return substitute(a:filetypes, '\<', '--', 'g') 43 | endfu 44 | 45 | fu! s:Ack.is_success(request) abort 46 | " later versions behaves like grep (0 - at least one matched line, 1 - no lines matched) 47 | return a:request.status == 0 48 | endfu 49 | -------------------------------------------------------------------------------- /autoload/esearch/adapter/gogrep.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#adapter#gogrep#new() abort 2 | return copy(s:Gogrep) 3 | endfu 4 | 5 | let s:Gogrep = esearch#adapter#base#import() 6 | 7 | let s:Gogrep.bin = 'gogrep' 8 | let s:Gogrep.options = '-tests' 9 | let s:Gogrep.mandatory_options = '' 10 | 11 | call extend(s:Gogrep, { 12 | \ 'regex': {}, 13 | \ 'textobj': {}, 14 | \ 'case': {}, 15 | \ 'before': 0, 16 | \ 'after': 0, 17 | \ 'parser': 'with_col', 18 | \ 'multi_pattern': 1, 19 | \ 'pattern_kinds': [ 20 | \ {'icon': '-x', 'opt': '-x ', 'regex': 0}, 21 | \ {'icon': '-g', 'opt': '-g ', 'regex': 0}, 22 | \ {'icon': '-v', 'opt': '-v ', 'regex': 0}, 23 | \ {'icon': '-a', 'opt': '-a ', 'regex': 0}, 24 | \ ], 25 | \ 'context': {'hint': 'parent nodes', 'opt': '-p'}, 26 | \}) 27 | 28 | fu! s:Gogrep.command(esearch) abort dict 29 | if empty(a:esearch.paths) 30 | let paths = self.pwd() 31 | else 32 | let paths = esearch#shell#join(a:esearch.paths) 33 | endif 34 | 35 | let context = '' 36 | if a:esearch.context > 0 | let context .= ' -p ' . a:esearch.context | endif 37 | 38 | return join([ 39 | \ self.bin, 40 | \ self.mandatory_options, 41 | \ self.options, 42 | \ context, 43 | \ a:esearch.pattern.arg, 44 | \ paths, 45 | \], ' ') 46 | endfu 47 | 48 | fu! s:Gogrep.pwd() abort dict 49 | return './...' 50 | endfu 51 | -------------------------------------------------------------------------------- /autoload/esearch/adapter/grep.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#adapter#grep#new() abort 2 | return copy(s:Grep) 3 | endfu 4 | 5 | let s:Grep = esearch#adapter#base#import() 6 | 7 | if exists('g:esearch#adapter#grep#bin') 8 | call esearch#util#deprecate('g:esearch#adapter#grep#options. Please, use g:esearch.adapters.grep.bin') 9 | let s:Grep.bin = g:esearch#adapter#grep#bin 10 | else 11 | let s:Grep.bin = 'grep' 12 | endif 13 | if exists('g:esearch#adapter#grep#options') 14 | call esearch#util#deprecate('g:esearch#adapter#grep#options. Please, use g:esearch.adapters.grep.options') 15 | let s:Grep.options = g:esearch#adapter#grep#options 16 | else 17 | " -I: don't match binary files 18 | let s:Grep.options = '-I' 19 | endif 20 | 21 | " Short options are used as they are supported more often than long ones 22 | 23 | " -n: output line numbers 24 | " -R: recursive, follow symbolic links 25 | " -H: Print the file name for each match. 26 | " -x: Line regexp 27 | let s:Grep.mandatory_options = '-H -R -n' 28 | call extend(s:Grep, { 29 | \ 'bool2regex': ['literal', 'basic'], 30 | \ 'regex': { 31 | \ 'literal': {'icon': '', 'option': '-F'}, 32 | \ 'basic': {'icon': 'G', 'option': '-G'}, 33 | \ 'extended': {'icon': 'E', 'option': '-E'}, 34 | \ 'pcre': {'icon': 'P', 'option': '-P'}, 35 | \ }, 36 | \ 'bool2textobj': ['none', 'word'], 37 | \ 'textobj': { 38 | \ 'none': {'icon': '', 'option': ''}, 39 | \ 'word': {'icon': 'w', 'option': '-w'}, 40 | \ 'line': {'icon': 'l', 'option': '-x'}, 41 | \ }, 42 | \ 'bool2case': ['ignore', 'sensitive'], 43 | \ 'case': { 44 | \ 'ignore': {'icon': '', 'option': '-i'}, 45 | \ 'sensitive': {'icon': 's', 'option': ''}, 46 | \ } 47 | \}) 48 | 49 | fu! s:Grep.is_success(request) abort 50 | " 0 if a line is match, 1 if no lines matched, > 1 are for errors 51 | return a:request.status == 0 || a:request.status == 1 52 | endfu 53 | 54 | fu! s:Grep.pwd() abort dict 55 | return '.' 56 | endfu 57 | -------------------------------------------------------------------------------- /autoload/esearch/adapter/parse/lua.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#adapter#parse#lua#import() abort 2 | return function('esearch#adapter#parse#lua#parse') 3 | endfu 4 | 5 | if g:esearch#has#nvim_lua 6 | fu! esearch#adapter#parse#lua#parse(data, from, to) abort dict 7 | return luaeval('{esearch.parse(_A[1], _A[2])}', [a:data[a:from : a:to], self._adapter.parser]) 8 | endfu 9 | else 10 | fu! esearch#adapter#parse#lua#parse(data, from, to) abort dict 11 | let [parsed, lines_delta, errors] = 12 | \ luaeval('vim.list({esearch.parse(_A.d, _A.p)})', 13 | \ {'d': a:data[a:from : a:to], 'p': self._adapter.parser}) 14 | return [parsed, float2nr(lines_delta), errors] 15 | endfu 16 | endif 17 | -------------------------------------------------------------------------------- /autoload/esearch/adapter/pt.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#adapter#pt#new() abort 2 | return copy(s:Pt) 3 | endfu 4 | 5 | let s:Pt = esearch#adapter#base#import() 6 | if exists('g:esearch#adapter#pt#bin') 7 | call esearch#util#deprecate('g:esearch#adapter#pt#options. Please, use g:esearch.adapters.pt.bin') 8 | let s:Pt.bin = g:esearch#adapter#pt#bin 9 | else 10 | let s:Pt.bin = 'pt' 11 | endif 12 | if exists('g:esearch#adapter#pt#options') 13 | call esearch#util#deprecate('g:esearch#adapter#pt#options. Please, use g:esearch.adapters.pt.options') 14 | let s:Pt.options = g:esearch#adapter#pt#options 15 | else 16 | let s:Pt.options = '--follow' 17 | endif 18 | let s:Pt.mandatory_options = '--nogroup --nocolor' 19 | " https://github.com/google/re2/wiki/Syntax 20 | call extend(s:Pt, { 21 | \ 'bool2regex': ['literal', 're2'], 22 | \ 'regex': { 23 | \ 'literal': {'icon': '', 'option': ''}, 24 | \ 're2': {'icon': 'r', 'option': '-e'}, 25 | \ }, 26 | \ 'bool2textobj': ['none', 'word'], 27 | \ 'textobj': { 28 | \ 'none': {'icon': '', 'option': ''}, 29 | \ 'word': {'icon': 'w', 'option': '--word-regexp'}, 30 | \ }, 31 | \ 'bool2case': ['ignore', 'sensitive'], 32 | \ 'case': { 33 | \ 'ignore': {'icon': '', 'option': '--ignore-case'}, 34 | \ 'sensitive': {'icon': 's', 'option': ''}, 35 | \ 'smart': {'icon': 'S', 'option': '--smart-case'}, 36 | \ } 37 | \}) 38 | 39 | fu! s:Pt.pwd() abort dict 40 | return '.' 41 | endfu 42 | 43 | fu! s:Pt.is_success(request) abort 44 | " https://github.com/monochromegane/the_platinum_searcher/issues/150 45 | return a:request.status == 0 46 | endfu 47 | -------------------------------------------------------------------------------- /autoload/esearch/adapter/semgrep.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#adapter#semgrep#new() abort 2 | return copy(s:Semgrep) 3 | endfu 4 | 5 | let s:Semgrep = esearch#adapter#base#import() 6 | 7 | let s:Semgrep.bin = 'semgrep' 8 | let s:Semgrep.options = '' 9 | let s:Semgrep.mandatory_options = '--json --quiet' 10 | 11 | call extend(s:Semgrep, { 12 | \ 'regex': {}, 13 | \ 'textobj': {}, 14 | \ 'case': {}, 15 | \ 'before': 0, 16 | \ 'after': 0, 17 | \ 'context': 0, 18 | \ 'parser': 'semgrep', 19 | \ 'multi_pattern': 1, 20 | \ 'pattern_kinds': [ 21 | \ {'icon': '-e', 'opt': '-e ', 'regex': 0}, 22 | \ ], 23 | \}) 24 | 25 | fu! s:Semgrep.command(esearch) abort dict 26 | 27 | if empty(a:esearch.paths) 28 | let paths = self.pwd() 29 | else 30 | let paths = esearch#shell#join(a:esearch.paths) 31 | endif 32 | 33 | return join([ 34 | \ self.bin, 35 | \ self.mandatory_options, 36 | \ self.options, 37 | \ a:esearch.pattern.arg, 38 | \ self.filetypes2args(a:esearch.filetypes), 39 | \ '--', 40 | \ paths, 41 | \], ' ') 42 | endfu 43 | 44 | let s:Semgrep.filetypes = split('go java js json python ruby c ocaml') 45 | 46 | fu! s:Semgrep.filetypes2args(filetypes) abort dict 47 | return substitute(a:filetypes, '\<', '--lang=', 'g') 48 | endfu 49 | -------------------------------------------------------------------------------- /autoload/esearch/async.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#async#debounce(callback, wait, ...) abort 2 | return s:Trailing.new(a:callback, a:wait, a:000) 3 | endfu 4 | 5 | let s:Trailing = {'t': -1} 6 | 7 | fu! s:Trailing.new(callback, wait, ...) abort dict 8 | let instance = copy(self) 9 | let instance.w = a:wait 10 | " Wrapping into a list prevents from unbinding self. The same old story as 11 | " with js this.*. 12 | let instance.c = [a:callback] 13 | 14 | return instance 15 | endfu 16 | 17 | fu! s:Trailing.cancel(...) abort dict 18 | call timer_stop(self.t) 19 | endfu 20 | 21 | fu! s:Trailing.apply(...) abort dict 22 | call timer_stop(self.t) 23 | let a = a:000 24 | let self.t = timer_start(self.w, {_ -> call(self.c[0], a) }) 25 | endfu 26 | -------------------------------------------------------------------------------- /autoload/esearch/backend/system.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#backend#system#init(cwd, adapter, cmd, ...) abort 2 | let request = { 3 | \ 'command': a:cmd, 4 | \ 'cwd': a:cwd, 5 | \ 'adapter': a:adapter, 6 | \ 'data': [], 7 | \ 'errors': [], 8 | \ 'is_consumed': function('is_consumed'), 9 | \ 'async': 0, 10 | \ 'cursor': 0, 11 | \ 'status': 0, 12 | \ 'finished': 0, 13 | \ 'cb': {}, 14 | \} 15 | 16 | return request 17 | endfu 18 | 19 | fu! s:is_consumed(wait) abort dict 20 | return self.finished 21 | endfu 22 | 23 | fu! esearch#backend#system#exec(request) abort 24 | let cwd = esearch#win#lcd(a:request.cwd) 25 | try 26 | let a:request.data = split(system(a:request.command), '\r\=\n') 27 | let a:request.status = v:shell_error 28 | let a:request.finished = 1 29 | 30 | if a:request.status !=# 0 31 | let a:request.errors = a:request.data 32 | call esearch#stderr#incremental(a:request.adapter, a:request.errors) 33 | redraw! 34 | endif 35 | finally 36 | call cwd.restore() 37 | endtry 38 | endfu 39 | 40 | fu! esearch#backend#system#abort(...) abort 41 | endfu 42 | -------------------------------------------------------------------------------- /autoload/esearch/cache/expiring.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#cache#expiring#new(opts) abort 2 | return s:Expiring.new(a:opts) 3 | endfu 4 | 5 | let s:Expiring = {} 6 | 7 | fu! s:Expiring.new(opts) abort dict 8 | let instance = copy(self) 9 | let instance.max_age = a:opts.max_age 10 | let instance.size = a:opts.size 11 | let instance.ages = {} 12 | let instance.data = {} 13 | 14 | return instance 15 | endfu 16 | 17 | fu! s:Expiring.has(key) abort dict 18 | return localtime() - get(self.ages, a:key) < self.max_age 19 | endfu 20 | 21 | fu! s:Expiring.get(key) abort dict 22 | if !self.has(a:key) 23 | return 24 | endif 25 | 26 | return self.data[a:key] 27 | endfu 28 | 29 | fu! s:Expiring.set(key, value) abort dict 30 | let self.data[a:key] = a:value 31 | let self.ages[a:key] = localtime() 32 | 33 | if len(self.data) >= self.size 34 | call self.evict() 35 | endif 36 | endfu 37 | 38 | " Random strategy that keeps 25% of data. Should be enough for now to prevent 39 | " bloats 40 | fu! s:Expiring.evict() abort dict 41 | for key in keys(self.ages)[ : len(self.ages) / 4] 42 | call self.remove(key) 43 | endfor 44 | endfu 45 | 46 | fu! s:Expiring.remove(key) abort dict 47 | call remove(self.data, a:key) 48 | call remove(self.ages, a:key) 49 | endfu 50 | -------------------------------------------------------------------------------- /autoload/esearch/cache/lru.vim: -------------------------------------------------------------------------------- 1 | let s:OrderedSet = vital#esearch#import('Data.OrderedSet') 2 | 3 | fu! esearch#cache#lru#new(size) abort 4 | return s:LRU.new(a:size) 5 | endfu 6 | 7 | let s:LRU = {} 8 | 9 | fu! s:LRU.new(size, ...) abort dict 10 | let instance = copy(self) 11 | let instance.size = a:size 12 | let instance.queue = s:OrderedSet.new() 13 | let instance.data = {} 14 | 15 | return instance 16 | endfu 17 | 18 | fu! s:LRU.has(key) abort dict 19 | return self.queue.has(a:key) 20 | endfu 21 | 22 | fu! s:LRU.get(key) abort dict 23 | call self.queue.remove(a:key) 24 | call self.queue.unshift(a:key) 25 | 26 | return self.data[string(a:key)] 27 | endfu 28 | 29 | fu! s:LRU.set(key, value) abort dict 30 | if !self.has(a:key) && self.queue.size() >= self.size 31 | call self.remove(self.queue.to_list()[-1]) 32 | endif 33 | 34 | call self.queue.unshift(a:key) 35 | let self.data[string(a:key)] = a:value 36 | endfu 37 | 38 | fu! s:LRU.remove(key) abort dict 39 | call self.queue.remove(a:key) 40 | let value = remove(self.data, string(a:key)) 41 | 42 | " Trigger the destructor if it's available 43 | if type(value) ==# type({}) && has_key(value, 'remove') 44 | call value.remove() 45 | endif 46 | endfu 47 | -------------------------------------------------------------------------------- /autoload/esearch/compat/filemanager/base.vim: -------------------------------------------------------------------------------- 1 | let s:Filepath = vital#esearch#import('System.Filepath') 2 | 3 | fu! esearch#compat#filemanager#base#import() abort 4 | return copy(s:Base) 5 | endfu 6 | 7 | let s:Base = {} 8 | 9 | fu! s:Base.paths_in_range(begin, end) abort 10 | let paths = [] 11 | 12 | let view = winsaveview() 13 | try 14 | for line in range(a:begin, a:end) 15 | call cursor(line, 0) 16 | let paths += [self.path_under_cursor()] 17 | endfor 18 | finally 19 | call winrestview(view) 20 | endtry 21 | 22 | return paths 23 | endfu 24 | 25 | fu! s:Base.nearest_dir_or_selected_nodes() abort 26 | let path = self.path_under_cursor() 27 | return isdirectory(path) ? [path] : [s:Filepath.dirname(path)] 28 | endfu 29 | -------------------------------------------------------------------------------- /autoload/esearch/compat/filemanager/defx.vim: -------------------------------------------------------------------------------- 1 | let s:Filepath = vital#esearch#import('System.Filepath') 2 | 3 | fu! esearch#compat#filemanager#defx#import() abort 4 | return s:Defx 5 | endfu 6 | 7 | let s:Defx = esearch#compat#filemanager#base#import() 8 | 9 | fu! s:Defx.path_under_cursor() abort 10 | return defx#get_candidate().action__path 11 | endfu 12 | -------------------------------------------------------------------------------- /autoload/esearch/compat/filemanager/dirvish.vim: -------------------------------------------------------------------------------- 1 | let s:Filepath = vital#esearch#import('System.Filepath') 2 | 3 | fu! esearch#compat#filemanager#dirvish#import() abort 4 | return s:Dirvish 5 | endfu 6 | 7 | let s:Dirvish = esearch#compat#filemanager#base#import() 8 | 9 | fu! s:Dirvish.path_under_cursor() abort 10 | return getline('.') 11 | endfu 12 | 13 | fu! s:Dirvish.paths_in_range(begin, end) abort 14 | return getline(a:begin, a:end) 15 | endfu 16 | -------------------------------------------------------------------------------- /autoload/esearch/compat/filemanager/fern.vim: -------------------------------------------------------------------------------- 1 | let s:Filepath = vital#esearch#import('System.Filepath') 2 | 3 | fu! esearch#compat#filemanager#fern#import() abort 4 | return s:Fern 5 | endfu 6 | 7 | let s:Fern = esearch#compat#filemanager#base#import() 8 | 9 | fu! s:Fern.path_under_cursor() abort 10 | return fern#helper#new().sync.get_cursor_node()._path 11 | endfu 12 | 13 | fu! s:Fern.nearest_dir_or_selected_nodes() abort 14 | let nodes = fern#helper#new().sync.get_selected_nodes() 15 | if len(nodes) == 1 16 | let path = nodes[0]._path 17 | return isdirectory(path) ? [path] : [s:Filepath.dirname(path)] 18 | else 19 | return map(nodes, 'v:val._path') 20 | endif 21 | endfu 22 | -------------------------------------------------------------------------------- /autoload/esearch/compat/filemanager/nerdtree.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#compat#filemanager#nerdtree#import() abort 2 | return s:NERDTree 3 | endfu 4 | 5 | let s:NERDTree = esearch#compat#filemanager#base#import() 6 | 7 | fu! s:NERDTree.nearest_dir_or_selected_nodes() abort 8 | let path = s:get_selected_node().path 9 | if !path.isDirectory 10 | let path = path.getParent() 11 | endif 12 | 13 | return [path.str({'escape': 0})] 14 | endfu 15 | 16 | fu! s:NERDTree.path_under_cursor() abort 17 | return s:get_selected_node().path.str({'escape': 0}) 18 | endfu 19 | 20 | fu! s:get_selected_node() abort 21 | let selected = g:NERDTreeFileNode.GetSelected() 22 | return empty(selected) ? g:NERDTreeFileNode.GetRootForTab() : selected 23 | endfu 24 | -------------------------------------------------------------------------------- /autoload/esearch/compat/filemanager/netranger.vim: -------------------------------------------------------------------------------- 1 | let s:Filepath = vital#esearch#import('System.Filepath') 2 | 3 | fu! esearch#compat#filemanager#netranger#import() abort 4 | return s:NETRanger 5 | endfu 6 | 7 | let s:NETRanger = esearch#compat#filemanager#base#import() 8 | 9 | fu! s:NETRanger.path_under_cursor() abort 10 | return netranger#api#cur_node_path() 11 | endfu 12 | 13 | fu! s:NETRanger.paths_in_range(begin, end) abort 14 | let paths = [] 15 | 16 | let view = winsaveview() 17 | try 18 | for line in range(a:begin, a:end) 19 | call cursor(line, 0) 20 | call esearch#util#doautocmd('CursorMoved') 21 | let paths += [self.path_under_cursor()] 22 | endfor 23 | finally 24 | call winrestview(view) 25 | call esearch#util#doautocmd('CursorMoved') 26 | endtry 27 | 28 | return paths 29 | endfu 30 | -------------------------------------------------------------------------------- /autoload/esearch/debug.vim: -------------------------------------------------------------------------------- 1 | if !exists('g:esearch#debug#log_output') 2 | let g:esearch#debug#output = '/tmp/esearch.log' 3 | endif 4 | 5 | fu! esearch#debug#log(...) abort 6 | if g:esearch#env is# 0 7 | " ignore if accidentially called in production 8 | return 9 | endif 10 | 11 | if g:esearch#debug#log_output is# 1 " to resemble posix STDOUT_FILENO 12 | echo a:000 13 | else 14 | let items = join(map(deepcopy(a:000), 'string(v:val)'), ', ') 15 | call writefile(['[DEBUG] [' . items . ']'], g:esearch#debug#log_output, 'a') 16 | endif 17 | endfu 18 | -------------------------------------------------------------------------------- /autoload/esearch/git.vim: -------------------------------------------------------------------------------- 1 | let s:Prelude = vital#esearch#import('Prelude') 2 | 3 | aug esearch_git 4 | au! 5 | au BufReadCmd esearchgit://* cal esearch#git#read_cmd(expand('')) 6 | aug END 7 | 8 | fu! esearch#git#url(filename, dir) abort 9 | return 'esearchgit://' . simplify(a:dir) . '//' . simplify(a:filename) 10 | endfu 11 | 12 | fu! esearch#git#dir(cwd) abort 13 | return fnamemodify(esearch#util#find_up(a:cwd, ['.git/HEAD']), ':h') 14 | endfu 15 | 16 | fu! esearch#git#read_cmd(path) abort 17 | let undolevels = esearch#let#restorable({'&l:undolevels': -1}) 18 | setlocal noswapfile buftype=nowrite readonly 19 | call esearch#util#doautocmd('BufReadPre') 20 | let [dir, filename] = split(a:path, '//')[1:2] 21 | let dir = s:Prelude.substitute_path_separator(shellescape(dir, 1)) 22 | let filename = s:Prelude.substitute_path_separator(shellescape(filename, 1)) 23 | " lockmarks to preserve the cursor location on open 24 | exe 'lockmarks 0read ++edit !git -C' dir 'cat-file -p' filename 25 | keepjumps silent $delete_ 26 | call undolevels.restore() 27 | call esearch#util#doautocmd('BufReadPost') 28 | endfu 29 | -------------------------------------------------------------------------------- /autoload/esearch/log.vim: -------------------------------------------------------------------------------- 1 | let s:Log = copy(vital#esearch#import('Vim.Message')) 2 | 3 | fu! esearch#log#import() abort 4 | return s:Log 5 | endfu 6 | 7 | fu! s:Log.info(msg) abort dict 8 | for m in split(a:msg, "\n") 9 | echomsg m 10 | endfor 11 | endfu 12 | 13 | fu! s:Log.echon(hl, msg) abort dict 14 | execute 'echohl' a:hl 15 | try 16 | echon a:msg 17 | finally 18 | echohl None 19 | endtry 20 | endfu 21 | 22 | fu! esearch#log#echo(msg, ...) abort 23 | call s:Log.echo(get(a:, 1, 'NONE'), a:msg) 24 | endfu 25 | 26 | fu! esearch#log#echomsg(msg, hl) abort 27 | call s:Log.echomsg(get(a:, 1, 'NONE'), a:msg) 28 | endfu 29 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/adapter.vim: -------------------------------------------------------------------------------- 1 | let s:modes = ['case', 'regex', 'textobj'] 2 | 3 | fu! esearch#middleware#adapter#apply(esearch) abort 4 | call s:set_current_adapter(a:esearch) 5 | call s:set_parser(a:esearch) 6 | 7 | for mode in s:modes 8 | if empty(a:esearch._adapter[mode]) | continue | endif 9 | 10 | if type(a:esearch[mode]) !=# type('') 11 | let a:esearch[mode] = a:esearch._adapter['bool2'.mode][!!a:esearch[mode]] 12 | elseif !has_key(a:esearch._adapter[mode], a:esearch[mode]) 13 | let a:esearch[mode] = a:esearch._adapter['bool2'.mode][0] 14 | endif 15 | endfor 16 | 17 | return a:esearch 18 | endfu 19 | 20 | fu! s:set_current_adapter(esearch) abort 21 | if type(a:esearch.paths) == type({}) 22 | \ && has_key(a:esearch.paths, 'adapters') 23 | \ && index(a:esearch.paths.adapters, a:esearch.adapter) < 0 24 | let a:esearch.adapter = a:esearch.paths.adapters[0] 25 | endif 26 | 27 | if has_key(a:esearch.adapters, a:esearch.adapter) 28 | call extend(a:esearch.adapters[a:esearch.adapter], 29 | \ esearch#adapter#{a:esearch.adapter}#new(), 'keep') 30 | else 31 | let a:esearch.adapters[a:esearch.adapter] = 32 | \ esearch#adapter#{a:esearch.adapter}#new() 33 | endif 34 | let a:esearch._adapter = a:esearch.adapters[a:esearch.adapter] 35 | endfu 36 | 37 | fu! s:set_parser(esearch) abort 38 | if a:esearch.parse_strategy ==# 'lua' 39 | let a:esearch.parse = esearch#adapter#parse#lua#import() 40 | else 41 | let a:esearch.parse = esearch#adapter#parse#viml#import()[a:esearch._adapter.parser] 42 | endif 43 | endfu 44 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/cwd.vim: -------------------------------------------------------------------------------- 1 | let s:Prelude = vital#esearch#import('Prelude') 2 | 3 | fu! esearch#middleware#cwd#apply(esearch) abort 4 | if has_key(a:esearch, 'cwd') 5 | if !empty(a:esearch.cwd) && !isdirectory(a:esearch.cwd) 6 | call esearch#util#warn('esearch: directory '.a:esearch.cwd." doesn't exist") 7 | throw 'Cancel' 8 | endif 9 | 10 | return a:esearch 11 | endif 12 | 13 | let root = esearch#util#find_up(getcwd(), a:esearch.root_markers) 14 | let a:esearch.cwd = empty(root) ? getcwd() : s:Prelude.substitute_path_separator(fnamemodify(root, ':h')) 15 | return a:esearch 16 | endfu 17 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/exec.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#middleware#exec#apply(esearch) abort 2 | if a:esearch.live_update && !a:esearch.force_exec | return a:esearch | endif 3 | 4 | let command = a:esearch._adapter.command(a:esearch) 5 | let is_xargs = type(a:esearch.paths) ==# type({}) 6 | let a:esearch.request = esearch#backend#{a:esearch.backend}#init( 7 | \ a:esearch.cwd, a:esearch.adapter, command, is_xargs) 8 | call esearch#backend#{a:esearch.backend}#exec(a:esearch.request) 9 | 10 | return a:esearch 11 | endfu 12 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/filemanager.vim: -------------------------------------------------------------------------------- 1 | let s:Filepath = vital#esearch#import('System.Filepath') 2 | 3 | let g:esearch#middleware#filemanager#filetype2filer = { 4 | \ 'defx': 'defx', 5 | \ 'fern': 'fern', 6 | \ 'nerdtree': 'nerdtree', 7 | \ 'dirvish': 'dirvish', 8 | \ 'netranger': 'netranger', 9 | \ } 10 | 11 | fu! esearch#middleware#filemanager#apply(esearch) abort 12 | if !has_key(g:esearch#middleware#filemanager#filetype2filer, &filetype) 13 | \ || !a:esearch.filemanager_integration 14 | \ || (a:esearch.live_update && a:esearch.force_exec) 15 | return a:esearch 16 | endif 17 | 18 | let filer = s:filer() 19 | let cwd = esearch#win#lcd(a:esearch.cwd) 20 | try 21 | if empty(get(a:esearch, 'region')) 22 | let paths = esearch#shell#argv(filer.nearest_dir_or_selected_nodes()) 23 | if paths ==# esearch#shell#argv([a:esearch.cwd]) 24 | let paths = esearch#shell#argv([]) 25 | endif 26 | else 27 | let paths = s:paths_in_range(filer, a:esearch.region) 28 | call remove(a:esearch, 'region') 29 | let a:esearch.force_exec = 0 30 | endif 31 | 32 | " TODO implement a:esearch.paths to work like an object 33 | if type(a:esearch.paths) == type({}) 34 | let a:esearch.paths.pathspec = paths 35 | else 36 | let a:esearch.paths = paths 37 | endif 38 | finally 39 | call cwd.restore() 40 | endtry 41 | 42 | return a:esearch 43 | endfu 44 | 45 | fu! s:paths_in_range(filer, region) abort 46 | let [begin, end] = esearch#operator#range(a:region) 47 | let paths = a:filer.paths_in_range(line(begin), line(end)) 48 | return esearch#shell#argv(paths) 49 | endfu 50 | 51 | fu! s:filer() abort 52 | let filer_name = g:esearch#middleware#filemanager#filetype2filer[&filetype] 53 | return esearch#compat#filemanager#{filer_name}#import() 54 | endfu 55 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/id.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#middleware#id#apply(esearch) abort 2 | let g:esearch.last_id += 1 3 | let a:esearch.id = g:esearch.last_id 4 | return a:esearch 5 | endfu 6 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/input.vim: -------------------------------------------------------------------------------- 1 | let g:esearch#middleware#input#cache = esearch#cache#lru#new(128) 2 | 3 | fu! esearch#middleware#input#apply(esearch) abort 4 | let esearch = extend(a:esearch, {'cmdline': ''}, 'keep') 5 | 6 | if empty(get(esearch, 'pattern')) 7 | let current_win = esearch#win#stay() 8 | let [esearch.pattern, esearch.select_prefilled] = esearch#prefill#try(esearch) 9 | call esearch.pattern.adapt(esearch._adapter) 10 | if esearch.force_exec 11 | let esearch.live_update = 0 12 | else 13 | let esearch = esearch#cmdline#read(esearch) 14 | endif 15 | 16 | if empty(esearch.pattern.peek().str) | call s:cancel(esearch, current_win) | endif 17 | else 18 | if type(esearch.pattern) ==# type('') 19 | let esearch.pattern = s:cached_or_new(esearch.pattern, esearch) 20 | endif 21 | call esearch.pattern.adapt(esearch._adapter) 22 | " avoid live_update if the pattern is present unless is it's a part of force_exec flow 23 | let esearch.live_update = esearch.force_exec 24 | endif 25 | 26 | return esearch 27 | endfu 28 | 29 | fu! s:cancel(esearch, current_win) abort 30 | if a:esearch.live_update && bufexists(a:esearch.live_update_bufnr) 31 | exe a:esearch.live_update_bufnr 'bwipeout' 32 | endif 33 | let a:esearch.live_update_bufnr = -1 34 | call a:current_win.restore() 35 | 36 | throw 'Cancel' 37 | endfu 38 | 39 | fu! s:cached_or_new(text, esearch) abort 40 | if g:esearch#middleware#input#cache.has(a:text) 41 | let pattern = g:esearch#middleware#input#cache.get(a:text) 42 | else 43 | let pattern = esearch#pattern#new(a:esearch._adapter, a:text) 44 | call g:esearch#middleware#input#cache.set(a:text, pattern) 45 | endif 46 | 47 | return pattern 48 | endfu 49 | 50 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/map.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#middleware#map#apply(esearch) abort 2 | if has_key(a:esearch, 'win_map') 3 | let a:esearch.win_map = g:esearch.win_map + a:esearch.win_map 4 | endif 5 | 6 | return a:esearch 7 | endfu 8 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/paths.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#middleware#paths#apply(esearch) abort 2 | if !has_key(a:esearch, 'paths') 3 | let a:esearch.paths = esearch#shell#argv([]) 4 | return a:esearch 5 | endif 6 | 7 | if g:esearch#has#posix_shell 8 | if type(a:esearch.paths) ==# type('') 9 | let [paths, error] = esearch#shell#split(a:esearch.paths) 10 | if !empty(error) | throw "Can't parse paths: " . error | endif 11 | let a:esearch.paths = paths 12 | endif 13 | endif 14 | 15 | return a:esearch 16 | endfu 17 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/prewarm.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#middleware#prewarm#apply(esearch) abort 2 | " All the prewarmers should be called before the commandline start to do 3 | " prewarming while user inputting the string 4 | call esearch#ftdetect#async_prewarm_cache() 5 | 6 | return a:esearch 7 | endfu 8 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/remember.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#middleware#remember#apply(esearch) abort 2 | if !empty(a:esearch.remember) 3 | if type(a:esearch.remember) is# type([]) 4 | let remember = a:esearch.remember 5 | else 6 | let remember = g:esearch.remember 7 | endif 8 | for c in remember 9 | let g:esearch[c] = a:esearch[c] 10 | endfor 11 | endif 12 | 13 | return a:esearch 14 | endfu 15 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/splice_pattern.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#middleware#splice_pattern#apply(esearch) abort 2 | call a:esearch.pattern.splice(a:esearch) 3 | let a:esearch.last_pattern = a:esearch.pattern 4 | return a:esearch 5 | endfu 6 | -------------------------------------------------------------------------------- /autoload/esearch/middleware/warnings.vim: -------------------------------------------------------------------------------- 1 | let s:Log = esearch#log#import() 2 | 3 | " Is used to unobtrusive warn users about deprecations without blocking the 4 | " input or triggering 'more' prompt 5 | fu! esearch#middleware#warnings#apply(esearch) abort 6 | if a:esearch.force_exec || empty(g:esearch.pending_warnings) 7 | return a:esearch 8 | endif 9 | 10 | call uniq(g:esearch.pending_warnings) 11 | for msg in g:esearch.pending_warnings 12 | call s:Log.info(msg) 13 | redraw 14 | endfor 15 | 16 | if len(g:esearch.pending_warnings) > 1 17 | call s:Log.warn(printf('%s. Run :messages to view all %d', 18 | \ g:esearch.pending_warnings[-1], 19 | \ len(g:esearch.pending_warnings))) 20 | endif 21 | let g:esearch.pending_warnings = [] 22 | 23 | return a:esearch 24 | endfu 25 | -------------------------------------------------------------------------------- /autoload/esearch/middleware_stack.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#middleware_stack#new(list) abort 2 | return s:MiddlewareStack.new(a:list) 3 | endfu 4 | 5 | let s:MiddlewareStack = {} 6 | 7 | fu! s:MiddlewareStack.new(list) abort dict 8 | return extend(copy(self), {'list': a:list}) 9 | endfu 10 | 11 | fu! s:MiddlewareStack.insert_before(existing_middleware, Callback) abort dict 12 | call insert(self.list, a:Callback, s:index(self.list, a:existing_middleware)) 13 | endfu 14 | 15 | fu! s:MiddlewareStack.insert_after(existing_middleware, Callback) abort dict 16 | call insert(self.list, a:Callback, s:index(self.list, a:existing_middleware) + 1) 17 | endfu 18 | 19 | fu! s:index(list, existing_middleware) abort 20 | if type(a:existing_middleware) ==# type('') 21 | return index(a:list, function('esearch#middleware#'.a:existing_middleware.'#apply')) 22 | else 23 | return index(a:list, a:existing_middleware) 24 | endif 25 | endfu 26 | -------------------------------------------------------------------------------- /autoload/esearch/operator.vim: -------------------------------------------------------------------------------- 1 | let [s:args, s:count, s:reg] = [[], 0, ''] 2 | 3 | fu! esearch#operator#expr(operatorfunc, ...) abort 4 | let s:args = copy(a:000) 5 | if mode(1)[:1] ==# 'no' 6 | return 'g@' 7 | elseif mode() ==# 'n' 8 | let [s:count, s:reg, &operatorfunc] = [v:count, v:register, a:operatorfunc] 9 | return ":\\".(s:count ? s:count : '').(empty(s:reg) ? '' : '"'.s:reg).'g@' 10 | else 11 | let [s:count, s:reg] = [v:count, v:register] 12 | return ":\call ".a:operatorfunc."(visualmode())\" 13 | endif 14 | endfu 15 | 16 | fu! esearch#operator#args() abort 17 | return s:args 18 | endfu 19 | 20 | fu! esearch#operator#vars() abort 21 | return [s:count, s:reg] 22 | endfu 23 | 24 | fu! esearch#operator#cmd(wise, seq, reg) abort 25 | let seq = (empty(a:reg) ? '' : '"' . a:reg) . a:seq 26 | if esearch#util#is_visual(a:wise) 27 | return 'normal! gv' . seq 28 | elseif a:wise ==# 'line' 29 | return "normal! '[V']" . seq 30 | else 31 | return 'normal! `[v`]' . seq 32 | endif 33 | endfu 34 | 35 | fu! esearch#operator#text(wise) abort 36 | let options = esearch#let#restorable({'&selection': 'inclusive', '@@': ''}) 37 | try 38 | exe esearch#operator#cmd(a:wise, 'y', '') 39 | return @@ 40 | finally 41 | call options.restore() 42 | endtry 43 | endfu 44 | 45 | fu! esearch#operator#range(wise) abort 46 | if esearch#util#is_visual(a:wise) 47 | return ["'<", "'>"] 48 | elseif a:wise ==# '' 49 | return ["'[", "']"] 50 | else 51 | return ['`[', '`]'] 52 | endif 53 | endfu 54 | 55 | fu! esearch#operator#is_linewise(wise) abort 56 | return a:wise =~# 'V$' || a:wise ==# 'line' " V, noV 57 | endfu 58 | 59 | fu! esearch#operator#is_charwise(wise) abort 60 | return a:wise =~# 'v' || a:wise ==# 'char' 61 | endfu 62 | -------------------------------------------------------------------------------- /autoload/esearch/out/win/appearance/annotations.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#out#win#appearance#annotations#init(es) abort 2 | if a:es.win_context_len_annotations && a:es.win_render_strategy ==# 'lua' 3 | if a:es.contexts[-1].id > 0 4 | cal luaeval('esearch.set_context_len_annotation(_A[1], _A[2])', 5 | \ [a:es.contexts[-1].begin, len(a:es.contexts[-1].lines)]) 6 | en 7 | cal luaeval('esearch.buf_attach_annotations()') 8 | en 9 | endfu 10 | 11 | fu! esearch#out#win#appearance#annotations#uninit(es) abort 12 | if a:es.win_context_len_annotations 13 | cal luaeval('esearch.buf_clear_annotations()') 14 | en 15 | endfu 16 | -------------------------------------------------------------------------------- /autoload/esearch/out/win/appearance/cursor_linenr.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#out#win#appearance#cursor_linenr#soft_stop(esearch) abort 2 | call esearch#out#win#appearance#cursor_linenr#uninit(a:esearch) 3 | endfu 4 | 5 | if has('nvim') && g:esearch#has#lua 6 | " vim methods like matchadd have problems when leaving the the 7 | fu! esearch#out#win#appearance#cursor_linenr#init(esearch) abort 8 | if !a:esearch.win_cursor_linenr_highlight | return | endif 9 | 10 | let a:esearch.linenr_ns_id = luaeval('esearch.CURSOR_LINENR_NS') 11 | aug esearch_win_hl_cursor_linenr 12 | au CompleteChanged,CursorMoved,CursorMovedI call luaeval('esearch.highlight_cursor_linenr()') 13 | au BufLeave call nvim_buf_clear_namespace(0, b:esearch.linenr_ns_id, 0, -1) 14 | aug END 15 | endfu 16 | 17 | fu! esearch#out#win#appearance#cursor_linenr#uninit(esearch) abort 18 | aug esearch_win_hl_cursor_linenr 19 | au! * 20 | aug END 21 | 22 | if has_key(a:esearch, 'linenr_ns_id') 23 | call nvim_buf_clear_namespace(0, b:esearch.linenr_ns_id, 0, -1) 24 | endif 25 | endfu 26 | finish 27 | endif 28 | 29 | fu! esearch#out#win#appearance#cursor_linenr#init(esearch) abort 30 | if !a:esearch.win_cursor_linenr_highlight | return | endif 31 | 32 | let a:esearch.linenr_hl_id = 0 33 | aug esearch_win_hl_cursor_linenr 34 | au CursorMoved,CursorMovedI call s:hl_cursor_linenr() 35 | " TODO has bugs when splitting using capital S 36 | au BufLeave call s:clear_cursor_line_number() 37 | aug END 38 | endfu 39 | 40 | fu! esearch#out#win#appearance#cursor_linenr#uninit(esearch) abort 41 | aug esearch_win_hl_cursor_linenr 42 | au! * 43 | aug END 44 | 45 | if has_key(a:esearch, 'linenr_hl_id') 46 | call s:clear_cursor_line_number() 47 | endif 48 | endfu 49 | 50 | fu! s:hl_cursor_linenr() abort 51 | call esearch#util#safe_matchdelete(b:esearch.linenr_hl_id) 52 | let b:esearch.linenr_hl_id = matchadd('esearchCursorLineNr', 53 | \ g:esearch#out#win#column_re . '\%'.line('.').'l', -1) 54 | endfu 55 | 56 | fu! s:clear_cursor_line_number() abort 57 | call esearch#util#safe_matchdelete(b:esearch.linenr_hl_id) 58 | endfu 59 | -------------------------------------------------------------------------------- /autoload/esearch/out/win/fold.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#out#win#fold#close() abort 2 | let contexts = esearch#out#win#repo#ctx#new(b:esearch, b:esearch.state) 3 | let ctx = contexts.by_line(line('.')) 4 | if ctx.id == 0 5 | return 6 | endif 7 | 8 | if ctx.rev 9 | let hash = split(ctx.filename, ':')[0] 10 | 11 | let [first, above] = [ctx, ctx] 12 | while 1 13 | let above = contexts.by_line(first._begin - 1) 14 | if above.id ==# 0 || !above.rev || split(above.filename, ':')[0] !=# hash | break | endif 15 | let first = above 16 | endw 17 | 18 | let [last, below] = [ctx, ctx] 19 | while 1 20 | let below = contexts.by_line(last._end + 1) 21 | if !below.rev || split(below.filename, ':')[0] !=# hash | break | endif 22 | let last = below 23 | endw 24 | else 25 | let [first, last] = [ctx, ctx] 26 | endif 27 | 28 | return first._begin.'GV'.last._end.'Gzf' 29 | endfu 30 | 31 | fu! esearch#out#win#fold#close_all() abort 32 | let contexts = esearch#out#win#repo#ctx#new(b:esearch, b:esearch.state) 33 | 34 | norm! zE 35 | let wlnum = contexts.by_line(1)._end + 1 36 | while wlnum < line('$') 37 | let first = contexts.by_line(wlnum) 38 | 39 | if first.rev 40 | let hash = split(first.filename, ':')[0] 41 | 42 | let [last, below] = [first, first] 43 | while 1 44 | let below = contexts.by_line(last._end + 1) 45 | if !below.rev || split(below.filename, ':')[0] !=# hash | break | endif 46 | let last = below 47 | endw 48 | else 49 | let last = first 50 | endif 51 | 52 | keepjumps exe 'norm! '.first._begin.'GV'.last._end.'Gzf' 53 | let wlnum = last._end + 1 54 | endw 55 | endfu 56 | 57 | fu! esearch#out#win#fold#text() abort 58 | let ctx1 = b:esearch.contexts[b:esearch.state[v:foldstart]] 59 | let ctx2 = b:esearch.contexts[b:esearch.state[v:foldend]] 60 | if ctx1.id == ctx2.id 61 | let lines_count = len(ctx1.lines) 62 | return getline(v:foldstart) . ' ' . lines_count . (lines_count == 1 ? ' line' : ' lines') 63 | else 64 | let files_count = ctx2.id - ctx1.id + 1 65 | return getline(v:foldstart) . ', ... ' . files_count . ' files' 66 | endif 67 | endfu 68 | -------------------------------------------------------------------------------- /autoload/esearch/out/win/matches.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#out#win#matches#pattern_each(esearch) abort 2 | if !has_key(a:esearch.pattern, 'vim') | return '' | endif 3 | " To avoid matching pseudo LineNr 4 | if a:esearch.pattern.vim[0] ==# '^' 5 | return g:esearch#out#win#ignore_ui_hat_re . '\%(' . a:esearch.pattern.vim[1:] . '\M\)' 6 | endif 7 | 8 | return g:esearch#out#win#ignore_ui_re . '\%(' . a:esearch.pattern.vim . '\M\)' 9 | endfu 10 | -------------------------------------------------------------------------------- /autoload/esearch/out/win/preview/split.vim: -------------------------------------------------------------------------------- 1 | let s:default_opts = { 2 | \ 'stay': 1, 3 | \ 'reuse': 1, 4 | \ 'let': {'&eventignore': g:esearch#preview#silent_open_eventignore, '&shortmess': &shortmess . 'F'}, 5 | \ 'let!': {'&l:foldenable': 0}, 6 | \} 7 | 8 | fu! esearch#out#win#preview#split#init(esearch) abort 9 | call extend(a:esearch, { 10 | \ 'split_preview_open': function('split_preview_open'), 11 | \ 'last_split_preview': {'filename': '', 'line_in_file': -1, 'bufnr': -1}, 12 | \ }) 13 | endfu 14 | 15 | " Wrapper around regular open 16 | fu! s:split_preview_open(...) abort dict 17 | if !self.is_current() || esearch#util#is_visual(mode()) | return | endif 18 | 19 | let last = self.last_split_preview 20 | let filename = self.filename() 21 | let line_in_file = self.line_in_file() 22 | if last.line_in_file ==# line_in_file && last.filename ==# filename && bufwinnr(last.bufnr) >= 0 23 | return 0 24 | endif 25 | let opts = get(a:000, 1, {}) 26 | let emphasis = get(opts, 'emphasis', g:esearch#emphasis#default) 27 | 28 | let bufnr = self.open(get(a:000, 0, 'vnew'), extend(copy(s:default_opts), opts)) 29 | let self.last_split_preview = {'filename': filename, 'line_in_file': line_in_file, 'bufnr': bufnr} 30 | if !empty(emphasis) 31 | call s:place_emphasis(emphasis, bufnr, line_in_file) 32 | endif 33 | 34 | return 1 35 | endfu 36 | 37 | fu! s:place_emphasis(emphasis, bufnr, line_in_file) abort 38 | call s:unplace_emphasis() 39 | let winid = bufwinid(a:bufnr) 40 | if winid == -1 | return | endif 41 | aug esearch_split_preview 42 | au! * 43 | au BufLeave,BufWinLeave,WinLeave call s:unplace_emphasis() 44 | aug END 45 | let b:esearch_emphasis = [] 46 | for e in a:emphasis 47 | call add(b:esearch_emphasis, e.new(winid, a:bufnr, a:line_in_file).place()) 48 | endfor 49 | endfu 50 | 51 | fu! s:unplace_emphasis() abort 52 | if exists('b:esearch_emphasis') 53 | call map(b:esearch_emphasis, 'v:val.unplace()') 54 | aug esearch_split_preview 55 | au! * 56 | aug END 57 | unlet b:esearch_emphasis 58 | endif 59 | endfu 60 | -------------------------------------------------------------------------------- /autoload/esearch/out/win/repo/ctx.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#out#win#repo#ctx#new(esearch, state) abort 2 | return { 3 | \ 'esearch': a:esearch, 4 | \ 'state': a:state, 5 | \ 'by_line': function('by_line'), 6 | \} 7 | endfu 8 | 9 | fu! s:by_line(wlnum) abort dict 10 | let wlnum = a:wlnum 11 | if len(self.state) <= wlnum 12 | return g:esearch#out#win#view_data#null_ctx 13 | endif 14 | let ctx = self.esearch.contexts[self.state[wlnum]] 15 | 16 | " read-through cache synchronization 17 | let ctx._begin = s:line_begin(self.state, ctx, wlnum) 18 | let ctx._end = s:line_end(self.state, ctx, wlnum) 19 | 20 | return ctx 21 | endfu 22 | 23 | fu! s:line_begin(state, ctx, wlnum) abort 24 | let wlnum = a:wlnum 25 | let state = a:state 26 | 27 | while wlnum > 0 28 | if state[wlnum - 1] !=# a:ctx.id 29 | return wlnum 30 | endif 31 | 32 | let wlnum -= 1 33 | endwhile 34 | 35 | return 1 36 | endfu 37 | 38 | fu! s:line_end(state, ctx, wlnum) abort 39 | let wlnum = a:wlnum 40 | let state = a:state 41 | 42 | while wlnum < len(state) - 1 43 | if state[wlnum + 1] !=# a:ctx.id 44 | return wlnum 45 | endif 46 | 47 | let wlnum += 1 48 | endwhile 49 | 50 | return len(state) - 1 51 | throw "Can't find ctx begin: " . string([a:ctx, a:wlnum]) 52 | endfu 53 | -------------------------------------------------------------------------------- /autoload/esearch/pattern/literal2vim.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#pattern#literal2vim#convert(string) abort 2 | return '\M'.escape(a:string, '\$^') 3 | endfu 4 | -------------------------------------------------------------------------------- /autoload/esearch/pattern/vim2literal.vim: -------------------------------------------------------------------------------- 1 | let s:esc = g:esearch#util#even_count_of_escapes_re . '\zs' 2 | 3 | fu! esearch#pattern#vim2literal#convert(string) abort 4 | let string = a:string 5 | " :h pattern-atoms 6 | let string = substitute(string, s:esc . '\\_\([$.^]\)', '', 'g') 7 | let string = substitute(string, s:esc . '\\[<>]', '', 'g') 8 | let string = substitute(string, s:esc . '\\z[se]', '', 'g') 9 | let string = substitute(string, s:esc . '\\%\([$^]\)', '', 'g') 10 | let string = substitute(string, s:esc . '\\%[V#]', '', 'g') 11 | " marks matches 12 | let string = substitute(string, s:esc . '\\%[<>]\=''\w', '', 'g') 13 | " line/column matches 14 | let string = substitute(string, s:esc . '\\%[<>]\=\d\+[vlc]', '', 'g') 15 | " (no)magic, very (no)magic and case sensitiveness 16 | let string = substitute(string, s:esc . '\\[mMvVcC]', '', 'g') 17 | " unescape / 18 | let string = substitute(string, s:esc . '\\/', '/', 'g') 19 | 20 | return string 21 | endfu 22 | -------------------------------------------------------------------------------- /autoload/esearch/pattern/vim2pcre.vim: -------------------------------------------------------------------------------- 1 | let s:esc = g:esearch#util#even_count_of_escapes_re . '\zs' 2 | 3 | fu! esearch#pattern#vim2pcre#convert(string) abort 4 | let string = a:string 5 | " :h pattern-atoms 6 | let string = substitute(string, s:esc . '\\_\([$.^]\)', '\1', 'g') 7 | let string = substitute(string, s:esc . '\\[<>]', '\\b', 'g') 8 | let string = substitute(string, s:esc . '\\z[se]', '', 'g') 9 | let string = substitute(string, s:esc . '\\%\([$^]\)', '\1', 'g') 10 | let string = substitute(string, s:esc . '\\%[V#]', '', 'g') 11 | " marks matches 12 | let string = substitute(string, s:esc . '\\%[<>]\=''\w', '', 'g') 13 | " line/column matches 14 | let string = substitute(string, s:esc . '\\%[<>]\=\d\+[vlc]', '', 'g') 15 | " (no)magic, very (no)magic and case sensitiveness 16 | let string = substitute(string, s:esc . '\\[mMvVcC]', '', 'g') 17 | 18 | return string 19 | endfu 20 | -------------------------------------------------------------------------------- /autoload/esearch/polyfill.vim: -------------------------------------------------------------------------------- 1 | if g:esearch#has#vim8_types 2 | let s:true = v:true 3 | let s:false = v:false 4 | let s:null = v:null 5 | let s:t_dict = v:t_dict 6 | let s:t_float = v:t_float 7 | let s:t_func = v:t_func 8 | let s:t_list = v:t_list 9 | let s:t_number = v:t_number 10 | let s:t_string = v:t_string 11 | else 12 | let s:true = 1 13 | let s:false = 0 14 | let s:null = 0 15 | let s:t_dict = type({}) 16 | let s:t_float = type(1.0) 17 | let s:t_func = type(function('tr')) 18 | let s:t_list = type([]) 19 | let s:t_number = type(1) 20 | let s:t_string = type('') 21 | endif 22 | 23 | fu! esearch#polyfill#definitions() abort 24 | return [ 25 | \ s:true, 26 | \ s:false, 27 | \ s:null, 28 | \ s:t_dict, 29 | \ s:t_float, 30 | \ s:t_func, 31 | \ s:t_list, 32 | \ s:t_number, 33 | \ s:t_string, 34 | \] 35 | endfu 36 | 37 | fu! esearch#polyfill#undefined() abort 38 | return function('esearch#polyfill#undefined') 39 | endfu 40 | -------------------------------------------------------------------------------- /autoload/esearch/preview/buf.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#preview#buf#import() abort 2 | return s:Buf 3 | endfu 4 | 5 | let s:Buf = {'kind': 'regular', 'swapname': ''} 6 | 7 | fu! s:Buf.new(filename, ...) abort dict 8 | let new = copy(self) 9 | let new.filename = a:filename 10 | 11 | let reuse_existing = get(a:, 1, 1) 12 | if reuse_existing && bufexists(a:filename) 13 | let new.id = esearch#buf#find(a:filename) 14 | else 15 | let new.id = bufadd(a:filename) 16 | endif 17 | 18 | return new 19 | endfu 20 | 21 | fu! s:Buf.fetch_or_create(filename, cache) abort dict 22 | if has_key(a:cache, a:filename) 23 | let cached = a:cache[a:filename] 24 | if cached.is_valid() 25 | return cached 26 | endif 27 | call remove(a:cache, a:filename) 28 | endif 29 | 30 | let new = self.new(a:filename) 31 | let a:cache[a:filename] = new 32 | 33 | return new 34 | endfu 35 | 36 | fu! s:Buf.edit_allowing_swap_prompt() abort dict 37 | if exists('#esearch_preview_autoclose') 38 | au! esearch_preview_autoclose 39 | endif 40 | 41 | try 42 | exe 'edit ' . fnameescape(self.filename) 43 | catch /E325:/ " swapexists exception, will be handled by a user 44 | catch /Vim:Interrupt/ " Throwed on cancelling swap 45 | endtry 46 | " When (Q)uit or (A)bort are pressed - vim unloads the current buffer as it 47 | " was with an existing swap 48 | if empty(bufname('%')) && !bufloaded('%') 49 | exe self.id . 'bwipeout' 50 | return 0 51 | endif 52 | 53 | let current_buffer_id = bufnr('%') 54 | if current_buffer_id != self.id && bufexists(self.id) && empty(bufname(self.id)) && !bufloaded(self.id) 55 | exe self.id . 'bwipeout' 56 | endif 57 | let self.id = current_buffer_id 58 | 59 | return 1 60 | endfu 61 | 62 | fu! s:Buf.is_valid() abort dict 63 | return self.id >= 0 && bufexists(self.id) 64 | endfu 65 | -------------------------------------------------------------------------------- /autoload/esearch/preview/opener_base.vim: -------------------------------------------------------------------------------- 1 | let s:Buf = esearch#preview#buf#import() 2 | let s:Log = esearch#log#import() 3 | let [s:true, s:false, s:null, s:t_dict, s:t_float, s:t_func, 4 | \ s:t_list, s:t_number, s:t_string] = esearch#polyfill#definitions() 5 | 6 | fu! esearch#preview#opener_base#import() abort 7 | return copy(s:OpenerBase) 8 | endfu 9 | 10 | let s:OpenerBase = {} 11 | 12 | fu! s:OpenerBase.new(location, shape, emphasis, vars, opts, close_on) abort dict 13 | let new = copy(self) 14 | let new.location = a:location 15 | let new.shape = a:shape 16 | let new.vars = a:vars 17 | let new.opts = a:opts 18 | let new.close_on = a:close_on 19 | let new.emphasis = a:emphasis 20 | return new 21 | endfu 22 | 23 | fu! s:OpenerBase.shell() abort dict 24 | let current_win = esearch#win#stay() 25 | let self.buf = s:Buf.fetch_or_create( 26 | \ self.location.filename, g:esearch#preview#buffers) 27 | 28 | try 29 | let g:esearch#preview#win = self.popup 30 | \.new(self.buf, self.location, self.shape, self.close_on) 31 | \.open() 32 | let self.win = g:esearch#preview#win 33 | let self.win.upd_at = reltime() 34 | let self.win.cache_key = [self.opts, self.shape.align] 35 | call self.win.let(self.vars) 36 | call self.win.place_emphasis(self.emphasis) 37 | call self.win.reshape() 38 | call self.win.init_autoclose_events() 39 | catch 40 | call esearch#preview#close() 41 | call s:Log.error(v:exception . (g:esearch#env is 0 ? '' : v:throwpoint)) 42 | return s:false 43 | finally 44 | noau keepj call current_win.restore() 45 | endtry 46 | 47 | return s:true 48 | endfu 49 | -------------------------------------------------------------------------------- /autoload/esearch/preview/popup_base.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#preview#popup_base#import() abort 2 | return copy(s:PopupBase) 3 | endfu 4 | 5 | let s:PopupBase = {'guard': 0, 'id': 0, 'emphasis': 0, 'variables': 0} 6 | 7 | fu! s:PopupBase.new(buf, location, shape, close_on) abort dict 8 | let new = copy(self) 9 | 10 | let new.buf = a:buf 11 | let new.location = a:location 12 | let new.shape = a:shape 13 | let new.close_on = a:close_on 14 | let new.emphasis = [] 15 | 16 | return new 17 | endfu 18 | 19 | fu! s:PopupBase.let(variables) abort dict 20 | let self.variables = a:variables 21 | let self.guard = esearch#let#bufwin_restorable(self.buf.id, self.id, a:variables) 22 | endfu 23 | 24 | fu! s:PopupBase.init_autoclose_events() abort dict 25 | let autocommands = join(self.close_on, ',') 26 | 27 | aug esearch_preview_autoclose 28 | au! 29 | exe 'au ' . autocommands . ' * ++once call esearch#preview#close()' 30 | exe 'au ' . g:esearch#preview#reset_on . ' * ++once call esearch#preview#reset()' 31 | au User esearch_open_pre ++once call esearch#preview#close() 32 | if exists('##TabNewEntered') 33 | " Prevent options inheritance 34 | au TabNewEntered * ++once call g:esearch#preview#last.win.guard.new(g:esearch#preview#last.buf.id, win_getid()).restore() 35 | endif 36 | 37 | " We cannot close the preview when entering cmdwin, so the only option is to 38 | " reinitialize the events. 39 | au CmdwinLeave * ++once call g:esearch#preview#win.init_autoclose_events() 40 | aug END 41 | endfu 42 | 43 | fu! s:PopupBase.place_emphasis(emphasis) abort dict 44 | let self.emphasis = [] 45 | 46 | for e in a:emphasis 47 | call add(self.emphasis, e.new(self.id, self.buf.id, self.location.line).place()) 48 | endfor 49 | endfu 50 | 51 | fu! s:PopupBase.unplace_emphasis() abort dict 52 | if !empty(self.emphasis) 53 | call map(self.emphasis, 'v:val.unplace()') 54 | let self.emphasis = [] 55 | endif 56 | endfu 57 | -------------------------------------------------------------------------------- /autoload/esearch/preview/vim/opener.vim: -------------------------------------------------------------------------------- 1 | let s:Log = esearch#log#import() 2 | let s:Buf = esearch#preview#buf#import() 3 | let [s:true, s:false, s:null, s:t_dict, s:t_float, s:t_func, 4 | \ s:t_list, s:t_number, s:t_string] = esearch#polyfill#definitions() 5 | 6 | fu! esearch#preview#vim#opener#import() abort 7 | return copy(s:VimOpener) 8 | endfu 9 | 10 | let s:VimOpener = esearch#preview#opener_base#import() 11 | let s:VimOpener.popup = esearch#preview#vim#popup#import() 12 | 13 | fu! s:VimOpener.open() abort dict 14 | let self.buf = s:Buf.new(self.location.filename) 15 | 16 | try 17 | call esearch#preview#close() 18 | let g:esearch#preview#win = self.popup 19 | \.new(self.buf, self.location, self.shape, self.close_on) 20 | \.open() 21 | let self.win = g:esearch#preview#win 22 | call self.win.let(self.vars) 23 | call self.win.place_emphasis(self.emphasis) 24 | call self.win.reshape() 25 | call self.win.init_autoclose_events() 26 | catch 27 | call esearch#preview#close() 28 | call s:Log.error(v:exception . (g:esearch#env is 0 ? '' : v:throwpoint)) 29 | return s:false 30 | endtry 31 | 32 | return s:true 33 | endfu 34 | 35 | fu! s:VimOpener.open_and_enter() abort dict 36 | let current_win = esearch#win#stay() 37 | let self.buf = s:Buf.new(self.location.filename) 38 | 39 | if esearch#preview#is_open() 40 | let g:esearch#preview#win.guard = {} 41 | endif 42 | call esearch#preview#close() 43 | call b:esearch.reusable_buffers_manager.open(self.location.filename) 44 | 45 | return s:true 46 | endfu 47 | -------------------------------------------------------------------------------- /autoload/esearch/preview/vim/popup.vim: -------------------------------------------------------------------------------- 1 | let s:Log = esearch#log#import() 2 | let [s:true, s:false, s:null, s:t_dict, s:t_float, s:t_func, 3 | \ s:t_list, s:t_number, s:t_string] = esearch#polyfill#definitions() 4 | let s:relative = 'win' 5 | 6 | fu! esearch#preview#vim#popup#import() abort 7 | return s:VimPopup 8 | endfu 9 | 10 | let s:VimPopup = esearch#preview#popup_base#import() 11 | 12 | fu! s:VimPopup.reshape() abort dict 13 | if !self.buf.is_valid() 14 | call s:Log.error('Preview buf was deleted') 15 | return esearch#preview#close() 16 | endif 17 | 18 | if g:esearch#has#getbufinfo_linecount 19 | call self.shape.clip_height(getbufinfo(self.buf.id)[0].linecount) 20 | endif 21 | let height = self.shape.height 22 | let line = self.location.line 23 | 24 | if &lines < height + self.shape.row 25 | let self.shape.row = &lines - height - 2 26 | endif 27 | call popup_setoptions(self.id, { 28 | \ 'firstline': max([1, line - height/2]), 29 | \ }) 30 | call popup_move(self.id, { 31 | \ 'maxheight': height, 32 | \ 'minheight': height, 33 | \ 'maxwidth': self.shape.width, 34 | \ 'minwidth': self.shape.width, 35 | \ 'line': self.shape.row, 36 | \ }) 37 | endfu 38 | 39 | fu! s:VimPopup.open() abort dict 40 | try 41 | noau let self.id = popup_create(self.buf.id, { 42 | \ 'maxwidth': self.shape.width, 43 | \ 'minwidth': self.shape.width, 44 | \ 'maxheight': self.shape.height, 45 | \ 'minheight': self.shape.height, 46 | \ 'line': self.shape.row, 47 | \ 'col': self.shape.col, 48 | \ 'pos': self.shape.anchor, 49 | \ 'highlight': 'esearchNormalFloat', 50 | \ 'scrollbar': 0, 51 | \}) 52 | catch /^Vim(let):E325:/ 53 | call popup_clear() " as SwapExists doesn't work 54 | exe self.buf.id . 'bdelete' 55 | throw "Can't open the preview: swap exists" 56 | endtry 57 | if empty(esearch#win#get(self.id, '&filtype')) 58 | call win_execute(self.id, 'filetype detect') 59 | endif 60 | return self 61 | endfu 62 | 63 | fu! s:VimPopup.close() abort dict 64 | call self.unplace_emphasis() 65 | call popup_close(self.id) 66 | endfu 67 | -------------------------------------------------------------------------------- /autoload/esearch/repeat.vim: -------------------------------------------------------------------------------- 1 | " Wrapper around Tim Pope's vim-repeat with fallback to minimal functionality to 2 | " avoid hard dependency 3 | 4 | fu! esearch#repeat#run(count) abort 5 | try 6 | let result = repeat#run(a:count) 7 | if type(result) ==# type('') | return result | endif 8 | return '' 9 | catch /E117:/ 10 | if !exists('s:changedtick') || s:changedtick != b:changedtick 11 | return 'norm! '.(a:count ? a:count : '').'.' 12 | else 13 | return 'norm '.(a:count ? a:count : (s:count ? s:count : '')) 14 | \.'"'.get(s:reg, s:seq, esearch#util#clipboard_reg()).s:seq 15 | endif 16 | endtry 17 | endfu 18 | 19 | fu! esearch#repeat#set(seq, count) abort 20 | try 21 | return repeat#set(a:seq, a:count) 22 | catch /E117:/ 23 | let [s:changedtick, s:seq, s:count] = [b:changedtick, a:seq, a:count] 24 | endtry 25 | endfu 26 | 27 | fu! esearch#repeat#setreg(seq, reg) abort 28 | try 29 | return repeat#setreg(a:seq, a:reg) 30 | catch /E117:/ 31 | let s:reg = {} 32 | let s:reg[a:seq] = a:reg 33 | endtry 34 | endfu 35 | -------------------------------------------------------------------------------- /autoload/esearch/stderr.vim: -------------------------------------------------------------------------------- 1 | let s:String = vital#esearch#import('Data.String') 2 | 3 | " Handle stderr from backends 4 | 5 | fu! esearch#stderr#append(esearch, errors) abort 6 | let a:esearch.request.errors += a:errors 7 | call esearch#stderr#incremental(a:esearch.adapter, a:errors) 8 | endfu 9 | 10 | fu! esearch#stderr#incremental(adapter, errors) abort 11 | for message in a:errors 12 | call esearch#util#warn(a:adapter . ': ' . message) 13 | endfor 14 | endfu 15 | 16 | fu! esearch#stderr#finish(esearch) abort 17 | let errors = a:esearch.request.errors 18 | if empty(errors) 19 | let message = printf('%s returned status %d. No STDERR was provided', 20 | \ a:esearch.adapter, 21 | \ a:esearch.request.status, 22 | \ ) 23 | call esearch#util#warn(message) 24 | endif 25 | endfu 26 | -------------------------------------------------------------------------------- /autoload/esearch/ui/app.vim: -------------------------------------------------------------------------------- 1 | let s:OptionsMenu = esearch#ui#menu#menu#import() 2 | let s:MenuController = esearch#ui#controllers#menu#import() 3 | let s:PathInputController = esearch#ui#controllers#path_input#import() 4 | let s:FiletypeInputController = esearch#ui#controllers#filetype_input#import() 5 | let s:SearchInputController = esearch#ui#controllers#search_input#import() 6 | 7 | let s:App = esearch#ui#component() 8 | 9 | fu! s:App.new(store) abort dict 10 | let instance = extend(copy(self), {'store': a:store}) 11 | let instance.current_route = 0 12 | let instance.location = a:store.state.location 13 | return instance 14 | endfu 15 | 16 | fu! s:App.render() abort dict 17 | if self.store.state.location ==# 'menu' 18 | call self.route('menu', s:MenuController.new({'menu_class': s:OptionsMenu})) 19 | elseif self.store.state.location ==# 'path_input' 20 | call self.route('path_input', s:PathInputController.new()) 21 | elseif self.store.state.location ==# 'filetype_input' 22 | call self.route('filetype_input', s:FiletypeInputController.new()) 23 | elseif self.store.state.location ==# 'search_input' 24 | call self.route('search_input', s:SearchInputController.new()) 25 | elseif self.store.state.location ==# 'exit' 26 | call esearch#ui#soft_clear() 27 | return 0 28 | else 29 | throw 'Unknown location' 30 | endif 31 | 32 | call self.current_route.render() 33 | 34 | return 1 35 | endfu 36 | 37 | fu! s:App.route(location, component) abort dict 38 | if self.location !=# a:location || empty(self.current_route) 39 | let self.location = a:location 40 | 41 | if !empty(self.current_route) 42 | call self.current_route.component_will_unmount() 43 | endif 44 | call a:component.component_will_mount() 45 | endif 46 | let self.current_route = a:component 47 | endfu 48 | 49 | fu! s:App.component_will_unmount() abort dict 50 | if !empty(self.current_route) 51 | call self.current_route.component_will_unmount() 52 | endif 53 | endfu 54 | 55 | let s:map_state_to_props = esearch#util#slice_factory(['location']) 56 | 57 | fu! esearch#ui#app#import() abort 58 | return s:App 59 | endfu 60 | -------------------------------------------------------------------------------- /autoload/esearch/ui/complete/base.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#ui#complete#base#word_and_prefix(arglead) abort 2 | let leading_words = split(a:arglead, '\s\+') 3 | " no current word 4 | if a:arglead =~# '\s$' || empty(leading_words) 5 | return ['' , a:arglead] 6 | endif 7 | 8 | let word = leading_words[-1] 9 | if strchars(a:arglead) == strchars(word) 10 | return [word, ''] 11 | endif 12 | 13 | return [word, a:arglead[: strchars(a:arglead) - strchars(word) - 1]] 14 | endfu 15 | 16 | fu! esearch#ui#complete#base#prepare(candidates, cmdline, prefix) abort 17 | return map(filter(a:candidates, 'stridx(a:cmdline, v:val) == -1'), 'a:prefix . v:val') 18 | endfu 19 | -------------------------------------------------------------------------------- /autoload/esearch/ui/complete/filetypes.vim: -------------------------------------------------------------------------------- 1 | let s:List = vital#esearch#import('Data.List') 2 | 3 | fu! esearch#ui#complete#filetypes#do(filetypes, arglead, cmdline, curpos) abort 4 | let [word, prefix] = esearch#ui#complete#base#word_and_prefix(a:arglead) 5 | let already_listed = split(a:cmdline) 6 | let candidates = s:gather_candidates(a:filetypes, word, already_listed) 7 | return map(candidates, 'prefix . v:val') 8 | endfu 9 | 10 | fu! s:gather_candidates(filetypes, word, already_listed) abort 11 | " remove incorrect and non-glob chars and match case insensitively 12 | let pattern = '\c' . substitute(a:word, '[^0-9A-Za-z_\-*?[\]]', '', 'g') 13 | if pattern =~# '[*?[\]]' 14 | let pattern = glob2regpat(pattern) 15 | endif 16 | 17 | let candidates = filter(copy(a:filetypes), 18 | \ '(v:val ==# a:word || !s:List.has(a:already_listed, v:val)) && v:val =~? pattern') 19 | return s:List.sort(candidates, '(len(a:a) - len(a:b))') 20 | endfu 21 | -------------------------------------------------------------------------------- /autoload/esearch/ui/complete/search.vim: -------------------------------------------------------------------------------- 1 | " borrowed from oblique and incsearch 2 | fu! esearch#ui#complete#search#do(arglead, ...) abort 3 | let equal = [] 4 | let partially_matched = [] 5 | let spell_suggested = [] 6 | let fuzzy_matched = [] 7 | let start_with = [] 8 | 9 | let fuzzy_re = s:fuzzy_re(a:arglead) 10 | let spell_re = s:spell_re(a:arglead) 11 | 12 | let word_len = strlen(a:arglead) 13 | for word in s:buffer_words(word_len) 14 | if word == a:arglead 15 | call add(equal, word) 16 | continue 17 | endif 18 | 19 | let substr_index = stridx(word, a:arglead) 20 | if substr_index == 0 21 | call add(start_with, word) 22 | elseif substr_index >= 0 23 | call add(partially_matched, word) 24 | elseif word_len > 2 25 | if word =~ spell_re 26 | call add(spell_suggested, word) 27 | elseif word =~ fuzzy_re 28 | call add(fuzzy_matched, word) 29 | endif 30 | endif 31 | endfor 32 | 33 | call sort(fuzzy_matched, 'esearch#util#compare_len') 34 | call sort(spell_suggested, 'esearch#util#compare_len') 35 | call sort(equal, 'esearch#util#compare_len') 36 | call sort(partially_matched, 'esearch#util#compare_len') 37 | return [a:arglead] + equal + start_with + partially_matched + spell_suggested + fuzzy_matched 38 | endfu 39 | 40 | fu! s:spell_re(arglead) abort 41 | let spell_re = a:arglead 42 | let spell = esearch#let#restorable({'&spellsuggest': 1, '&spell': 1}) 43 | try 44 | return substitute(spell_re, '\h\k*', '\=s:spell_suggestions(submatch(0))', 'g') 45 | finally 46 | call spell.restore() 47 | endtry 48 | endfu 49 | 50 | fu! s:spell_suggestions(word) abort 51 | return printf('\m\(%s\)', join(spellsuggest(a:word, 10), '\|')) 52 | endfu 53 | 54 | fu! s:fuzzy_re(arglead) abort 55 | let chars = map(split(a:arglead, '.\zs'), 'escape(v:val, "\\[]^$.*")') 56 | let fuzzy_re = join( 57 | \ extend(map(chars[0 : -2], "v:val . '[^' .v:val. ']\\{-}'"), 58 | \ chars[-1:-1]), '') 59 | endfu 60 | 61 | fu! s:buffer_words(min_len) abort 62 | let words = esearch#util#buff_words() 63 | if a:min_len < 4 64 | call filter(words, 'a:min_len <= strlen(v:val)') 65 | endif 66 | return words 67 | endfu 68 | -------------------------------------------------------------------------------- /autoload/esearch/ui/controllers/filetype_input.vim: -------------------------------------------------------------------------------- 1 | let s:Log = esearch#log#import() 2 | let s:FiletypeInputController = esearch#ui#component() 3 | 4 | fu! s:FiletypeInputController.render() abort dict 5 | let s:_adapter = self.props._adapter 6 | redraw! 7 | 8 | try 9 | let filetypes = input('[filetypes] > ', 10 | \ self.props.filetypes, 11 | \ 'customlist,esearch#ui#controllers#filetype_input#complete') 12 | catch /Vim:Interrupt/ 13 | return self.props.dispatch({'type': 'SET_LOCATION', 'location': 'menu'}) 14 | endtry 15 | 16 | call self.props.dispatch({'type': 'SET_FILETYPES', 'filetypes': filetypes}) 17 | call self.props.dispatch({'type': 'SET_LOCATION', 'location': 'menu'}) 18 | endfu 19 | 20 | fu! esearch#ui#controllers#filetype_input#complete(arglead, cmdline, curpos) abort 21 | return esearch#ui#complete#filetypes#do(s:_adapter.filetypes, a:arglead, a:cmdline, a:curpos) 22 | endfu 23 | 24 | let s:map_state_to_props = esearch#util#slice_factory(['filetypes', '_adapter']) 25 | 26 | fu! esearch#ui#controllers#filetype_input#import() abort 27 | return esearch#ui#connect(s:FiletypeInputController, s:map_state_to_props) 28 | endfu 29 | -------------------------------------------------------------------------------- /autoload/esearch/ui/controllers/path_input.vim: -------------------------------------------------------------------------------- 1 | let s:Log = esearch#log#import() 2 | let s:PathInputController = esearch#ui#component() 3 | 4 | fu! s:PathInputController.render() abort dict 5 | let s:self = self 6 | if type(self.props.paths) == type({}) 7 | let user_input_in_shell_format = '' 8 | else 9 | let user_input_in_shell_format = esearch#shell#join(self.props.paths) 10 | endif 11 | 12 | redraw! 13 | while 1 14 | try 15 | let user_input_in_shell_format = input('[paths] > ', 16 | \ user_input_in_shell_format, 17 | \'customlist,esearch#ui#controllers#path_input#complete') 18 | catch /Vim:Interrupt/ 19 | return self.props.dispatch({'type': 'SET_LOCATION', 'location': 'menu'}) 20 | endtry 21 | 22 | let [paths, error] = esearch#shell#split(user_input_in_shell_format) 23 | if empty(error) | break | endif 24 | 25 | call s:Log.echon('ErrorMsg', " can't parse paths: " . error) 26 | call getchar() 27 | redraw! 28 | endwhile 29 | 30 | call self.props.dispatch({'type': 'SET_PATHS', 'paths': paths}) 31 | call self.props.dispatch({'type': 'SET_LOCATION', 'location': 'menu'}) 32 | endfu 33 | 34 | fu! esearch#ui#controllers#path_input#complete(arglead, cmdline, curpos) abort 35 | return esearch#ui#complete#paths#do(s:self.props.cwd, a:arglead, a:cmdline, a:curpos) 36 | endfu 37 | 38 | fu! s:map_state_to_props(state) abort dict 39 | return { 40 | \ 'paths': get(a:state, 'paths', esearch#shell#argv([])), 41 | \ 'cwd': a:state.cwd, 42 | \} 43 | endfu 44 | 45 | fu! esearch#ui#controllers#path_input#import() abort 46 | return esearch#ui#connect(s:PathInputController, function('map_state_to_props')) 47 | endfu 48 | -------------------------------------------------------------------------------- /autoload/esearch/ui/controllers/selection.vim: -------------------------------------------------------------------------------- 1 | let s:SearchPrompt = esearch#ui#prompt#search#import() 2 | 3 | let [s:true, s:false, s:null, s:t_dict, s:t_float, s:t_func, 4 | \ s:t_list, s:t_number, s:t_string] = esearch#polyfill#definitions() 5 | 6 | let s:SelectionController = esearch#ui#component() 7 | 8 | fu! s:SelectionController.render() abort dict 9 | let str = self.props.pattern.peek().str 10 | 11 | call esearch#ui#render(s:SearchPrompt.new()) 12 | call esearch#ui#render([['Visual', substitute(str, "\n", '\\n', 'g')]]) 13 | 14 | let retype = '' 15 | let finish = s:false 16 | let char = esearch#util#getchar() 17 | 18 | if index(g:esearch#cmdline#insert_register_content_chars, char) >= 0 19 | let retype = char 20 | let char = esearch#util#getchar() 21 | let retype .= char 22 | 23 | " From :h c_CTRL-R 24 | if char =~# '^[0-9a-z"%#:\-=.]$' 25 | let str = '' 26 | endif 27 | elseif index(g:esearch#cmdline#clear_selection_chars, char) >= 0 28 | let str = '' 29 | let retype = char 30 | elseif index(g:esearch#cmdline#start_search_chars, char) >= 0 31 | let finish = s:true 32 | elseif index(g:esearch#cmdline#cancel_selection_and_retype_chars, char) >= 0 33 | let retype = char 34 | elseif index(g:esearch#cmdline#cancel_selection_chars, char) >= 0 35 | " no-op 36 | elseif !empty(esearch#keymap#escape_kind(char)) 37 | let retype = char 38 | elseif mapcheck(char, 'c') !=# '' 39 | let retype = char 40 | else 41 | let str = char 42 | endif 43 | redraw! 44 | 45 | return [str, finish, retype] 46 | endfu 47 | 48 | let s:map_state_to_props = esearch#util#slice_factory(['pattern']) 49 | 50 | fu! esearch#ui#controllers#selection#import() abort 51 | return esearch#ui#connect(s:SelectionController, s:map_state_to_props) 52 | endfu 53 | -------------------------------------------------------------------------------- /autoload/esearch/ui/menu/after_entry.vim: -------------------------------------------------------------------------------- 1 | let s:UnsignedIntEntry = esearch#ui#menu#unsigned_int_entry#import() 2 | let s:AfterEntry = esearch#ui#component() 3 | 4 | fu! s:AfterEntry.new(props) abort dict 5 | let instance = extend(copy(self), {'props': a:props}) 6 | let instance.entry = s:UnsignedIntEntry.new() 7 | let instance.entry.props['+'] = 'a' 8 | let instance.entry.props['-'] = 'A' 9 | let instance.entry.props.name = 'after' 10 | let instance.entry.props.option = a:props._adapter.after.opt 11 | let instance.entry.props.hint = a:props._adapter.after.hint 12 | let instance.entry.props.value = a:props.after 13 | let instance.entry.props.i = a:props.i 14 | let down = g:esearch#has#unicode ? g:esearch#unicode#down : 'v' 15 | let instance.entry.props.icon = '[ '.down.']' 16 | 17 | return instance 18 | endfu 19 | 20 | fu! s:AfterEntry.render() abort dict 21 | return self.entry.render() 22 | endfu 23 | 24 | fu! s:AfterEntry.keypress(event) abort dict 25 | return self.entry.keypress(a:event) 26 | endfu 27 | 28 | let s:map_state_to_props = esearch#util#slice_factory(['after', '_adapter']) 29 | 30 | fu! esearch#ui#menu#after_entry#import() abort 31 | return esearch#ui#connect(s:AfterEntry, s:map_state_to_props) 32 | endfu 33 | 34 | -------------------------------------------------------------------------------- /autoload/esearch/ui/menu/before_entry.vim: -------------------------------------------------------------------------------- 1 | let s:UnsignedIntEntry = esearch#ui#menu#unsigned_int_entry#import() 2 | let s:BeforeEntry = esearch#ui#component() 3 | 4 | fu! s:BeforeEntry.new(props) abort dict 5 | let instance = extend(copy(self), {'props': a:props}) 6 | let instance.entry = s:UnsignedIntEntry.new() 7 | let instance.entry.props['+'] = 'b' 8 | let instance.entry.props['-'] = 'B' 9 | let instance.entry.props.name = 'before' 10 | let instance.entry.props.option = a:props._adapter.before.opt 11 | let instance.entry.props.hint = a:props._adapter.before.hint 12 | let instance.entry.props.value = a:props.before 13 | let instance.entry.props.i = a:props.i 14 | let up = g:esearch#has#unicode ? g:esearch#unicode#up : 'v' 15 | let instance.entry.props.icon = '['.up.' ]' 16 | 17 | return instance 18 | endfu 19 | 20 | fu! s:BeforeEntry.render() abort dict 21 | return self.entry.render() 22 | endfu 23 | 24 | fu! s:BeforeEntry.keypress(event) abort dict 25 | return self.entry.keypress(a:event) 26 | endfu 27 | 28 | let s:map_state_to_props = esearch#util#slice_factory(['before', '_adapter']) 29 | 30 | fu! esearch#ui#menu#before_entry#import() abort 31 | return esearch#ui#connect(s:BeforeEntry, s:map_state_to_props) 32 | endfu 33 | -------------------------------------------------------------------------------- /autoload/esearch/ui/menu/case_entry.vim: -------------------------------------------------------------------------------- 1 | let s:String = vital#esearch#import('Data.String') 2 | let s:List = vital#esearch#import('Data.List') 3 | let s:CaseEntry = esearch#ui#component() 4 | 5 | fu! s:CaseEntry.render() abort dict 6 | let icon = self.props.case ==# 'ignore' ? ['Comment', '(?i)'] : 7 | \ self.props.case ==# 'sensitive' ? ['Constant', '[Cs]'] : ['String', '[Sc]'] 8 | 9 | let result = [['None', s:String.pad_right(self.props.keys[0], 7, ' ')], icon, ['NONE', ' case match']] 10 | let option = self.props._adapter.case[self.props.case].option 11 | let option = join(filter([self.props.case, option], '!empty(v:val)'), ': ') 12 | let result += [['Comment', ' (' . option . ')']] 13 | 14 | return result 15 | endfu 16 | 17 | fu! s:CaseEntry.keypress(event) abort dict 18 | if s:List.has(self.props.keys, a:event.key) || a:event.key ==# "\" 19 | call self.props.dispatch({'type': 'NEXT_CASE'}) 20 | let stop_propagation = 1 21 | return stop_propagation 22 | endif 23 | endfu 24 | 25 | let s:map_state_to_props = esearch#util#slice_factory(['case', '_adapter']) 26 | 27 | fu! esearch#ui#menu#case_entry#import() abort 28 | return esearch#ui#connect(s:CaseEntry, s:map_state_to_props) 29 | endfu 30 | -------------------------------------------------------------------------------- /autoload/esearch/ui/menu/context_entry.vim: -------------------------------------------------------------------------------- 1 | let s:UnsignedIntEntry = esearch#ui#menu#unsigned_int_entry#import() 2 | let s:ContextEntry = esearch#ui#component() 3 | 4 | fu! s:ContextEntry.new(props) abort dict 5 | let instance = extend(copy(self), {'props': a:props}) 6 | let instance.entry = s:UnsignedIntEntry.new() 7 | let instance.entry.props['+'] = 'c' 8 | let instance.entry.props['-'] = 'C' 9 | let instance.entry.props.name = 'context' 10 | let instance.entry.props.option = a:props._adapter.context.opt 11 | let instance.entry.props.hint = a:props._adapter.context.hint 12 | let instance.entry.props.value = a:props.context 13 | let instance.entry.props.i = a:props.i 14 | let updown = g:esearch#has#unicode ? g:esearch#unicode#updown : '^v' 15 | let instance.entry.props.icon = '['.updown.']' 16 | 17 | return instance 18 | endfu 19 | 20 | fu! s:ContextEntry.render() abort dict 21 | return self.entry.render() 22 | endfu 23 | 24 | fu! s:ContextEntry.keypress(event) abort dict 25 | return self.entry.keypress(a:event) 26 | endfu 27 | 28 | let s:map_state_to_props = esearch#util#slice_factory(['context', '_adapter']) 29 | 30 | fu! esearch#ui#menu#context_entry#import() abort 31 | return esearch#ui#connect(s:ContextEntry, s:map_state_to_props) 32 | endfu 33 | -------------------------------------------------------------------------------- /autoload/esearch/ui/menu/filetype_entry.vim: -------------------------------------------------------------------------------- 1 | let s:String = vital#esearch#import('Data.String') 2 | let s:List = vital#esearch#import('Data.List') 3 | let s:PathPrompt = esearch#ui#prompt#path#import() 4 | let s:FiletypeEntry = esearch#ui#component() 5 | 6 | fu! s:FiletypeEntry.render() abort dict 7 | let icon = empty(self.props.filetypes) ? ['Comment', '[ft]'] : ['Special', '[ft]'] 8 | let result = [['None', s:String.pad_right(self.props.keys[0], 7, ' ')], icon, ['NONE', ' search only in filetypes']] 9 | 10 | if empty(self.props.filetypes) 11 | let result += [['Comment', ' (none)']] 12 | else 13 | let result += [['Comment', ' ('.self.props._adapter.filetypes2args(self.props.filetypes).')']] 14 | endif 15 | 16 | return result 17 | endfu 18 | 19 | fu! s:FiletypeEntry.keypress(event) abort dict 20 | if s:List.has(self.props.keys, a:event.key) || a:event.key ==# "\" 21 | call self.props.dispatch({'type': 'SET_LOCATION', 'location': 'filetype_input'}) 22 | let stop_propagation = 1 23 | return stop_propagation 24 | end 25 | endfu 26 | 27 | let s:map_state_to_props = esearch#util#slice_factory(['filetypes', '_adapter']) 28 | 29 | fu! esearch#ui#menu#filetype_entry#import() abort 30 | return esearch#ui#connect(s:FiletypeEntry, s:map_state_to_props) 31 | endfu 32 | -------------------------------------------------------------------------------- /autoload/esearch/ui/menu/path_entry.vim: -------------------------------------------------------------------------------- 1 | let s:String = vital#esearch#import('Data.String') 2 | let s:List = vital#esearch#import('Data.List') 3 | let s:PathPrompt = esearch#ui#prompt#path#import() 4 | let s:PathEntry = esearch#ui#component() 5 | 6 | fu! s:PathEntry.render() abort dict 7 | let icon = empty(self.props.paths) ? ['Comment', '[./]'] : ['Directory', '[./]'] 8 | let result = [['None', s:String.pad_right(self.props.keys[0], 7, ' ')], icon, ['NONE', ' search only in paths']] 9 | 10 | if empty(self.props.paths) 11 | let result += [['Comment', ' (none)']] 12 | else 13 | let result += [['Comment', ' (']] 14 | let result += s:PathPrompt.new({'normal_hl': 'Comment'}).render() 15 | let result += [['Comment', ')']] 16 | endif 17 | 18 | return result 19 | endfu 20 | 21 | fu! s:PathEntry.keypress(event) abort dict 22 | if s:List.has(self.props.keys, a:event.key) || a:event.key ==# "\" 23 | call self.props.dispatch({'type': 'SET_LOCATION', 'location': 'path_input'}) 24 | let stop_propagation = 1 25 | return stop_propagation 26 | end 27 | endfu 28 | 29 | fu! s:map_state_to_props(state) abort dict 30 | return {'paths': get(a:state, 'paths')} 31 | endfu 32 | 33 | fu! esearch#ui#menu#path_entry#import() abort 34 | return esearch#ui#connect(s:PathEntry, function('map_state_to_props')) 35 | endfu 36 | -------------------------------------------------------------------------------- /autoload/esearch/ui/menu/regex_entry.vim: -------------------------------------------------------------------------------- 1 | let s:String = vital#esearch#import('Data.String') 2 | let s:List = vital#esearch#import('Data.List') 3 | let s:RegexEntry = esearch#ui#component() 4 | 5 | fu! s:RegexEntry.render() abort dict 6 | let icon = self.props.regex is# 'literal' ? ['Comment', '\.\*'] : ['String', '/.*/'] 7 | 8 | let result = [['None', s:String.pad_right(self.props.keys[0], 7, ' ')], icon, ['NONE', ' regex match']] 9 | let option = self.props._adapter.regex[self.props.regex].option 10 | let option = join(filter([self.props.regex, option], '!empty(v:val)'), ': ') 11 | let result += [['Comment', ' (' . option . ')']] 12 | 13 | return result 14 | endfu 15 | 16 | fu! s:RegexEntry.keypress(event) abort dict 17 | if s:List.has(self.props.keys, a:event.key) || a:event.key ==# "\" 18 | call self.props.dispatch({'type': 'NEXT_REGEX'}) 19 | let stop_propagation = 1 20 | return stop_propagation 21 | endif 22 | endfu 23 | 24 | let s:map_state_to_props = esearch#util#slice_factory(['regex', '_adapter']) 25 | 26 | fu! esearch#ui#menu#regex_entry#import() abort 27 | return esearch#ui#connect(s:RegexEntry, s:map_state_to_props) 28 | endfu 29 | -------------------------------------------------------------------------------- /autoload/esearch/ui/menu/textobj_entry.vim: -------------------------------------------------------------------------------- 1 | let s:String = vital#esearch#import('Data.String') 2 | let s:List = vital#esearch#import('Data.List') 3 | let s:TextobjEntry = esearch#ui#component() 4 | 5 | fu! s:TextobjEntry.render() abort dict 6 | let icon = self.props.textobj ==# 'none' ? ['Comment', '[""]'] : 7 | \ self.props.textobj ==# 'word' ? ['Keyword', '[\b]'] : ['String', '[^$]'] 8 | let result = [['None', s:String.pad_right(self.props.keys[0], 7, ' ')], icon, ['NONE', ' textobj match']] 9 | let option = self.props._adapter.textobj[self.props.textobj].option 10 | let option = join(filter([self.props.textobj, option], '!empty(v:val)'), ': ') 11 | let result += [['Comment', ' (' . option . ')']] 12 | 13 | return result 14 | endfu 15 | 16 | fu! s:TextobjEntry.keypress(event) abort dict 17 | if s:List.has(self.props.keys, a:event.key) || a:event.key ==# "\" 18 | call self.props.dispatch({'type': 'NEXT_TEXTOBJ'}) 19 | let stop_propagation = 1 20 | return stop_propagation 21 | endif 22 | endfu 23 | 24 | let s:map_state_to_props = esearch#util#slice_factory(['textobj', '_adapter']) 25 | 26 | fu! esearch#ui#menu#textobj_entry#import() abort 27 | return esearch#ui#connect(s:TextobjEntry, s:map_state_to_props) 28 | endfu 29 | -------------------------------------------------------------------------------- /autoload/esearch/ui/prompt/case.vim: -------------------------------------------------------------------------------- 1 | let s:Case = esearch#ui#component() 2 | 3 | fu! s:Case.render() abort dict 4 | if empty(self.props._adapter.case) | return [] | endif 5 | 6 | let icon = self.props._adapter.case[self.props.case].icon 7 | return [['NONE', empty(icon) ? '>' : icon]] 8 | endfu 9 | 10 | let s:map_state_to_props = esearch#util#slice_factory(['case', '_adapter']) 11 | 12 | fu! esearch#ui#prompt#case#import() abort 13 | return esearch#ui#connect(s:Case, s:map_state_to_props) 14 | endfu 15 | -------------------------------------------------------------------------------- /autoload/esearch/ui/prompt/current_pattern.vim: -------------------------------------------------------------------------------- 1 | let s:CurrentPattern = esearch#ui#component() 2 | 3 | fu! s:CurrentPattern.render() abort dict 4 | if !self.props._adapter.multi_pattern | return [] | endif 5 | 6 | let opt = self.props.pattern.peek().icon 7 | let args = self.get_args() 8 | return [['NONE', args.(len(args) ? ' ' : '').opt]] 9 | endfu 10 | 11 | fu! s:CurrentPattern.get_args() abort 12 | let state = self.__context__().store.state 13 | let converted = map(copy(self.props.pattern.patterns.list[:-2]), 'v:val.convert(state)') 14 | return join(map(converted, 'v:val.opt . v:val.arg')) 15 | endfu 16 | 17 | let s:map_state_to_props = esearch#util#slice_factory(['pattern', '_adapter']) 18 | 19 | fu! esearch#ui#prompt#current_pattern#import() abort 20 | return esearch#ui#connect(s:CurrentPattern, s:map_state_to_props) 21 | endfu 22 | -------------------------------------------------------------------------------- /autoload/esearch/ui/prompt/filetype.vim: -------------------------------------------------------------------------------- 1 | let s:Dict = vital#esearch#import('Data.Dict') 2 | let s:FiletypePrompt = esearch#ui#component() 3 | 4 | fu! s:FiletypePrompt.render() abort dict 5 | if empty(self.props.filetypes) || empty(self.props._adapter.filetypes) 6 | return [] 7 | endif 8 | 9 | let result = [] 10 | let available_filetypes = s:Dict.make_index(self.props._adapter.filetypes) 11 | for filetype in split(self.props.filetypes) 12 | let highlight = get(available_filetypes, filetype) ? 'Typedef' : self.props.normal_hl 13 | let result += [[highlight, '<.'.filetype.'>']] + [[self.props.normal_hl, ' ']] 14 | endfor 15 | 16 | return result 17 | endfu 18 | 19 | let s:FiletypePrompt.default_props = {'normal_hl': 'NONE'} 20 | let s:map_state_to_props = esearch#util#slice_factory(['_adapter', 'filetypes']) 21 | 22 | fu! esearch#ui#prompt#filetype#import() abort 23 | return esearch#ui#connect(s:FiletypePrompt, s:map_state_to_props) 24 | endfu 25 | -------------------------------------------------------------------------------- /autoload/esearch/ui/prompt/path.vim: -------------------------------------------------------------------------------- 1 | let s:Log = esearch#log#import() 2 | let s:PathPrompt = esearch#ui#component() 3 | 4 | fu! s:PathPrompt.render() abort dict 5 | if empty(self.props.paths) 6 | return [] 7 | elseif type(self.props.paths) ==# type({}) 8 | return [['Special', self.props.paths.repr()]] 9 | elseif !g:esearch#has#posix_shell 10 | return [[self.props.normal_hl, esearch#shell#join(self.props.paths)]] 11 | endif 12 | 13 | let cwd = self.props.cwd 14 | let paths = self.props.paths 15 | let result = [] 16 | let dir_icon = g:esearch#cmdline#dir_icon 17 | let end = len(paths) - 1 18 | for i in range(0, end) 19 | let path = paths[i] 20 | if path.raw 21 | let result += [['Special', path.str]] 22 | elseif isdirectory(esearch#util#abspath(cwd, path.str)) 23 | let result += [['Directory', dir_icon . esearch#shell#escape(path)]] 24 | elseif empty(path.metachars) 25 | let result += [[self.props.normal_hl, esearch#shell#escape(path)]] 26 | else 27 | let result += self.highlight_metachars(path) 28 | endif 29 | 30 | if i != end && !empty(self.props.separator) 31 | let result += [[self.props.normal_hl, self.props.separator]] 32 | endif 33 | endfor 34 | 35 | return result 36 | endfu 37 | 38 | fu! s:PathPrompt.highlight_metachars(path) abort dict 39 | let parts = esearch#shell#split_by_metachars(a:path) 40 | let result = [] 41 | 42 | for regular_index in range(0, len(parts)-3, 2) 43 | let i = regular_index + 1 44 | let result += [[self.props.normal_hl, parts[regular_index]]] 45 | let result += [['Identifier', parts[i]]] 46 | endfor 47 | 48 | let result += [[self.props.normal_hl, parts[-1]]] 49 | return result 50 | endfu 51 | 52 | let s:PathPrompt.default_props = {'normal_hl': 'NONE', 'separator': ' '} 53 | 54 | let s:map_state_to_props = esearch#util#slice_factory(['cwd', 'paths']) 55 | 56 | fu! esearch#ui#prompt#path#import() abort 57 | return esearch#ui#connect(s:PathPrompt, s:map_state_to_props) 58 | endfu 59 | -------------------------------------------------------------------------------- /autoload/esearch/ui/prompt/regex.vim: -------------------------------------------------------------------------------- 1 | let s:Regex = esearch#ui#component() 2 | 3 | fu! s:Regex.render() abort dict 4 | if empty(self.props._adapter.regex) | return [] | endif 5 | 6 | let icon = self.props._adapter.regex[self.props.regex].icon 7 | return [['NONE', empty(icon) ? '>' : icon]] 8 | endfu 9 | 10 | let s:map_state_to_props = esearch#util#slice_factory(['regex', '_adapter']) 11 | 12 | fu! esearch#ui#prompt#regex#import() abort 13 | return esearch#ui#connect(s:Regex, s:map_state_to_props) 14 | endfu 15 | -------------------------------------------------------------------------------- /autoload/esearch/ui/prompt/search.vim: -------------------------------------------------------------------------------- 1 | let s:Case = esearch#ui#prompt#case#import() 2 | let s:Regex = esearch#ui#prompt#regex#import() 3 | let s:Textobj = esearch#ui#prompt#textobj#import() 4 | let s:CurrentPattern = esearch#ui#prompt#current_pattern#import() 5 | 6 | let s:SearchPrompt = esearch#ui#component() 7 | 8 | fu! s:SearchPrompt.new(props) abort dict 9 | let instance = extend(copy(self), {'props': a:props}) 10 | let instance.items = [ 11 | \ s:CurrentPattern.new({'key': 'p'}), 12 | \ s:Case.new({'key': 's'}), 13 | \ s:Regex.new({'key': 'r'}), 14 | \ s:Textobj.new({'key': 'w'}), 15 | \ ] 16 | let instance.height = len(instance.items) 17 | 18 | return instance 19 | endfu 20 | 21 | fu! s:SearchPrompt.render() abort dict 22 | let result = [['NONE', self.props.adapter.' ']] 23 | for item in self.items 24 | let result += item.render() 25 | endfor 26 | let result += [['NONE', ' ']] 27 | 28 | return result 29 | endfu 30 | 31 | let s:map_state_to_props = esearch#util#slice_factory(['adapter']) 32 | 33 | fu! esearch#ui#prompt#search#import() abort 34 | return esearch#ui#connect(s:SearchPrompt, s:map_state_to_props) 35 | endfu 36 | -------------------------------------------------------------------------------- /autoload/esearch/ui/prompt/textobj.vim: -------------------------------------------------------------------------------- 1 | let s:Textobj = esearch#ui#component() 2 | 3 | fu! s:Textobj.render() abort dict 4 | if empty(self.props._adapter.textobj) | return [] | endif 5 | 6 | let icon = self.props._adapter.textobj[self.props.textobj].icon 7 | return [['NONE', empty(icon) ? '>' : icon]] 8 | endfu 9 | 10 | let s:map_state_to_props = esearch#util#slice_factory(['textobj', '_adapter']) 11 | 12 | fu! esearch#ui#prompt#textobj#import() abort 13 | return esearch#ui#connect(s:Textobj, s:map_state_to_props) 14 | endfu 15 | -------------------------------------------------------------------------------- /autoload/esearch/undotree.vim: -------------------------------------------------------------------------------- 1 | let s:Log = esearch#log#import() 2 | 3 | fu! esearch#undotree#import() abort 4 | return copy(s:Undotree) 5 | endfu 6 | 7 | let s:Undotree = {} 8 | 9 | fu! s:Undotree.new(state) abort dict 10 | let initial = s:node(a:state) 11 | let nodes = {'0': initial} 12 | let nodes[changenr()] = initial 13 | let head = s:node(copy(a:state)) 14 | let written = extend(copy(head), {'changenr': empty(undotree().entries) ? 0 : changenr()}) 15 | return extend(copy(self), { 16 | \ 'written': written, 17 | \ 'head': initial, 18 | \ 'nodes': nodes, 19 | \}) 20 | endfu 21 | 22 | fu! s:node(state) abort 23 | return {'changenr': changenr(), 'state': a:state} 24 | endfu 25 | 26 | fu! s:Undotree.has(changenr) abort dict 27 | return has_key(self.nodes, a:changenr) 28 | endfu 29 | 30 | fu! s:Undotree.commit(state) abort dict 31 | let self.head = s:node(a:state) 32 | let self.nodes[self.head.changenr] = self.head 33 | return self.head.state 34 | endfu 35 | 36 | fu! s:Undotree.on_write() abort dict 37 | let self.written = self.head 38 | endfu 39 | 40 | fu! s:Undotree.checkout(changenr, ...) abort dict 41 | let self.head = self.nodes[a:changenr] 42 | return self.head.state 43 | endfu 44 | 45 | fu! s:Undotree.squash(state) abort dict 46 | let self.head = s:node(a:state) 47 | let self.nodes = {'0': self.head} 48 | let self.nodes[changenr()] = self.head 49 | endfu 50 | -------------------------------------------------------------------------------- /autoload/esearch/unicode.vim: -------------------------------------------------------------------------------- 1 | scriptencoding utf-8 2 | 3 | if exists('g:WebDevIconsUnicodeDecorateFolderNodesDefaultSymbol') 4 | let g:esearch#unicode#dir_icon = g:WebDevIconsUnicodeDecorateFolderNodesDefaultSymbol 5 | elseif has('osx') 6 | let g:esearch#unicode#dir_icon = '📂 ' 7 | else 8 | let g:esearch#unicode#dir_icon = '🗀 ' 9 | endif 10 | let g:esearch#unicode#spinner = [' ◜ ', ' ◝', ' ◞', ' ◟ '] 11 | let g:esearch#unicode#less_or_equal = '≤' 12 | let g:esearch#unicode#slash = '∕' 13 | let g:esearch#unicode#quote_right = '›' 14 | let g:esearch#unicode#quote_left = '‹' 15 | if has('osx') 16 | let g:esearch#unicode#up = '∧' 17 | let g:esearch#unicode#down = '∨' 18 | let g:esearch#unicode#updown = '∧∨' 19 | else 20 | let g:esearch#unicode#up = '🡑' 21 | let g:esearch#unicode#down = '🡓' 22 | let g:esearch#unicode#updown = '🡑🡓' 23 | endif 24 | let g:esearch#unicode#arrow_right = ' ➔' 25 | -------------------------------------------------------------------------------- /autoload/health/esearch.vim: -------------------------------------------------------------------------------- 1 | fu! health#esearch#check() abort 2 | try 3 | let util = esearch#config#default_adapter() 4 | if util ==# 'grep' || util ==# 'git' 5 | call health#report_warn("Can't find a fast search util executable.", "Install one of rg, ag, pt or ack.") 6 | else 7 | call health#report_ok('Search util "' . util . '" is available.') 8 | endif 9 | catch 10 | call health#report_error("Can't find a search util executable.", "Install one of rg, ag, pt or ack.") 11 | endtry 12 | if g:esearch#has#lua 13 | call health#report_ok('Lua interface is available.') 14 | else 15 | call health#report_warn("Lua interface isn't available.", "Install vim with lua support for better performance.") 16 | endif 17 | if g:esearch#has#jobs 18 | call health#report_ok('Asynchronous processing is available.') 19 | else 20 | call health#report_warn("Asynchronous processing isn't available.", "Install vim with job control.") 21 | endif 22 | 23 | if g:esearch#has#preview 24 | call health#report_ok('Floating preview feature is available.') 25 | else 26 | call health#report_info("Floating preview feature isn't available. Neovim of version >=0.4.0 is required.") 27 | endif 28 | if g:esearch#has#annotations 29 | call health#report_ok('Virtual text annotations are available.') 30 | else 31 | call health#report_info("Virtual text annotations aren't available. Neovim of version >=0.4.3 is required.") 32 | endif 33 | if g:esearch#has#unicode 34 | call health#report_ok('Unicode icons are available.') 35 | else 36 | call health#report_info("Unicode icons aren't available. +multi_byte feature and utf-8 encoding settings are required") 37 | endif 38 | endfu 39 | -------------------------------------------------------------------------------- /autoload/unite/sources/outline/defaults/esearch.vim: -------------------------------------------------------------------------------- 1 | fu! unite#sources#outline#defaults#esearch#outline_info() abort 2 | return extend(copy(s:outline_info), {'is_volatile': !b:esearch.request.finished}) 3 | endfu 4 | 5 | let s:outline_info = { 6 | \ 'heading' : g:esearch#out#win#filename_re . '\%>2l', 7 | \ 'highlight_rules': [ 8 | \ { 'name' : 'filename', 9 | \ 'pattern' : '/^.*/', 10 | \ 'highlight': 'esearchFilename' }, 11 | \ ], 12 | \} 13 | 14 | if g:esearch#has#lua 15 | fu! s:outline_info.extract_headings(context) abort 16 | return luaeval('esearch.extract_headings(_A)', a:context.buffer.nr) 17 | endfu 18 | else 19 | fu! s:outline_info.create_heading(_which, heading_line, _matched_line, _context) abort 20 | return {'word': a:heading_line, 'type': 'filename'} 21 | endfu 22 | endif 23 | -------------------------------------------------------------------------------- /autoload/vital/_esearch.vim: -------------------------------------------------------------------------------- 1 | let s:_plugin_name = expand(':t:r') 2 | 3 | function! vital#{s:_plugin_name}#new() abort 4 | return vital#{s:_plugin_name[1:]}#new() 5 | endfunction 6 | 7 | function! vital#{s:_plugin_name}#function(funcname) abort 8 | silent! return function(a:funcname) 9 | endfunction 10 | -------------------------------------------------------------------------------- /autoload/vital/_esearch/Vim/Message.vim: -------------------------------------------------------------------------------- 1 | " ___vital___ 2 | " NOTE: lines between '" ___vital___' is generated by :Vitalize. 3 | " Do not modify the code nor insert new lines before '" ___vital___' 4 | function! s:_SID() abort 5 | return matchstr(expand(''), '\zs\d\+\ze__SID$') 6 | endfunction 7 | execute join(['function! vital#_esearch#Vim#Message#import() abort', printf("return map({'capture': '', 'echomsg': '', 'echo': '', 'warn': '', 'get_hit_enter_max_length': '', 'error': ''}, \"vital#_esearch#function('%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n") 8 | delfunction s:_SID 9 | " ___vital___ 10 | let s:save_cpo = &cpo 11 | set cpo&vim 12 | 13 | 14 | 15 | function! s:echo(hl, msg) abort 16 | execute 'echohl' a:hl 17 | try 18 | echo a:msg 19 | finally 20 | echohl None 21 | endtry 22 | endfunction 23 | 24 | function! s:echomsg(hl, msg) abort 25 | execute 'echohl' a:hl 26 | try 27 | for m in split(a:msg, "\n") 28 | echomsg m 29 | endfor 30 | finally 31 | echohl None 32 | endtry 33 | endfunction 34 | 35 | function! s:error(msg) abort 36 | call s:echomsg('ErrorMsg', a:msg) 37 | endfunction 38 | 39 | function! s:warn(msg) abort 40 | call s:echomsg('WarningMsg', a:msg) 41 | endfunction 42 | 43 | function! s:capture(command) abort 44 | try 45 | redir => out 46 | silent execute a:command 47 | finally 48 | redir END 49 | endtry 50 | return out 51 | endfunction 52 | 53 | " * Get max length of |hit-enter|. 54 | " If a string length of a message is greater than the max length, 55 | " Vim waits for user input according to |hit-enter|. 56 | " XXX: Those fixed values may be different between different OSes? 57 | " Currently tested on only Windows. 58 | function! s:get_hit_enter_max_length() abort 59 | let maxlen = &columns * &cmdheight - 1 60 | if &ruler 61 | " TODO 62 | endif 63 | if &showcmd 64 | let maxlen -= 11 65 | endif 66 | return maxlen 67 | endfunction 68 | 69 | 70 | 71 | let &cpo = s:save_cpo 72 | unlet s:save_cpo 73 | 74 | " vim:set et ts=2 sts=2 sw=2 tw=0: 75 | -------------------------------------------------------------------------------- /autoload/vital/esearch.vital: -------------------------------------------------------------------------------- 1 | esearch 2 | 69eca5ec64744e3545a5c1f9129fac3ed900cc3c 3 | 4 | Async.Promise 5 | Data.OrderedSet 6 | Text.Parser 7 | Text.Lexer 8 | Data.Dict 9 | Vim.Message 10 | Data.String 11 | System.Filepath 12 | Vim.Highlight 13 | Vim.BufferManager 14 | Mapping 15 | -------------------------------------------------------------------------------- /lua/esearch/nvim.lua: -------------------------------------------------------------------------------- 1 | local matches = require('esearch/nvim/appearance/matches') 2 | local annotations = require('esearch/nvim/appearance/annotations') 3 | local ui = require('esearch/nvim/appearance/ui') 4 | local cursor_linenr = require('esearch/nvim/appearance/cursor_linenr') 5 | 6 | return { 7 | ANNOTATIONS_NS = annotations.ANNOTATIONS_NS, 8 | ATTACHED_ANNOTATIONS = annotations.ATTACHED_ANNOTATIONS, 9 | set_context_len_annotation = annotations.set_context_len_annotation, 10 | annotate = annotations.annotate, 11 | buf_clear_annotations = annotations.buf_clear_annotations, 12 | buf_attach_annotations = annotations.buf_attach_annotations, 13 | 14 | MATCHES_NS = matches.MATCHES_NS, 15 | ATTACHED_MATCHES = matches.ATTACHED_MATCHES, 16 | deferred_highlight_viewport = matches.deferred_highlight_viewport, 17 | highlight_viewport = matches.highlight_viewport, 18 | buf_attach_matches = matches.buf_attach_matches, 19 | 20 | UI_NS = ui.UI_NS, 21 | ATTACHED_UI = ui.ATTACHED_UI, 22 | highlight_header = ui.highlight_header, 23 | buf_attach_ui = ui.buf_attach_ui, 24 | highlight_ui = ui.highlight_ui, 25 | 26 | CURSOR_LINENR_NS = cursor_linenr.CURSOR_LINENR_NS, 27 | highlight_cursor_linenr = cursor_linenr.highlight_cursor_linenr, 28 | 29 | render = require('esearch/nvim/render').render, 30 | parse = require('esearch/nvim/parse').parse, 31 | util = require('esearch/shared/util'), 32 | extract_headings = require('esearch/shared/outline').extract_headings, 33 | } 34 | -------------------------------------------------------------------------------- /lua/esearch/nvim/appearance/annotations.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | ATTACHED_ANNOTATIONS = {}, 3 | ANNOTATIONS_NS = vim.api.nvim_create_namespace('esearch_annotations'), 4 | } 5 | 6 | local function on_lines(_event_name, bufnr, _changedtick, from, old_to, to, _old_byte_size) 7 | if to < old_to then -- if lines are removed 8 | vim.api.nvim_buf_clear_namespace(bufnr, M.ANNOTATIONS_NS, from, to + 1) 9 | end 10 | end 11 | 12 | local function on_detach(bufnr) 13 | M.ATTACHED_ANNOTATIONS[bufnr] = nil 14 | end 15 | 16 | function M.buf_attach_annotations() 17 | local bufnr = vim.api.nvim_get_current_buf() 18 | if not M.ATTACHED_ANNOTATIONS[bufnr] then 19 | M.ATTACHED_ANNOTATIONS[bufnr] = true 20 | vim.api.nvim_buf_attach(0, false, {on_lines=on_lines, on_detach=on_detach}) 21 | end 22 | end 23 | 24 | function M.buf_clear_annotations() 25 | vim.api.nvim_buf_clear_namespace(0, M.ANNOTATIONS_NS, 0, -1) 26 | end 27 | 28 | function M.annotate(contexts) 29 | for _, ctx in pairs(contexts) do 30 | -- don't annotate the header or not finished contexts 31 | if ctx['id'] > 0 and ctx['end'] > 0 then 32 | M.set_context_len_annotation(ctx['begin'], ctx['end'] - ctx['begin'] - 1) 33 | end 34 | end 35 | end 36 | 37 | function M.set_context_len_annotation(line, size) 38 | if size == 1 then 39 | vim.api.nvim_buf_set_virtual_text(0, M.ANNOTATIONS_NS, line - 1, {{size .. ' line', 'Comment'}}, {}) 40 | else 41 | vim.api.nvim_buf_set_virtual_text(0, M.ANNOTATIONS_NS, line - 1, {{size .. ' lines', 'Comment'}}, {}) 42 | end 43 | end 44 | 45 | return M 46 | -------------------------------------------------------------------------------- /lua/esearch/nvim/appearance/cursor_linenr.lua: -------------------------------------------------------------------------------- 1 | local ui = require('esearch/nvim/appearance/ui') 2 | 3 | local M = { 4 | CURSOR_LINENR_NS = vim.api.nvim_create_namespace('esearch_cursor_linenr'), 5 | } 6 | 7 | function M.highlight_cursor_linenr() 8 | local current_line = vim.api.nvim_get_current_line() 9 | local _, last_col = current_line:find(ui.LINENR_RE) 10 | local lnum = vim.api.nvim_win_get_cursor(0)[1] - 1 11 | local ns = M.CURSOR_LINENR_NS 12 | local bufnr = vim.api.nvim_get_current_buf() 13 | 14 | vim.schedule(function() 15 | if vim.api.nvim_call_function('bufexists', {bufnr}) == 0 then return end 16 | 17 | -- the condition is needed to prevent adding highlights to the buffer when leaving them 18 | if bufnr ~= vim.api.nvim_get_current_buf() then return end 19 | 20 | vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) 21 | if last_col then 22 | vim.api.nvim_buf_add_highlight(bufnr, ns, 'esearchCursorLineNr', lnum, 0, last_col) 23 | end 24 | end) 25 | end 26 | 27 | return M 28 | -------------------------------------------------------------------------------- /lua/esearch/nvim/parse.lua: -------------------------------------------------------------------------------- 1 | local PARSERS = require('esearch/shared/adapter/parse').PARSERS 2 | 3 | local M = {} 4 | 5 | local function entry_constructor(tbl) 6 | return tbl 7 | end 8 | 9 | function M.parse(data, parser) 10 | return PARSERS[parser](data, entry_constructor) 11 | end 12 | 13 | return M 14 | -------------------------------------------------------------------------------- /lua/esearch/nvim/util.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.unpack = unpack and unpack or table.unpack 4 | 5 | M.ifirst = 1 6 | 7 | function M.ilast(tbl) 8 | return #tbl 9 | end 10 | 11 | M.NIL = vim.NIL or {} 12 | 13 | function M.list(tbl) 14 | return tbl 15 | end 16 | 17 | function M.dict(tbl) 18 | return tbl 19 | end 20 | 21 | function M.append(tbl, val) 22 | tbl[#tbl + 1] = val 23 | end 24 | 25 | function M.buf_get_lines(bufnr) 26 | return vim.api.nvim_buf_get_lines(bufnr or 0, 0, -1, true) 27 | end 28 | 29 | if vim.fn then -- neovim >= 0.5 30 | function M.json_decode(text) 31 | return vim.fn.json_decode(text) 32 | end 33 | 34 | function M.fnameescape(path) 35 | return vim.fn.fnameescape(path) 36 | end 37 | 38 | function M.filereadable(path, cache) 39 | if cache[path] then return true end 40 | local result = vim.fn.filereadable(path) == 1 41 | cache[path] = result 42 | return result 43 | end 44 | else -- neovim < 0.5 45 | function M.json_decode(text) 46 | return vim.api.nvim_call_function('json_decode', {text}) 47 | end 48 | 49 | function M.fnameescape(path) 50 | return vim.api.nvim_call_function('fnameescape', {path}) 51 | end 52 | 53 | function M.filereadable(path, cache) 54 | if cache[path] then return true end 55 | local result = vim.api.nvim_call_function('filereadable', {path}) == 1 56 | cache[path] = result 57 | return result 58 | end 59 | end 60 | 61 | function M.debounce(callback, wait) 62 | local fn = {} 63 | 64 | setmetatable(fn, {__call = function(_, ...) 65 | if fn.timer then 66 | fn.timer:stop() 67 | if not fn.timer:is_closing() then 68 | fn.timer:close() 69 | end 70 | end 71 | fn.timer = M.set_timeout(callback, wait, ...) 72 | end}) 73 | 74 | return fn 75 | end 76 | 77 | function M.set_timeout(callback, delay, ...) 78 | local timer = vim.loop.new_timer() 79 | local args = {...} 80 | timer:start(delay, 0, function() 81 | timer:stop() 82 | timer:close() 83 | callback(M.unpack(args)) 84 | end) 85 | return timer 86 | end 87 | 88 | return M 89 | -------------------------------------------------------------------------------- /lua/esearch/shared/outline.lua: -------------------------------------------------------------------------------- 1 | local util = require('esearch/shared/util') 2 | local list, dict, buf_get_lines = util.list, util.dict, util.buf_get_lines 3 | 4 | local M = {} 5 | 6 | function M.extract_headings(bufnr) 7 | local headings = {} 8 | local lines = buf_get_lines(bufnr) 9 | for lnum = 1, #lines do 10 | local word = lines[lnum] 11 | if lnum > 2 and word:len() > 0 and word:sub(1, 1) ~= ' ' then 12 | headings[#headings + 1] = dict({ 13 | lnum = tostring(lnum), 14 | word = word, 15 | ['type'] = 'filename', 16 | level = '1', 17 | }) 18 | end 19 | end 20 | 21 | return list(headings) 22 | end 23 | 24 | return M 25 | -------------------------------------------------------------------------------- /lua/esearch/shared/util.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | if vim.api then 4 | M = require('esearch/nvim/util') 5 | else 6 | M = require('esearch/vim/util') 7 | end 8 | 9 | -- From https://www.lua.org/pil/20.4.html. Is used to perform unquoting 10 | function M.code(s) 11 | return (string.gsub(s, "\\([\\\"])", function (x) 12 | return string.format("\\%03d", string.byte(x)) 13 | end)) 14 | end 15 | 16 | function M.decode(s) 17 | return (string.gsub(s, "\\(%d%d%d)", function (d) 18 | return "\\" .. string.char(d) 19 | end)) 20 | end 21 | 22 | function M.is_true(val) 23 | return val == 1 or val == true 24 | end 25 | 26 | return M 27 | -------------------------------------------------------------------------------- /lua/esearch/vim.lua: -------------------------------------------------------------------------------- 1 | return { 2 | parse = require('esearch/vim/parse').parse, 3 | render = require('esearch/vim/render').render, 4 | util = require('esearch/shared/util'), 5 | extract_headings = require('esearch/shared/outline').extract_headings, 6 | } 7 | -------------------------------------------------------------------------------- /lua/esearch/vim/parse.lua: -------------------------------------------------------------------------------- 1 | local PARSERS = require('esearch/shared/adapter/parse').PARSERS 2 | 3 | local M = {} 4 | 5 | function M.parse(data, parser) 6 | local entries, lines_delta, errors = PARSERS[parser](data, vim.dict) 7 | return vim.list(entries), lines_delta, errors 8 | end 9 | 10 | return M 11 | -------------------------------------------------------------------------------- /lua/esearch/vim/util.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.unpack = unpack and unpack or table.unpack 4 | 5 | if vim.funcref('has')('patch-8.2.1066') == 1 then 6 | M.ifirst = 1 7 | function M.ilast(tbl) 8 | return #tbl 9 | end 10 | else 11 | M.ifirst = 0 12 | function M.ilast(tbl) 13 | return #tbl - 1 14 | end 15 | end 16 | M.NIL = vim.list() 17 | 18 | function M.list(tbl) 19 | return vim.list(tbl) 20 | end 21 | 22 | function M.dict(tbl) 23 | return vim.dict(tbl) 24 | end 25 | 26 | function M.append(tbl, val) 27 | tbl:add(val) 28 | end 29 | 30 | function M.json_decode(text) 31 | return vim.funcref('json_decode')(text) 32 | end 33 | 34 | function M.fnameescape(path) 35 | return vim.funcref('fnameescape')(path) 36 | end 37 | 38 | function M.filereadable(path, cache) 39 | if cache[path] then return true end 40 | local result = vim.funcref('filereadable')(path) == 1 41 | cache[path] = result 42 | return result 43 | end 44 | 45 | function M.buf_get_lines(bufnr) 46 | return vim.buffer(bufnr) 47 | end 48 | 49 | return M 50 | -------------------------------------------------------------------------------- /plugin/esearch.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_esearch') 2 | finish 3 | endif 4 | let g:loaded_esearch = '0.4.12' 5 | 6 | if !hasmapto('(esearch)') && !hasmapto('(operator-esearch-prefill)') && get(get(g:, 'esearch', {}), 'default_mappings', 1) 7 | nmap ff (esearch) 8 | map f (operator-esearch-prefill) 9 | endif 10 | 11 | nnoremap (esearch) :call esearch#init({'remember': 1}) 12 | xmap (esearch) (operator-esearch-prefill) 13 | 14 | noremap (operator-esearch-prefill) esearch#prefill({'remember': 1}) 15 | noremap (operator-esearch-exec) esearch#exec({'remember': 1}) 16 | -------------------------------------------------------------------------------- /spec/api.vader: -------------------------------------------------------------------------------- 1 | Include: helper.vader 2 | 3 | Before: 4 | Save g:esearch 5 | let g:esearch.cwd = 'spec/fixtures/api'.g:test_number.next().'/' 6 | After: 7 | Restore g:esearch 8 | 9 | Execute(.reload with params): 10 | call Fixture(g:esearch.cwd.'file.txt', ['text']) 11 | call esearch#init({'pattern': '.'}) 12 | AssertEqual b:esearch.before, 0 13 | call b:esearch.reload({'before': 42}) 14 | AssertEqual b:esearch.before, 42 15 | 16 | Execute(.filetype with existing filetype): 17 | call Fixture(g:esearch.cwd.'file.vim', ['text']) 18 | call esearch#init({'pattern': '.'}) 19 | AssertEqual b:esearch.filetype(), 'vim' 20 | 21 | Execute(.filetype with missing filetype): 22 | call Fixture(g:esearch.cwd.'file.custom', ['text']) 23 | call esearch#init({'pattern': '.'}) 24 | AssertEqual b:esearch.filetype(), v:null 25 | 26 | Execute(.filetype with {'fast': 1}): 27 | call Fixture(g:esearch.cwd.'file.vim', ['text']) 28 | call esearch#init({'pattern': '.'}) 29 | AssertEqual b:esearch.filetype({'fast': 1}), 'vim' 30 | 31 | Execute(.filetype with {'fast': 0}): 32 | call Fixture(g:esearch.cwd.'file.vim', ['text']) 33 | call esearch#init({'pattern': '.'}) 34 | AssertEqual b:esearch.filetype({'fast': 0}), 'vim' 35 | -------------------------------------------------------------------------------- /spec/appearance.vader: -------------------------------------------------------------------------------- 1 | Include: helper.vader 2 | 3 | Before: 4 | Save g:esearch 5 | let g:esearch.cwd = 'spec/fixtures/appearance/' 6 | let g:file1 = g:esearch.cwd.'file1.txt' 7 | After: 8 | Restore g:esearch 9 | 10 | Execute(zero-length match highlight with viewport hl strategy): 11 | if !has('nvim') | finish | endif 12 | call Fixture(g:file1, ['l1', 'l2']) 13 | let g:esearch.win_matches_highlight_strategy = 'viewport' 14 | call esearch#init({'pattern': 'l|', 'regex': 1}) 15 | norm! G 16 | AssertEqual line('.'), line('$'), 'expect no deadlocks' 17 | -------------------------------------------------------------------------------- /spec/gogrep.vader: -------------------------------------------------------------------------------- 1 | Include: helper.vader 2 | 3 | Before: 4 | Save g:esearch 5 | let g:esearch.cwd = 'spec/fixtures/gogrep/' 6 | let g:esearch.backend = 'system' 7 | After: 8 | Restore g:esearch 9 | 10 | Execute(#win): 11 | let g:file = Fixture(g:esearch.cwd.'file.go', [ 12 | \ 'package test', 13 | \ 'func Foo() int {', 14 | \ ' return 1', 15 | \ '}', 16 | \]) 17 | call esearch#init({ 18 | \ 'adapter': 'gogrep', 19 | \ 'pattern': 'return $x', 20 | \}) 21 | AssertEqual getline(4), ' 3 return 1' 22 | 23 | Execute(#qflist): 24 | let g:file = Fixture(g:esearch.cwd.'file.go', [ 25 | \ 'package test', 26 | \ 'func Foo() int {', 27 | \ ' return 1', 28 | \ '}', 29 | \]) 30 | call esearch#init({ 31 | \ 'out': 'qflist', 32 | \ 'adapter': 'gogrep', 33 | \ 'pattern': 'return $x', 34 | \}) 35 | AssertEqual len(getqflist()), 1 36 | AssertEqual getqflist()[0].text, ' return 1' 37 | -------------------------------------------------------------------------------- /spec/input_assisting.vader: -------------------------------------------------------------------------------- 1 | Include: helper.vader 2 | 3 | Before: 4 | let g:esearch.cwd = 'spec/fixtures/input_assisting'.g:test_number.next().'/' 5 | let g:esearch.pattern = '.' 6 | let g:file = g:esearch.cwd.'file.txt' 7 | 8 | Execute (Realign when adding new lines using ): 9 | call Fixture(g:file, ['a'] + repeat([''], 8) + ['b'] + repeat([''], 989) + ['c']) 10 | call esearch#init() 11 | exe "norm /file.txt\A\|1" 12 | exe "norm A\|2\" 13 | exe "norm /a\A\|3" 14 | exe "norm A\|4\" 15 | exe "norm /b\A\|\" 16 | exe "norm /c\A\|5" 17 | exe "norm A\|6\" 18 | 19 | Expect esearch_test: 20 | Matches in 3 lines, 1 file. Finished. 21 | 22 | file.txt 23 | ^ 1 |1 24 | ^ 1 |2 25 | 1 a 26 | + 1 |3 27 | + 1 |4 28 | 10 b 29 | + 10 | 30 | 1000 c 31 | + 1000 |5 32 | + 1000 |6 33 | 34 | Execute ("I" starts insert after virtual LineNr column): 35 | call Fixture(g:file, ['a', 'b', 'c', 'd', 'e']) 36 | call esearch#init() 37 | " test correct pos after + sign from dollar pos 38 | exe "norm /a$\A\new1\$I|\" 39 | " and from zero pos 40 | exe "norm A\new2\0I|" 41 | 42 | " test add | at the correct pos when non-blank entry from dollar pos 43 | exe "norm /b$\$I|\" 44 | " and from zero pos 45 | exe "norm /c$\0I|\" 46 | 47 | " test add | at the correct pos when blank entry from dollar pos 48 | exe "norm /d$\x$I|\" 49 | " and from zero pos 50 | exe "norm /e$\x0I|\" 51 | 52 | Expect esearch_test(to locate the right position): 53 | Matches in 5 lines, 1 file. Finished. 54 | 55 | file.txt 56 | 1 a 57 | + 1 |new1 58 | + 1 |new2 59 | 2 |b 60 | 3 |c 61 | 4 | 62 | 5 | 63 | -------------------------------------------------------------------------------- /spec/known_issues.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'support/known_issues' 4 | 5 | # Match examples by user defined metadata and mark it with skip or pending: 6 | # https://relishapp.com/rspec/rspec-core/docs/metadata/user-defined-metadata 7 | # The same approach is used in other repos (ex. JRuby) to avoid overcrowding 8 | # example bodies with inline if-condition-then-pending and to keep all the 9 | # pending examples info in one place. In addition, only exceptions matching 10 | # exception_pattern are handled to avoid false positives. 11 | # 12 | # Usage: 13 | # pending! description_substring, exception_pattern, **other_metadata 14 | # skip! description_substring, **other_metadata 15 | # random_failure! description_substring, exception_pattern, **other_metadata 16 | # 17 | # #skip! and pending! follow RSpec semantics 18 | # #random_failure! works like pending, but won't fail if an example is succeeded 19 | KnownIssues.allow_tests_to_fail_matching_by_metadata do 20 | # TODO: investigate 21 | skip! '/3\d+5/', adapter: :git, matching: :regexp 22 | skip! '/3\d*5/', adapter: :git, matching: :regexp 23 | skip! '/3\d+5/', adapter: :grep, matching: :regexp 24 | skip! '/3\d*5/', adapter: :grep, matching: :regexp 25 | 26 | # Git have different way to escape globs: 27 | # `ag *.txt`, `ag \*.txt` - a wildcard and a regular string 28 | # `git grep *.txt`, `git grep \*.txt` - both metachars 29 | pending! 'globbing escaped *', /expected to have results.*got.*\[.+\]/m, adapter: :git 30 | 31 | # Ack cannot work with files named ~ 32 | pending! 'searching in a file with name "~"', /MissingEntry/, adapter: :ack 33 | pending! 'searching in a file with name "-"', /MissingEntry/, adapter: :ack 34 | end 35 | -------------------------------------------------------------------------------- /spec/let.vader: -------------------------------------------------------------------------------- 1 | Include: helper.vader 2 | 3 | Execute(Guard): 4 | if !has('nvim') | finish | endif " TODO migrate to win ids 5 | let b:buf_var = 'b:original' 6 | let w:win_var = 'w:original' 7 | let &g:makeprg = 'original' 8 | let &g:statusline = 'original' 9 | let &l:foldenable = 1 10 | let g:owerwritten = { 11 | \ '&makeprg': 'overwritten_buffer_option', 12 | \ '&equalprg': 'overwritten_global_option', 13 | \ '&statusline': 'overwritten_win_option', 14 | \ 'b:buf_var': 'ovewritten_buf_var', 15 | \ 'w:win_var': 'ovewritten_win_var', 16 | \ '&foldenable': 0, 17 | \ '&winhighlight': 'Normal:esearchNormalFloat', 18 | \ 'b:undefined': 'buf_undefined', 19 | \ 'w:undefined': 'win_undefined', 20 | \} 21 | let g:bufnr = bufnr() 22 | let g:winid = win_getid() 23 | for _ in range(3) 24 | 0tabnew 25 | $tabnew 26 | let g:guard = esearch#let#bufwin_restorable(g:bufnr, g:winid, g:owerwritten) 27 | let actual = { 28 | \ '&makeprg': getbufvar(g:bufnr, '&makeprg'), 29 | \ '&equalprg': getbufvar(g:bufnr, '&equalprg'), 30 | \ '&statusline': gettabwinvar(win_id2tabwin(g:winid)[0], g:winid, '&statusline'), 31 | \ '&foldenable': gettabwinvar(win_id2tabwin(g:winid)[0], g:winid, '&foldenable'), 32 | \ '&winhighlight': gettabwinvar(win_id2tabwin(g:winid)[0], g:winid, '&winhighlight'), 33 | \ 'b:buf_var': getbufvar(g:bufnr, 'buf_var'), 34 | \ 'w:win_var': gettabwinvar(win_id2tabwin(g:winid)[0], g:winid, 'win_var'), 35 | \ 'b:undefined': getbufvar(g:bufnr, 'undefined'), 36 | \ 'w:undefined': gettabwinvar(win_id2tabwin(g:winid)[0], g:winid, 'undefined'), 37 | \} 38 | AssertEqual g:owerwritten, actual 39 | call g:guard.restore() 40 | call win_gotoid(g:winid) 41 | AssertEqual b:buf_var, 'b:original' 42 | AssertEqual w:win_var, 'w:original' 43 | AssertEqual &g:makeprg, 'original' 44 | AssertEqual &g:statusline, 'original' 45 | " Deltetion of previously undefined variables isn't supported by vim 46 | AssertEqual &l:foldenable, 1 47 | endfor 48 | -------------------------------------------------------------------------------- /spec/lib/viml_value/serialization_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe VimlValue::SerializationHelpers do 6 | include described_class 7 | 8 | describe '#var' do 9 | subject(:dumped) { VimlValue.dump(ruby_object) } 10 | 11 | context 'vim option variable' do 12 | let(:ruby_object) { var('&filetype') } 13 | 14 | it { expect(dumped).to eq('&filetype') } 15 | end 16 | 17 | context 'vim autoloadable variable' do 18 | let(:ruby_object) { var('g:a#b') } 19 | 20 | it { expect(dumped).to eq('g:a#b') } 21 | end 22 | end 23 | 24 | describe '#func' do 25 | subject(:dumped) { VimlValue.dump(ruby_object) } 26 | 27 | context 'without arguments' do 28 | let(:ruby_object) { func('g:Given#FunctionName') } 29 | 30 | it { expect(dumped).to eq('g:Given#FunctionName()') } 31 | end 32 | 33 | context 'when not-nested arguments' do 34 | let(:ruby_object) { func('g:Given#FunctionName', 1, '2', var('&ft'), [], {}) } 35 | 36 | it { expect(dumped).to eq("g:Given#FunctionName(1,'2',&ft,[],{})") } 37 | end 38 | 39 | context 'when nested arguments' do 40 | let(:ruby_object) { func('g:Given#FunctionName', 1, '2', var('&ft'), [4, {}], key5: [6]) } 41 | 42 | it { expect(dumped).to eq("g:Given#FunctionName(1,'2',&ft,[4,{}],{'key5':[6]})") } 43 | end 44 | end 45 | 46 | describe '#funcref' do 47 | subject(:loaded) { VimlValue.load(viml_string, allow_toplevel_literals: true) } 48 | let(:viml_string) { "function('Given#FunctionName')" } 49 | 50 | it { expect(loaded).to eq(funcref('Given#FunctionName')) } 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/middleware.vader: -------------------------------------------------------------------------------- 1 | Include: helper.vader 2 | 3 | Before: 4 | Save g:esearch 5 | call esearch#config#eager() 6 | let g:esearch.prefill = [] 7 | After: 8 | Restore g:esearch 9 | 10 | Execute(insert order): 11 | let [g:before_pattern, g:after_pattern, g:before_id, g:after_id] = [0, 0, 0, 0] 12 | call g:esearch.middleware.insert_before('id', {e-> execute('let g:before_id = 1') ? e : e}) 13 | call g:esearch.middleware.insert_after('id', {e-> execute('let g:after_id = 2') ? e : e}) 14 | call g:esearch.middleware.insert_before('input', {e-> execute('let g:before_pattern = 3') ? e : e}) 15 | call g:esearch.middleware.insert_after('input', {e-> execute('let g:after_pattern = 4') ? e : e}) 16 | exe "norm \(esearch)\" 17 | AssertEqual [g:before_id, g:after_id, g:before_pattern, g:after_pattern], [1, 2, 3, 0] 18 | 19 | Execute(Handle missing cwd in esearch#middleware#cwd#apply): 20 | call esearch#init({'pattern': '.', 'cwd': 'missing-cwd'}) 21 | AssertEqual split(execute('messages'), '\n')[-1], "esearch: directory missing-cwd doesn't exist" 22 | -------------------------------------------------------------------------------- /spec/semgrep.vader: -------------------------------------------------------------------------------- 1 | Include: helper.vader 2 | 3 | Before: 4 | Save g:esearch 5 | let g:esearch.cwd = 'spec/fixtures/semgrep/' 6 | let g:esearch.backend = 'system' 7 | After: 8 | Restore g:esearch 9 | 10 | Execute(#win): 11 | if !empty($TRAVIS_BUILD_ID) | finish | endif " TODO 12 | let g:file = Fixture(g:esearch.cwd.'file.py', [ 13 | \ 'class Foo():', 14 | \ "\tpass" 15 | \]) 16 | call esearch#init({ 17 | \ 'adapter': 'semgrep', 18 | \ 'pattern': 'class $X(): ...', 19 | \ 'filetypes': 'py', 20 | \}) 21 | AssertEqual join(getline(1, '$'), "\n"), join([ 22 | \ 'Matches in 2 lines, 1 file. Finished.', 23 | \ '', 24 | \ 'file.py', 25 | \ ' 1 class Foo():', 26 | \ ' 2 pass', 27 | \], "\n") 28 | 29 | 30 | Execute(#qflist): 31 | if !empty($TRAVIS_BUILD_ID) | finish | endif " TODO 32 | let g:file = Fixture(g:esearch.cwd.'file.py', [ 33 | \ 'class Foo():', 34 | \ "\tpass" 35 | \]) 36 | call esearch#init({ 37 | \ 'adapter': 'semgrep', 38 | \ 'filetypes': 'py', 39 | \ 'pattern': 'class $X(): ...', 40 | \ 'out': 'qflist', 41 | \}) 42 | AssertEqual join(getline(1, '$'), "\n"), join([ 43 | \ 'file.py|1| class Foo():', 44 | \ 'file.py|2| pass', 45 | \], "\n") 46 | -------------------------------------------------------------------------------- /spec/support/api/esearch/configuration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class API::ESearch::Configuration 4 | include VimlValue::SerializationHelpers 5 | 6 | attr_reader :editor, :cache, :staged_configuration 7 | attr_writer :output 8 | 9 | def initialize(editor) 10 | @editor = editor 11 | @cache = CacheStore.new 12 | 13 | @staged_configuration = {} 14 | end 15 | 16 | def global 17 | editor.echo func('get', var('g:'), 'esearch', {}) 18 | end 19 | 20 | def configure(options) 21 | cache.write_multi(options) 22 | staged_configuration.deep_merge!(options.deep_symbolize_keys) 23 | end 24 | 25 | # TODO: a hack that should be rewrited in future 26 | def submit!(overwrite: true) 27 | dict = VimlValue.dump(staged_configuration) 28 | 29 | if overwrite 30 | editor.command!("let g:esearch = #{dict}") 31 | else 32 | editor.command!("if !exists('g:esearch') | "\ 33 | "let g:esearch = #{dict} | "\ 34 | 'else | '\ 35 | "call extend(g:esearch, #{dict}) | "\ 36 | 'endif') 37 | end 38 | staged_configuration.clear 39 | end 40 | 41 | def qr 42 | editor.echo var('g:esearch#unicode#quote_right') 43 | end 44 | 45 | def ql 46 | editor.echo var('g:esearch#unicode#quote_left') 47 | end 48 | 49 | def win_map 50 | staged_configuration['win_map'] ||= [] 51 | end 52 | 53 | def configure!(options) 54 | configure(options) 55 | submit!(overwrite: true) 56 | end 57 | 58 | def adapter_bin=(path) 59 | staged_configuration.deep_merge!({adapters: {adapter => {bin: path.to_s}}}.deep_symbolize_keys) 60 | end 61 | 62 | def adapter 63 | cache.fetch('adapter') do 64 | editor.echo func('get', func('get', var('g:'), 'esearch', {}), 'adapter', func('esearch#config#default_adapter')) 65 | end 66 | end 67 | 68 | def output 69 | # cache.fetch('out') do 70 | editor.echo func('get', func('get', var('g:'), 'esearch', {}), 'out', 'win') 71 | # end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/support/api/esearch/core.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class API::ESearch::Core 4 | attr_reader :editor 5 | 6 | def initialize(editor) 7 | @editor = editor 8 | end 9 | 10 | def search!(search_string, **kwargs) 11 | editor.press! ":call esearch#init(#{VimlValue.dump(kwargs)})#{search_string}" 12 | end 13 | 14 | def input!(search_string, **kwargs) 15 | editor.press! ":call esearch#init(#{VimlValue.dump(kwargs)})#{search_string}" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/support/api/esearch/facade.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/module/delegation' 4 | require 'active_support/hash_with_indifferent_access' 5 | require 'active_support/core_ext/hash/indifferent_access' 6 | 7 | class API::ESearch::Facade 8 | attr_reader :vim_client_getter, :configuration, :editor, :output, :core 9 | 10 | OUTPUTS = { 11 | win: API::ESearch::Window, 12 | qflist: API::ESearch::QuickFix, 13 | stubbed: API::ESearch::StubbedOutput, 14 | }.with_indifferent_access 15 | 16 | delegate :search!, :input!, to: :core 17 | delegate :configure, :configure!, to: :configuration 18 | delegate :cd!, :edit!, :cleanup!, to: :editor 19 | delegate :grep_and_kill_process_by!, 20 | :has_no_process_matching?, 21 | to: :platform 22 | 23 | delegate :has_search_started?, 24 | :has_live_update_search_started?, 25 | :has_no_live_update_search_started?, 26 | :has_search_finished?, 27 | :has_output_message?, 28 | :has_valid_buffer_basename?, 29 | :has_reported_a_single_result?, 30 | :has_search_highlight?, 31 | :has_filename_highlight?, 32 | :has_outputted_result_from_file_in_line?, 33 | :has_not_reported_errors?, 34 | :has_search_freezed?, 35 | :close_search!, 36 | to: :output 37 | 38 | def initialize(editor) 39 | @outputs = {} 40 | @editor = editor 41 | end 42 | 43 | # rubocop:disable Lint/DuplicateMethods 44 | def platform 45 | @platform ||= API::ESearch::Platform.new 46 | end 47 | 48 | def configuration 49 | @configuration ||= API::ESearch::Configuration.new(editor) 50 | end 51 | 52 | def output 53 | @outputs[configuration.output] ||= OUTPUTS.fetch(configuration.output).new(editor) 54 | end 55 | 56 | def core 57 | @core ||= API::ESearch::Core.new(editor) 58 | end 59 | # rubocop:enable Lint/DuplicateMethods 60 | end 61 | -------------------------------------------------------------------------------- /spec/support/api/esearch/platform.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class API::ESearch::Platform 4 | include API::Mixins::BecomeTruthyWithinTimeout 5 | 6 | class_attribute :process_check_timeout, default: Configuration.process_check_timeout 7 | 8 | def grep_and_kill_process_by!(pattern, signal: 'KILL') 9 | pids = `ps -A -o pid,command | grep "#{pattern}" | grep -v grep | awk '{print $1}'`.split("\n") 10 | 11 | # as far as POSIX xargs doesn't have -r option 12 | pids.each do |pid| 13 | `kill -s #{signal} #{pid}` 14 | end 15 | end 16 | 17 | def has_no_process_matching?(command_pattern, timeout: process_check_timeout) 18 | became_truthy_within?(timeout) do 19 | # we are not interesting in `ignore_pattern` as in 20 | # `has_running_processes_matching?` as any process matching 21 | # `command_pattern` (no matter a perent or a child) have to not be runned 22 | # or to be killed during the timeout 23 | processess_matching(command_pattern).blank? 24 | end 25 | end 26 | 27 | # TODO: consider to refactor 28 | def processess_matching(command_pattern, ignore_pattern = nil) 29 | processes = ps_commands.select { |str| str.include?(command_pattern) } 30 | processes = processes.reject { |str| str.include?(ignore_pattern) } if ignore_pattern 31 | 32 | processes 33 | end 34 | 35 | def ps_commands 36 | `ps -A -o pid,etime,command | sed 1d`.split("\n") 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/support/api/esearch/quick_fix.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/numeric/time' 4 | 5 | class API::ESearch::QuickFix 6 | include API::Mixins::BecomeTruthyWithinTimeout 7 | 8 | class_attribute :search_event_timeout, default: Configuration.search_event_timeout 9 | class_attribute :search_freeze_timeout, default: Configuration.search_freeze_timeout 10 | attr_reader :editor 11 | 12 | def initialize(editor) 13 | @editor = editor 14 | end 15 | 16 | def has_search_started?(timeout: search_event_timeout) 17 | became_truthy_within?(timeout) do 18 | editor.press!('lh') # press jk to close "Press ENTER or type command to continue" prompt 19 | inside_quickfix_search_window? 20 | end 21 | end 22 | 23 | def has_search_finished? 24 | raise 'TODO' 25 | end 26 | 27 | def has_reported_a_single_result? 28 | raise 'TODO' 29 | end 30 | 31 | def has_outputted_result_from_file_in_line? 32 | raise 'TODO' 33 | end 34 | 35 | def has_not_reported_errors? 36 | has_reported_errors_in_title? 37 | end 38 | 39 | def has_reported_errors_in_title? 40 | raise 'TODO' 41 | end 42 | 43 | def has_search_freezed?(timeout: search_freeze_timeout) 44 | !became_truthy_within?(timeout) do 45 | editor.with_ignore_cache { has_reported_finish_in_title? } 46 | end 47 | end 48 | 49 | def has_reported_finish_in_title? 50 | editor.quickfix_window_name.include?('Finished') 51 | end 52 | 53 | def close_search! 54 | editor.delete_current_buffer! if inside_quickfix_search_window? 55 | end 56 | 57 | private 58 | 59 | def inside_quickfix_search_window? 60 | quickfix_window_name = editor.quickfix_window_name 61 | filetype = editor.filetype 62 | 63 | quickfix_window_name&.match?(/\A:Search/) && filetype == 'qf' 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/support/api/esearch/stubbed_output.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/numeric/time' 4 | 5 | # TODO rewrite 6 | class API::ESearch::StubbedOutput 7 | include VimlValue::SerializationHelpers 8 | 9 | attr_reader :editor 10 | 11 | def initialize(editor) 12 | @editor = editor 13 | end 14 | 15 | def calls_history 16 | editor.echo func('get', var('g:'), 'esearch#out#stubbed#calls_history', []) 17 | end 18 | 19 | def reset_calls_history! 20 | editor.command!('let g:esearch#out#stubbed#calls_history = []') 21 | end 22 | 23 | def echo_calls_history 24 | editor.echo var('g:esearch#util#echo_calls_history') 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/support/api/esearch/window/entries_parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class API::ESearch::Window::EntriesParser 4 | class MissingEntryError < RuntimeError; end 5 | 6 | class MissingBlankLineError < RuntimeError; end 7 | 8 | FILE_NAME_REGEXP = /\A[^ ]/.freeze 9 | FILE_ENTRY_REGEXP = /\A\s{1,3}[1-9]\d*\s/.freeze 10 | 11 | attr_reader :editor, :lines_enum 12 | 13 | def initialize(editor) 14 | @editor = editor 15 | @lines_enum = editor.lines.with_index(1) 16 | end 17 | 18 | def parse 19 | return enum_for(:parse) unless block_given? 20 | 21 | lines_enum.rewind 22 | lines_enum.next # skip header 23 | 24 | loop do 25 | raise MissingBlankLineError unless lines_enum.peek[0].empty? 26 | 27 | lines_enum.next 28 | 29 | relative_path = next_file_relative_path! 30 | raise MissingEntryError, lines_enum.peek[0] unless line_with_entry? 31 | 32 | next_lines_with_entries! do |line_content, line_in_window| 33 | yield API::ESearch::Window::Entry 34 | .new(editor, 35 | relative_path, 36 | line_content, 37 | line_in_window) 38 | end 39 | end 40 | rescue StopIteration 41 | nil 42 | end 43 | 44 | private 45 | 46 | def line_with_entry? 47 | lines_enum.peek[0].match?(FILE_ENTRY_REGEXP) 48 | end 49 | 50 | def next_file_relative_path! 51 | lines_enum.next[0] 52 | end 53 | 54 | def next_lines_with_entries! 55 | yield lines_enum.next while line_with_entry? 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/support/api/esearch/window/header_parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class API::ESearch::Window::HeaderParser 4 | HEADER_REGEXP = /Matches in (?\d+) lines?, (?\d+) files?/.freeze 5 | 6 | attr_reader :editor 7 | 8 | def initialize(editor) 9 | @editor = editor 10 | end 11 | 12 | def parse 13 | return OpenStruct.new if header_line !~ HEADER_REGEXP 14 | 15 | OpenStruct.new(header_line.match(HEADER_REGEXP).named_captures.transform_values(&:to_i)) 16 | end 17 | 18 | def finished? 19 | header_line.match?(HEADER_REGEXP) && header_line.match?(/\. Finished\.\z/) 20 | end 21 | 22 | def running? 23 | header_line.match?(HEADER_REGEXP) && !header_line.match?(/\. Finished\.\z/) 24 | end 25 | 26 | def errors? 27 | header_line.match?(/\AERRORS from/) 28 | end 29 | 30 | private 31 | 32 | def header_line 33 | editor.lines.first 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/support/api/esearch/window/missing_entry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | API::ESearch::Window::MissingEntry = Struct.new(:relative_path, :line_in_file) do 4 | def empty? 5 | true 6 | end 7 | 8 | def line_content 9 | nil 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/api/esearch/window/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'ostruct' 4 | 5 | class API::ESearch::Window::Parser 6 | attr_reader :editor 7 | 8 | def initialize(editor) 9 | @editor = editor 10 | end 11 | 12 | def header_finished? 13 | header_parser.finished? 14 | end 15 | 16 | def header_running? 17 | header_parser.running? 18 | end 19 | 20 | def header 21 | header_parser.parse 22 | end 23 | 24 | def entries 25 | entries_parser.parse 26 | end 27 | 28 | def header_parser 29 | @header_parser ||= API::ESearch::Window::HeaderParser.new(editor) 30 | end 31 | 32 | def entries_parser 33 | @entries_parser ||= API::ESearch::Window::EntriesParser.new(editor) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/support/api/mixins/become_truthy_within_timeout.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'timeout' 4 | 5 | module API::Mixins::BecomeTruthyWithinTimeout 6 | def became_truthy_within?(timeout) 7 | t0 = Time.now 8 | 9 | loop do 10 | break true if yield 11 | break false if Time.now - t0 > timeout 12 | 13 | sleep 0.1 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/support/api/mixins/rollback_state.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module API::Mixins::RollbackState 4 | class RollbackCursorPositionError < RuntimeError; end 5 | 6 | class RollbackCurrentBufferError < RuntimeError; end 7 | 8 | def rollback_cursor_location(editor, &block) 9 | rollback_cursor_location_inside_buffer(editor) do 10 | rollback_current_buffer(editor, &block) 11 | end 12 | end 13 | 14 | def rollback_current_buffer(editor) 15 | old_buffer_name = editor.current_buffer_name 16 | 17 | yield 18 | ensure 19 | 10.times do 20 | break if old_buffer_name == editor.current_buffer_name 21 | 22 | editor.press! '' 23 | end 24 | 25 | if old_buffer_name != editor.current_buffer_name 26 | raise RollbackCurrentBufferError, 27 | "can't rollback to buffer #{old_buffer_name.inspect} #{editor.current_buffer_name.inspect}" 28 | end 29 | end 30 | 31 | def rollback_cursor_location_inside_buffer(editor) 32 | old_line_number = editor.current_line_number 33 | old_column_number = editor.current_column_number 34 | 35 | yield 36 | ensure 37 | editor.locate_cursor!(old_line_number, old_column_number) 38 | if old_line_number != editor.current_line_number 39 | raise RollbackCursorPositionError, "can't rollback to line #{old_line_number}" 40 | end 41 | if old_column_number != editor.current_column_number 42 | raise RollbackCursorPositionError, "can't rollback to column #{old_column_number}" 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/support/api/mixins/throttling.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module API::Mixins::Throttling 4 | # Simple implementation of throttling for debug purposes with `sleep()` call 5 | # if provided time interval is not waited between calls. As it's a part of 6 | # API, we don't expect concurrent access from multiple threads to a single vim 7 | # instance, so Mutexes aren't required. It also makes no sense to bundle a 8 | # gem so far and introduce another dependency for 10 simple lines of code 9 | def throttle(scope = :global, interval: 1.seconds) 10 | return yield unless interval.positive? 11 | 12 | Thread.current[:throttling_scopes] ||= {} 13 | last_call_at = Thread.current[:throttling_scopes][scope] 14 | 15 | if last_call_at 16 | elapsed_time = Time.now - last_call_at 17 | sleep(interval - elapsed_time) if elapsed_time < interval 18 | end 19 | 20 | result = yield 21 | Thread.current[:throttling_scopes][scope] = Time.now 22 | result 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/support/api/visual_multi.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class API::VisualMulti 4 | include VimlValue::SerializationHelpers 5 | 6 | LEADER = '\\\\' 7 | 8 | attr_reader :editor 9 | 10 | def initialize(editor) 11 | @editor = editor 12 | end 13 | 14 | def regions 15 | editor.echo(var('b:VM_Selection.Regions')) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/support/cache_store.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/cache' 4 | 5 | class CacheStore < ActiveSupport::Cache::MemoryStore 6 | include CleanCaller 7 | 8 | def data 9 | @data.transform_values(&:value) 10 | end 11 | 12 | def clear(options = nil) 13 | instrument(:clear, nil, merged_options(options).merge(object_id: object_id)) do 14 | super 15 | end 16 | end 17 | 18 | def write(name, value, options = nil) 19 | instrument(:write_value, name, merged_options(options).merge(value: value)) do 20 | super 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/clean_caller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/backtrace_cleaner' 4 | 5 | module CleanCaller 6 | BACKTRACE_CLEANER = ActiveSupport::BacktraceCleaner.new.tap do |bc| 7 | bc.add_filter { |line| line.gsub(Configuration.root.to_s, '') } 8 | end 9 | 10 | def clean_caller 11 | BACKTRACE_CLEANER.clean(caller.tap(&:unshift)) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Client < DecoratorBase 4 | def echo(*expressions) 5 | arg = expressions.join(' ').gsub("'", "''") 6 | server.remote_expr("VimrunnerEvaluate('#{arg}')") 7 | end 8 | 9 | def feedkeys(string) 10 | string = string.gsub('"', '\"') 11 | command(%{call feedkeys("#{string}", "i")}) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/decorator_base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'delegate' 4 | 5 | class DecoratorBase < SimpleDelegator 6 | alias __class__ class 7 | def class 8 | __getobj__.class 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/support/fixtures/lazy_directory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'fileutils' 4 | require 'pathname' 5 | require 'digest' 6 | require 'shellwords' 7 | require 'active_support/core_ext/class/attribute' 8 | 9 | class Fixtures::LazyDirectory 10 | class_attribute :fixtures_directory 11 | attr_reader :given_name 12 | attr_accessor :files 13 | 14 | def initialize(files = [], given_name = nil) 15 | @files = files 16 | @given_name = given_name 17 | end 18 | 19 | def persist! 20 | directory_path = path 21 | 22 | FileUtils.mkdir_p(directory_path) 23 | system("git init #{path} >/dev/null 2>&1") unless File.directory?(dot_git) 24 | 25 | files.each do |f| 26 | f.working_directory = directory_path 27 | f.persist! 28 | system("git -C #{Shellwords.escape(path)} add #{Shellwords.escape(f.path)} >/dev/null 2>&1") 29 | system("git -C #{Shellwords.escape(path)} commit -m #{Shellwords.escape(f.path)} >/dev/null 2>&1") 30 | end 31 | 32 | self 33 | end 34 | 35 | def rm_rf 36 | FileUtils.remove_dir(path) 37 | @files.freeze 38 | @given_name.freeze 39 | 40 | self 41 | end 42 | 43 | def to_s 44 | path.to_s 45 | end 46 | 47 | def path 48 | Pathname(fixtures_directory.join(name)).cleanpath 49 | end 50 | 51 | def name 52 | given_name || digest_name 53 | end 54 | 55 | private 56 | 57 | def dot_git 58 | @dot_git ||= path.join('.git') 59 | end 60 | 61 | def digest_name 62 | Digest::MD5.hexdigest(files.map(&:digest_key).sort.to_s) 63 | end 64 | 65 | # We cannot be 100% sure that everything is persisted when `@given_name` is 66 | # specified, so it's required to check the files. Otherwise, `#name` is 67 | # generated based on the directory content so checking if the directory 68 | # exists is enough 69 | # TODO figure out a better name as the current is misleading 70 | def persisted? 71 | !given_name && path.directory? 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/support/fixtures/lazy_file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'fileutils' 4 | require 'pathname' 5 | 6 | class Fixtures::LazyFile 7 | attr_reader :given_relative_path, :raw_content, :kwargs 8 | attr_accessor :working_directory 9 | 10 | def initialize(raw_content, given_relative_path = nil, working_directory = nil, **kwargs) 11 | @given_relative_path = Pathname(given_relative_path).cleanpath.to_s if given_relative_path 12 | @raw_content = raw_content 13 | @working_directory = working_directory 14 | @kwargs = kwargs 15 | end 16 | 17 | def persist! 18 | absolute_path = path 19 | FileUtils.mkdir_p(absolute_path.dirname) unless absolute_path.dirname.directory? 20 | File.open(absolute_path, open_mode) { |f| f.puts(content) } 21 | self 22 | end 23 | 24 | def path 25 | working_directory.join(relative_path) 26 | end 27 | 28 | def relative_path 29 | given_relative_path || digest_name 30 | end 31 | 32 | def basename 33 | File.basename(relative_path) 34 | end 35 | 36 | def to_s 37 | path.to_s 38 | end 39 | 40 | def digest_key 41 | [relative_path, content].map(&:to_s).to_s 42 | end 43 | 44 | def lines 45 | content.split("\n") 46 | end 47 | 48 | def readlines 49 | File.readlines(path) 50 | end 51 | 52 | def unlink 53 | File.unlink(path) 54 | end 55 | 56 | def write_content(new_raw_content) 57 | self.class 58 | .new(new_raw_content, given_relative_path, working_directory, **kwargs) 59 | .persist! 60 | end 61 | 62 | def content 63 | @content ||= 64 | if raw_content.is_a? String 65 | raw_content 66 | elsif raw_content.is_a? Array 67 | raw_content.join("\n") 68 | elsif raw_content.nil? 69 | '' 70 | else 71 | raise ArgumentError 72 | end 73 | end 74 | 75 | def digest_name 76 | Digest::MD5.hexdigest([content, kwargs.to_a.sort].map(&:to_s).to_s) 77 | end 78 | 79 | def open_mode 80 | return 'wb' if kwargs[:binary] 81 | 82 | 'w' 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/support/fixtures/lazy_swap_file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'fileutils' 4 | require 'pathname' 5 | 6 | # TODO: reduce duplication with lazy file 7 | class Fixtures::LazySwapFile 8 | attr_reader :raw_content, :lazy_file 9 | attr_accessor :working_directory 10 | 11 | def initialize(raw_content, lazy_file) 12 | @raw_content = raw_content 13 | @lazy_file = lazy_file 14 | end 15 | 16 | def persist! 17 | absolute_path = path 18 | FileUtils.mkdir_p(absolute_path.dirname) unless absolute_path.dirname.directory? 19 | File.open(absolute_path, open_mode) { |f| f.puts(content) } 20 | self 21 | end 22 | 23 | def path 24 | Pathname([ 25 | lazy_file.path.dirname.to_s, 26 | [lazy_file.path.basename.to_s, '.swp'].join, 27 | ].join('/')) 28 | end 29 | 30 | def relative_path 31 | digest_name 32 | end 33 | 34 | def basename 35 | File.basename(relative_path) 36 | end 37 | 38 | def to_s 39 | path.to_s 40 | end 41 | 42 | def digest_key 43 | [relative_path, content].map(&:to_s).to_s 44 | end 45 | 46 | def lines 47 | content.split("\n") 48 | end 49 | 50 | def readlines 51 | File.readlines(path) 52 | end 53 | 54 | def unlink 55 | File.unlink(path) 56 | end 57 | 58 | def content 59 | @content ||= 60 | if raw_content.is_a? String 61 | raw_content 62 | elsif raw_content.is_a? Array 63 | raw_content.join("\n") 64 | elsif raw_content.nil? 65 | '' 66 | else 67 | raise ArgumentError 68 | end 69 | end 70 | 71 | def digest_name 72 | Digest::MD5.hexdigest(content.to_s) 73 | end 74 | 75 | def open_mode 76 | 'wb' 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /spec/support/helpers/file_system.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers::FileSystem 4 | extend ActiveSupport::Concern 5 | 6 | # Normally we shouldn't do any setup outside `it` blocks, but as far as all the 7 | # files are lazy then it's not an issue 8 | delegate :swap_file, :file, :directory, to: :class 9 | 10 | class_methods do 11 | def swap_file(...) 12 | Fixtures::LazySwapFile.new(...) 13 | end 14 | 15 | def file(...) 16 | Fixtures::LazyFile.new(...) 17 | end 18 | 19 | def directory(...) 20 | Fixtures::LazyDirectory.new(...) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/helpers/modifiable/commandline.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers::Modifiable::Commandline 4 | extend RSpec::Matchers::DSL 5 | 6 | matcher :change_entries_text do |*entries, timeout: 1| 7 | include API::Mixins::BecomeTruthyWithinTimeout 8 | 9 | supports_block_expectations 10 | 11 | match do |block| 12 | @before = reloaded_text(entries) 13 | block.call 14 | 15 | @changed = became_truthy_within?(timeout) do 16 | @after = reloaded_text(entries) 17 | @before != @after 18 | end 19 | return false unless @changed 20 | 21 | if @to 22 | @changed_to_expected = became_truthy_within?(timeout) do 23 | @after = reloaded_text(entries) 24 | values_match?(@to, @after) 25 | end 26 | return false unless @changed_to_expected 27 | end 28 | 29 | true 30 | end 31 | 32 | def reloaded_text(entries) 33 | esearch.output.reloaded_entries!(entries).map(&:result_text) 34 | end 35 | 36 | chain :to do |to| 37 | @to = to 38 | end 39 | 40 | failure_message do 41 | msg = "expected to change #{@before.inspect}" 42 | msg += " to #{@to.inspect}, got #{@after.inspect}" if @to 43 | msg 44 | end 45 | end 46 | 47 | define_negated_matcher :not_to_change_entries_text, :change_entries_text 48 | end 49 | -------------------------------------------------------------------------------- /spec/support/helpers/open.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # For testing opening entries of both window and quickfix output 4 | 5 | module Helpers::Open 6 | extend RSpec::Matchers::DSL 7 | 8 | def tabpage_buffers_list 9 | editor.echo func('tabpagebuflist') 10 | end 11 | 12 | def tabpage_windows_list 13 | (1..editor.echo(func('tabpagewinnr', func('tabpagenr'), '$'))).to_a 14 | end 15 | 16 | def tabpages_list 17 | (1..editor.echo(func('tabpagenr', '$'))).to_a 18 | end 19 | 20 | def start_editing(path) 21 | change { editor.current_buffer_name } 22 | .to(path.to_s) 23 | end 24 | 25 | def windows 26 | editor.echo func('nvim_list_wins') 27 | end 28 | 29 | def open_window(path) 30 | change { editor.current_buffer_name } 31 | .to end_with path.to_s 32 | end 33 | 34 | def open_tab(path) 35 | change { tabpages_list.count } 36 | .by(1) 37 | .and change { editor.current_buffer_name } 38 | .to end_with path 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/support/helpers/output.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers::Output 4 | extend RSpec::Matchers::DSL 5 | 6 | def found_results_in_files(filenames) 7 | have_search_started 8 | .and have_search_finished 9 | .and have_not_reported_errors 10 | .and have_results_in_files(filenames) 11 | end 12 | 13 | matcher :have_outputted_results do |count:| 14 | match do |esearch| 15 | @actual = esearch.output.entries.to_a.count 16 | @actual == count 17 | end 18 | end 19 | 20 | matcher :have_outputted_result_with_right_position_inside_file do |relative_path, line_in_file, _column| 21 | match do 22 | entry = esearch.output.find_entry(relative_path, line_in_file) 23 | if entry.empty? 24 | @missing_entry = true 25 | return false 26 | end 27 | 28 | @line_in_file, @lines = entry.open { [editor.current_line_number, editor.lines.to_a] } 29 | @line_in_file == line_in_file 30 | end 31 | 32 | failure_message do 33 | message = 'expected to have_outputted_result_with_right_position_inside_file' 34 | return "#{message}, got entry #{relative_path} is missing" if @missing_entry 35 | 36 | "#{message}, got actual: #{@line_in_file}, expected: #{line_in_file}, lines: #{@lines}" 37 | end 38 | end 39 | 40 | matcher :have_not_reported_errors do 41 | match(&:has_not_reported_errors?) 42 | 43 | failure_message do |esearch| 44 | ['expected to have_not_reported_errors,', 45 | "got output:\n\t#{esearch.output.errors.to_a.join("\n")}",].join(' ') 46 | end 47 | end 48 | 49 | matcher :have_results_in_files do |files| 50 | match do |esearch| 51 | @expected = files 52 | @actual = esearch.output.entries.map(&:relative_path) 53 | values_match?(@expected.sort, @actual.sort) 54 | end 55 | 56 | failure_message do 57 | "expected to have results in \n#{@expected},\ngot\n#{@actual}" 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/support/helpers/pattern/convert_from_vim.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers::Pattern::ConvertFromVim 4 | extend RSpec::Matchers::DSL 5 | 6 | shared_examples 'avoid conversion when slashes are escaped' do 7 | it { expect(convert.call('\\\\_^')).to eq('\\\\_^') } 8 | it { expect(convert.call('\\\\_$')).to eq('\\\\_$') } 9 | it { expect(convert.call('\\\\>')).to eq('\\\\>') } 10 | it { expect(convert.call('\\\\<')).to eq('\\\\<') } 11 | it { expect(convert.call('\\\\zs')).to eq('\\\\zs') } 12 | it { expect(convert.call('\\\\ze')).to eq('\\\\ze') } 13 | it { expect(convert.call('\\\\%^')).to eq('\\\\%^') } 14 | it { expect(convert.call('\\\\%$')).to eq('\\\\%$') } 15 | end 16 | 17 | shared_examples 'sanitize match modes atoms' do 18 | it { expect(convert.call('\m')).to eq('') } 19 | it { expect(convert.call('\M')).to eq('') } 20 | it { expect(convert.call('\v')).to eq('') } 21 | it { expect(convert.call('\V')).to eq('') } 22 | it { expect(convert.call('\c')).to eq('') } 23 | it { expect(convert.call('\C')).to eq('') } 24 | end 25 | 26 | shared_examples 'sanitize position atoms' do 27 | it { expect(convert.call('\\zs')).to eq('') } 28 | it { expect(convert.call('\\ze')).to eq('') } 29 | it { expect(convert.call('\\%V')).to eq('') } 30 | it { expect(convert.call('\\%#')).to eq('') } 31 | it { expect(convert.call("\\%<'a")).to eq('') } 32 | it { expect(convert.call("\\%'k")).to eq('') } 33 | it { expect(convert.call("\\%>'z")).to eq('') } 34 | it { expect(convert.call('\\%<1l')).to eq('') } 35 | it { expect(convert.call('\\%20l')).to eq('') } 36 | it { expect(convert.call('\\%>300l')).to eq('') } 37 | it { expect(convert.call('\\%<1c')).to eq('') } 38 | it { expect(convert.call('\\%20c')).to eq('') } 39 | it { expect(convert.call('\\%>300c')).to eq('') } 40 | it { expect(convert.call('\\%<1v')).to eq('') } 41 | it { expect(convert.call('\\%20v')).to eq('') } 42 | it { expect(convert.call('\\%>300v')).to eq('') } 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/support/helpers/report_editor_state_on_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers::ReportEditorStateOnError 4 | extend RSpec::Matchers::DSL 5 | 6 | shared_context 'report editor state on error' do 7 | prepend_after do |e| 8 | next if e.exception.nil? 9 | 10 | # TODO: figure out how trigger formatter before Editor#cleanup! to capture 11 | # all the data 12 | DumpEditorStateOnErrorFormatter 13 | .new($stderr) 14 | .example_failed(nil) 15 | end 16 | end 17 | 18 | shared_context 'report editor messages on error' do 19 | prepend_after do |e| 20 | next if e.exception.nil? 21 | 22 | # TODO: figure out how trigger formatter before Editor#cleanup! to capture 23 | # all the data 24 | warn Debug.messages 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/support/helpers/running_processes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers::RunningProcesses 4 | extend RSpec::Matchers::DSL 5 | 6 | matcher :have_running_processes_matching do |command_pattern, ignore_pattern, count: 1, timeout: nil| 7 | include API::Mixins::BecomeTruthyWithinTimeout 8 | 9 | match do |esearch| 10 | timeout ||= esearch.platform.process_check_timeout 11 | 12 | became_truthy_within?(timeout) do 13 | @processes = esearch.platform.processess_matching(command_pattern, ignore_pattern) 14 | @processes.count == count 15 | end 16 | end 17 | 18 | failure_message do |esearch| 19 | process_description = "running processe(s) matching #{command_pattern} (ignoring #{ignore_pattern})" 20 | 21 | got = 22 | if @processes.count == 0 23 | "got #{@processes.count}. Other processes list: \n#{esearch.platform.ps_commands.join("\n")}" 24 | else 25 | "got #{@processes.count}:\n#{@processes.join("\n")}" 26 | end 27 | 28 | "expected to have #{count} #{process_description}, #{got}" 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/support/helpers/shell.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers::Shell 4 | def split(str) 5 | paths, error = editor.echo(func('esearch#shell#split', str)) 6 | return :error if error != 0 7 | 8 | paths.map do |path| 9 | [path['str'], path['begin']..path['end']] 10 | end 11 | end 12 | 13 | def metachars_at(str) 14 | paths, error = editor.echo(func('esearch#shell#split', str)) 15 | return :error if error != 0 16 | 17 | paths.map { |word| word['metachars'] } 18 | end 19 | 20 | def split_and_escape(str) 21 | paths, error = editor.echo(func('esearch#shell#split', str)) 22 | return :error if error != 0 23 | 24 | paths.map do |path| 25 | editor.echo(func('esearch#shell#escape', path)) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/support/helpers/strings.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers::Strings 4 | REGEXP_WRAPPING_SLASHES = %r{\A/|/\w*\z}.freeze 5 | 6 | extend ActiveSupport::Concern 7 | 8 | class_methods do 9 | def dump(arg) 10 | case arg 11 | when String then arg.dump 12 | when Regexp then arg.inspect 13 | else arg.to_s 14 | end 15 | end 16 | end 17 | 18 | def to_search(search_string) 19 | return search_string.inspect.gsub(REGEXP_WRAPPING_SLASHES, '') if search_string.is_a? Regexp 20 | 21 | search_string.to_s 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/helpers/undotree.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers::Undotree 4 | extend RSpec::Matchers::DSL 5 | 6 | def undotree_nodes 7 | collector = lambda do |entries| 8 | entries.map { |e| e['seq'] } + entries.map { |e| e['alt'] }.compact.map(&collector) 9 | end 10 | 11 | collector.call(editor.echo(var('undotree().entries'))).flatten 12 | end 13 | 14 | def esearch_undotree_nodes 15 | editor.echo(var('keys(b:esearch.undotree.nodes)')).map(&:to_i) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/support/helpers/vim.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Helpers::Vim 4 | extend RSpec::Matchers::DSL 5 | include VimlValue::SerializationHelpers 6 | 7 | shared_context 'set options' do |**options| 8 | before do 9 | editor.command! <<~TEXT 10 | let g:save = #{VimlValue.dump(options.keys.zip(options.keys).to_h.transform_values { |v| var("&#{v}") })} 11 | 12 | #{options.map { |k, v| "let &#{k} = #{VimlValue.dump(v)}" }.join("\n")} 13 | TEXT 14 | end 15 | 16 | after do 17 | editor.command! <<~TEXT 18 | #{options.map { |k, _v| "let &#{k} = g:save.#{k}" }.join("\n")} 19 | TEXT 20 | end 21 | end 22 | 23 | # TODO: fix reusability 24 | matcher :change_option do |option, timeout: 1| 25 | include API::Mixins::BecomeTruthyWithinTimeout 26 | 27 | supports_block_expectations 28 | 29 | match do |block| 30 | editor.with_ignore_cache do 31 | @before = editor.echo(var(option)) 32 | block.call 33 | 34 | @changed = became_truthy_within?(timeout) do 35 | @after = editor.echo(var(option)) 36 | @before != @after 37 | end 38 | return false unless @changed 39 | 40 | if @to 41 | @changed_to_expected = became_truthy_within?(timeout) do 42 | @after = editor.echo(var(option)) 43 | values_match?(@to, @after) 44 | end 45 | return false unless @changed_to_expected 46 | end 47 | end 48 | 49 | true 50 | end 51 | 52 | chain :to do |to| 53 | @to = to 54 | end 55 | 56 | failure_message do 57 | msg = "expected to change #{@before.inspect}" 58 | msg += " to #{@to.inspect}, got #{@after.inspect}" if @to 59 | msg 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/support/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/inflector' 4 | 5 | ActiveSupport::Inflector.inflections do |inflect| 6 | inflect.acronym 'API' 7 | inflect.acronym 'DSL' 8 | inflect.acronym 'ESearch' 9 | inflect.acronym 'RSpec' 10 | end 11 | -------------------------------------------------------------------------------- /spec/support/lib/editor/read/base.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Editor::Read::Base 4 | include VimlValue::SerializationHelpers 5 | 6 | VIM_EXCEPTION_REGEXP = /\AVim(\(\w+\))?:E\d+:/.freeze 7 | VIMRUNNER_EXCEPTION_REGEXP = /\AVimrunner(\(\w+\))?/.freeze 8 | 9 | class ReadError < RuntimeError; end 10 | 11 | attr_reader :vim_client_getter, :cache_enabled 12 | 13 | def initialize(vim_client_getter, cache_enabled) 14 | @vim_client_getter = vim_client_getter 15 | @cache_enabled = cache_enabled 16 | @cache = CacheStore.new 17 | end 18 | 19 | def echo 20 | raise NotImplementedError 21 | end 22 | 23 | # TODO 24 | def echo_command(command) 25 | vim.command(command) 26 | end 27 | 28 | def invalidate_cache! 29 | cache.clear 30 | end 31 | 32 | def evaluated?(_value) 33 | true 34 | end 35 | 36 | def with_ignore_cache 37 | @with_ignore_cache ||= [] 38 | @with_ignore_cache << true 39 | yield 40 | ensure 41 | @with_ignore_cache.pop 42 | end 43 | 44 | private 45 | 46 | def reset! 47 | cache.clear 48 | end 49 | 50 | def cache 51 | return null_cache if @with_ignore_cache&.last || !cache_enabled 52 | 53 | @cache 54 | end 55 | 56 | def evaluate(str) 57 | result = vim.echo(str) 58 | if VIM_EXCEPTION_REGEXP.match?(result) || 59 | VIMRUNNER_EXCEPTION_REGEXP.match?(result) 60 | reset! 61 | raise ReadError, result 62 | end 63 | 64 | result 65 | end 66 | 67 | def null_cache 68 | @null_cache ||= ActiveSupport::Cache::NullStore.new 69 | end 70 | 71 | def vim 72 | vim_client_getter.call 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/support/lib/editor/read/batched.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/cache' 4 | 5 | class Editor::Read::Batched < Editor::Read::Base 6 | attr_reader :batch 7 | 8 | def initialize(vim_client_getter, cache_enabled) 9 | super(vim_client_getter, cache_enabled) 10 | @batch = Batch.new(method(:eager!)) 11 | end 12 | 13 | def echo(serializable_argument) 14 | container = Container.new(serializable_argument, batch) 15 | batch.push(container) 16 | container 17 | end 18 | 19 | def evaluated?(container) 20 | !container.__value__.equal?(Editor::Read::Batched::Container::UNDEFINED) 21 | end 22 | 23 | def invalidate_cache! 24 | eager! 25 | super 26 | end 27 | 28 | private 29 | 30 | def reset! 31 | batch.clear 32 | super 33 | end 34 | 35 | def eager! 36 | return false if batch.blank? 37 | 38 | batch 39 | .lookup!(cache) 40 | .evaluate! { |viml_values| VimlValue.load(evaluate(VimlValue.dump(viml_values))) } 41 | .write(cache) 42 | .clear 43 | 44 | true 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/support/lib/editor/read/batched/batch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Editor::Read::Batched::Batch 4 | attr_reader :blank_containers, :loaded_containers 5 | 6 | def initialize(eager_method) 7 | @eager_method = eager_method 8 | @blank_containers = [] 9 | @loaded_containers = [] 10 | end 11 | 12 | def eager! 13 | @eager_method.call 14 | end 15 | 16 | def push(container) 17 | blank_containers << container 18 | self 19 | end 20 | 21 | def clear 22 | blank_containers.clear 23 | loaded_containers.clear 24 | self 25 | end 26 | 27 | def lookup!(identity_map, &block) 28 | retrieved_containers, @blank_containers = 29 | if block.nil? 30 | blank_containers.partition do |container| 31 | next false unless identity_map.exist?(container.__argument__) 32 | 33 | container.__setobj__(identity_map.fetch(container.__argument__)) || true 34 | end 35 | else 36 | block.call(blank_containers) 37 | end 38 | 39 | loaded_containers.concat(retrieved_containers) 40 | 41 | self 42 | end 43 | 44 | def evaluate!(&evaluator) 45 | return self unless blank_containers.present? 46 | 47 | values = blank_containers 48 | .map(&:__argument__) 49 | .yield_self(&evaluator) 50 | blank_containers 51 | .zip(values) 52 | .each { |container, value| container.__setobj__(value) } 53 | 54 | loaded_containers.concat(blank_containers) 55 | blank_containers.clear 56 | 57 | self 58 | end 59 | 60 | def write(identity_map, &block) 61 | if block.nil? 62 | loaded_containers.each do |container| 63 | next if identity_map.exist?(container.__argument__) 64 | 65 | identity_map.write(container.__argument__, container.__getobj__) 66 | end 67 | else 68 | loaded_containers.each(&block) 69 | end 70 | 71 | self 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/support/lib/editor/read/batched/container.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'delegate' 4 | 5 | # rubocop:disable Lint/UnderscorePrefixedVariableName 6 | class Editor::Read::Batched::Container < Delegator 7 | UNDEFINED = ::Object.new.freeze 8 | 9 | attr_reader :__argument__, :__value__ 10 | 11 | def initialize(__argument__, __batch__) 12 | super(UNDEFINED) 13 | @__argument__ = __argument__ 14 | @__batch__ = __batch__ 15 | end 16 | 17 | def __setobj__(obj) 18 | @__value__ = obj 19 | end 20 | 21 | def __getobj__ 22 | __eager__! if @__value__.equal?(UNDEFINED) 23 | @__value__ 24 | end 25 | 26 | alias __class__ class 27 | def class 28 | __getobj__.class 29 | end 30 | 31 | private 32 | 33 | def __eager__! 34 | @__batch__.eager! 35 | remove_instance_variable(:@__batch__) 36 | end 37 | end 38 | # rubocop:enable Lint/UnderscorePrefixedVariableName 39 | -------------------------------------------------------------------------------- /spec/support/lib/editor/read/eager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/cache' 4 | 5 | class Editor::Read::Eager < Editor::Read::Base 6 | def echo(serializable_argument) 7 | cache.fetch(serializable_argument) do 8 | # NOTE: execution is wrapped in [] to prevent ambiguity in VimlValue#load 9 | VimlValue.load(evaluate(VimlValue.dump([serializable_argument])))[0] 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module VimlValue 4 | class ParseError < RuntimeError; end 5 | 6 | def self.load(string, allow_toplevel_literals: false) 7 | tree = Parser 8 | .new(Lexer.new(string), allow_toplevel_literals: allow_toplevel_literals) 9 | .parse 10 | 11 | return tree if tree.nil? 12 | 13 | Visitors::ToRuby.new.accept(tree) 14 | end 15 | 16 | def self.dump(object, visitor: Visitors::ToVim) 17 | visitor.new.accept(object) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/.gitignore: -------------------------------------------------------------------------------- 1 | /lexer.rb 2 | /parser.rb 3 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/ast.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Defined to be compliant with ast gem, but without introducing it as a 4 | # dependency as we don't use most of it's features 5 | class VimlValue::AST 6 | Node = Struct.new(:type, :children) do 7 | def inspect 8 | "(#{type} #{children.map(&:inspect).join(' ')})" 9 | end 10 | end 11 | 12 | module Sexp 13 | def s(type, *children) 14 | Node.new(type, children) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/serializable/expression.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Base class for serializable values 4 | class VimlValue::Serializable::Expression 5 | attr_reader :property_access 6 | 7 | def [](property_access) 8 | @property_access = property_access 9 | self 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/serializable/function_call.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VimlValue::Serializable::FunctionCall < VimlValue::Serializable::Expression 4 | attr_reader :name, :arguments 5 | 6 | def initialize(name, *arguments) 7 | @name = name 8 | @arguments = arguments 9 | end 10 | 11 | def to_s 12 | "(#{name} #{arguments.map(&:inspect).join(' ')})" 13 | end 14 | 15 | def inspect 16 | "" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/serializable/identifier.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VimlValue::Serializable::Identifier < VimlValue::Serializable::Expression 4 | attr_reader :string_representation 5 | alias to_s string_representation 6 | 7 | def initialize(string_representation) 8 | @string_representation = string_representation 9 | end 10 | 11 | def inspect 12 | "" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/serialization_helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module VimlValue::SerializationHelpers 4 | def var(string_representation) 5 | VimlValue::Serializable::Identifier.new(string_representation) 6 | end 7 | 8 | def func(name, *arguments) 9 | VimlValue::Serializable::FunctionCall.new(name, *arguments) 10 | end 11 | 12 | def funcref(*args) 13 | VimlValue::Types::Funcref.new(*args) 14 | end 15 | 16 | def none 17 | VimlValue::Types::None.new 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/tree_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VimlValue::TreeBuilder 4 | def numeric(tstring) 5 | node(:numeric, [token_value(tstring)]) 6 | end 7 | 8 | def boolean(tstring) 9 | node(:boolean, [token_value(tstring)]) 10 | end 11 | 12 | def none 13 | node(:none) 14 | end 15 | 16 | def null(tnull) 17 | node(:null, [token_value(tnull)]) 18 | end 19 | 20 | def funcref(tstring, *curried_args) 21 | node(:funcref, [tstring, *curried_args]) 22 | end 23 | 24 | def string(tstring) 25 | node(:string, [token_value(tstring)]) 26 | end 27 | 28 | def dict(pairs) 29 | node(:dict, pairs) 30 | end 31 | 32 | def list(values) 33 | node(:list, values) 34 | end 35 | 36 | def pair(key, token_value) 37 | node(:pair, [key, token_value]) 38 | end 39 | 40 | def dict_recursive_ref 41 | node(:dict_recursive_ref) 42 | end 43 | 44 | def list_recursive_ref 45 | node(:list_recursive_ref) 46 | end 47 | 48 | private 49 | 50 | def token_value(token) 51 | token.value 52 | end 53 | 54 | def node(type, children = []) 55 | VimlValue::AST::Node.new(type, children.freeze) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/types/dict_recursive_ref.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VimlValue::Types::DictRecursiveRef 4 | def inspect 5 | '{...}' 6 | end 7 | 8 | def ==(other) 9 | self.class == other.class 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/types/funcref.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | VimlValue::Types::Funcref = Struct.new(:name, :args) do 4 | def initialize(name, *args) 5 | super(name, args) 6 | end 7 | 8 | def inspect 9 | "function(#{[name.inspect, *args.map(&:inspect)].join(', ')})" 10 | end 11 | 12 | def pretty_print(pretty_print) 13 | pretty_print.text(inspect) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/types/list_recursive_ref.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VimlValue::Types::ListRecursiveRef 4 | def inspect 5 | '[...]' 6 | end 7 | 8 | def ==(other) 9 | self.class == other.class 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/types/none.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VimlValue::Types::None 4 | def inspect 5 | 'v:none' 6 | end 7 | 8 | def ==(other) 9 | self.class == other.class 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/visitors/to_ruby.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VimlValue::Visitors::ToRuby 4 | def accept(tree) 5 | visit(tree) 6 | end 7 | 8 | private 9 | 10 | def visit(node) 11 | send(:"visit_#{node.type}", node) 12 | end 13 | 14 | def visit_dict(node) 15 | node 16 | .children 17 | .map { |pair| visit_pair(pair) } 18 | .to_h 19 | end 20 | 21 | def visit_list(node) 22 | node 23 | .children 24 | .map { |value| visit(value) } 25 | end 26 | 27 | def visit_pair(pair) 28 | [visit_string(pair.children.first), visit(pair.children.last)] 29 | end 30 | 31 | def visit_none(_node) 32 | VimlValue::Types::None.new 33 | end 34 | 35 | def visit_funcref(node) 36 | VimlValue::Types::Funcref.new( 37 | visit_string(node.children.first), 38 | *node.children[1..].map { |n| visit(n) } 39 | ) 40 | end 41 | 42 | def visit_dict_recursive_ref(_node) 43 | VimlValue::Types::DictRecursiveRef.new 44 | end 45 | 46 | def visit_list_recursive_ref(_node) 47 | VimlValue::Types::ListRecursiveRef.new 48 | end 49 | 50 | def visit_literal(node) 51 | node.children.first 52 | end 53 | alias visit_boolean visit_literal 54 | alias visit_null visit_literal 55 | alias visit_string visit_literal 56 | alias visit_numeric visit_literal 57 | end 58 | -------------------------------------------------------------------------------- /spec/support/lib/viml_value/visitors/to_vim7.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class VimlValue::Visitors::ToVim7 < VimlValue::Visitors::ToVim 4 | def visit_nil(_object) 5 | "''" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/platform_check.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rbconfig' 4 | 5 | module PlatformCheck 6 | def platform_name 7 | @platform_name ||= 8 | if linux? 9 | :linux 10 | elsif osx? 11 | :osx 12 | else 13 | raise 'Unknown platform' 14 | end 15 | end 16 | 17 | def osx? 18 | @osx = RbConfig::CONFIG['host_os'].match?(/darwin/) if @osx.nil? 19 | @osx 20 | end 21 | 22 | def linux? 23 | @linux = RbConfig::CONFIG['host_os'].match?(/linux/) if @linux.nil? 24 | @linux 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/support/profiling/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | /data 3 | -------------------------------------------------------------------------------- /spec/support/profiling/generate_html_files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -r data 4 | mkdir data 5 | 6 | for n in {1..1000}; do 7 | name="data/file$( printf %03d "$n" ).html" 8 | 9 | { printf "
"; printf "%d" "$RANDOM"; printf "
"; } > "$name" 10 | done 11 | -------------------------------------------------------------------------------- /spec/support/profiling/profile.vim: -------------------------------------------------------------------------------- 1 | profile start profile.log 2 | profile file * 3 | profile func * 4 | 5 | if has('nvim') 6 | command KK profile stop | edit profile.log 7 | else 8 | command KK qall 9 | endif 10 | 11 | call esearch#init() 12 | -------------------------------------------------------------------------------- /spec/support/scripts/search_in_infinite_random_stdin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | util=$1 4 | shift 5 | cat /dev/urandom | $util "$@" 6 | -------------------------------------------------------------------------------- /spec/support/scripts/sort_search_results.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | "$@" | sort -t : -k 1,1 -k 2n,2n 4 | -------------------------------------------------------------------------------- /spec/support/setup/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.7.0-buster 2 | 3 | COPY Gemfile* spec/support/setup/ / 4 | RUN set -eux; \ 5 | apt-get update; apt-get install -y ansible; \ 6 | ansible-playbook site.yml; \ 7 | apt-get install -y xvfb; \ 8 | rm -rf /var/lib/apt/lists/*; apt-get clean -y; 9 | 10 | WORKDIR /app 11 | ENV DISPLAY=:99 12 | ENV LANG=C.UTF-8 13 | 14 | ENTRYPOINT ["/entrypoint.sh"] 15 | CMD ["bash"] 16 | -------------------------------------------------------------------------------- /spec/support/setup/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | Xvfb :99 & 4 | [ "$RUN_VNC" = '1' ] && x11vnc & 5 | 6 | exec "$@" 7 | -------------------------------------------------------------------------------- /spec/support/setup/vars/darwin.yml: -------------------------------------------------------------------------------- 1 | vim_package_name: macvim 2 | tar_package_name: gnu-tar 3 | pip_package_name: python3 4 | ack_package_name: ack 5 | ag_package_name: the_silver_searcher 6 | pt_download_url: https://github.com/monochromegane/the_platinum_searcher/releases/download/v2.2.0/pt_darwin_amd64.zip 7 | rg_download_url: https://github.com/BurntSushi/ripgrep/releases/download/12.1.1/ripgrep-12.1.1-x86_64-apple-darwin.tar.gz 8 | ps_package_name: 9 | package_become: no 10 | -------------------------------------------------------------------------------- /spec/support/setup/vars/debian.yml: -------------------------------------------------------------------------------- 1 | vim_package_name: vim-gtk3 2 | tar_package_name: tar 3 | pip_package_name: python3-pip 4 | ack_package_name: ack-grep 5 | ag_package_name: silversearcher-ag 6 | pt_download_url: https://github.com/monochromegane/the_platinum_searcher/releases/download/v2.2.0/pt_linux_amd64.tar.gz 7 | rg_download_url: https://github.com/BurntSushi/ripgrep/releases/download/12.1.1/ripgrep-12.1.1-x86_64-unknown-linux-musl.tar.gz 8 | ps_package_name: procps 9 | package_become: yes 10 | -------------------------------------------------------------------------------- /spec/support/subscriptions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/notifications' 4 | 5 | LOG_TAG_PADDING = 15 6 | 7 | ActiveSupport::Notifications.subscribe(/cache_read/) do |_name, _start, _finish, _id, payload| 8 | Configuration.log.tagged('cache.read'.rjust(LOG_TAG_PADDING)) do 9 | Configuration.log.debug { (payload[:key]).to_s } 10 | end 11 | end 12 | 13 | ActiveSupport::Notifications.subscribe(/cache_clear/) do |_name, _start, _finish, _id, payload| 14 | Configuration.log.tagged('cache.clear'.rjust(LOG_TAG_PADDING)) do 15 | Configuration.log.debug { "CLEAR #{payload[:object_id]}" } 16 | end 17 | end 18 | 19 | ActiveSupport::Notifications.subscribe(/cache_write_value/) do |_name, _start, _finish, _id, payload| 20 | Configuration.log.tagged('cache.write'.rjust(LOG_TAG_PADDING)) do 21 | Configuration.log.debug do 22 | echos = 23 | if Configuration.debug_specs_performance? 24 | VimrunnerSpy.echo_call_history.count 25 | else 26 | 'n/a' 27 | end 28 | 29 | "#{payload[:key]} := #{payload[:value]} (echos: #{echos})" 30 | end 31 | end 32 | end 33 | 34 | ActiveSupport::Notifications.subscribe(/\Aeditor\./) do |name, _start, _finish, _id, payload| 35 | Configuration.log.tagged(name.rjust(LOG_TAG_PADDING)) do 36 | Configuration.log.debug { (payload[:data]).to_s } 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/support/viml/autoload/esearch/out/stubbed.vim: -------------------------------------------------------------------------------- 1 | fu! esearch#out#stubbed#init(out_params) abort 2 | if !exists('g:esearch#out#stubbed#calls_history') 3 | let g:esearch#out#stubbed#calls_history = [] 4 | endif 5 | call add(g:esearch#out#stubbed#calls_history, a:out_params) 6 | endfu 7 | -------------------------------------------------------------------------------- /spec/support/viml/vader.vim: -------------------------------------------------------------------------------- 1 | set runtimepath+=. 2 | set runtimepath+=~/.cache/esearch-dev/plugins/vader.vim/ 3 | set runtimepath+=~/.cache/esearch-dev/plugins/vim-visual-multi/ 4 | set runtimepath+=~/.cache/esearch-dev/plugins/vim-fugitive/ 5 | set runtimepath+=~/.cache/esearch-dev/plugins/nerdtree/ 6 | set runtimepath+=~/.cache/esearch-dev/plugins/vim-dirvish/ 7 | set runtimepath+=~/.cache/esearch-dev/plugins/vim-netranger/ 8 | set runtimepath+=~/.cache/esearch-dev/plugins/fern.vim/ 9 | set runtimepath+=~/.cache/esearch-dev/plugins/defx.nvim/ 10 | if has('nvim') 11 | " TODO 12 | " call remote#host#RegisterPlugin('python3', $HOME.'/.cache/esearch-dev/plugins/defx.nvim/rplugin/python3/defx', [ 13 | " \ {'sync': v:true, 'name': '_defx_init', 'type': 'function', 'opts': {}}, 14 | " \ ]) 15 | else 16 | set runtimepath+=~/.cache/esearch-dev/plugins/nvim-yarp/ 17 | set runtimepath+=~/.cache/esearch-dev/plugins/vim-hug-neovim-rpc/ 18 | endif 19 | 20 | filetype plugin indent on 21 | syntax enable 22 | -------------------------------------------------------------------------------- /spec/support/vimrunner_spy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'delegate' 4 | 5 | class VimrunnerSpy < DecoratorBase 6 | include CleanCaller 7 | 8 | def self.echo_call_history 9 | @echo_call_history ||= [] 10 | end 11 | 12 | def self.reset! 13 | @echo_call_history = [] 14 | end 15 | 16 | def echo(arg) 17 | result = super(arg) 18 | __class__.echo_call_history << [arg, result, clean_caller] 19 | result 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/unit/buf_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'esearch#buf' do 6 | include Helpers::FileSystem 7 | include VimlValue::SerializationHelpers 8 | 9 | describe 'esearch#buf#find' do 10 | context 'when filename contains special characters' do 11 | let(:filename) { 'asd' } 12 | let(:filename) { "%#$^\\$\\^{}\\{\\}#{'\\' * 3}{1,2\\},\\n{}[foo][bar[]]\\[\\]?\\?*\\*.\\. \\ %#\\%\\#" } 13 | 14 | let(:files) { [file('', filename.gsub('\\', '\\\\\\\\'))] } 15 | let!(:test_directory) { directory(files).persist! } 16 | 17 | before { editor.cd! test_directory } 18 | 19 | context 'when listed' do 20 | before do 21 | editor.command! "edit `=#{VimlValue.dump(filename)}`" 22 | end 23 | 24 | it { expect(editor.echo(func('esearch#buf#find', filename))).to be > 0 } 25 | end 26 | 27 | context 'when hidden' do 28 | before do 29 | editor.command! "edit `=#{VimlValue.dump(filename)}`" 30 | editor.command! 'enew' 31 | end 32 | 33 | it { expect(editor.echo(func('esearch#buf#find', filename))).to be > 0 } 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/unit/pattern/vim2literal_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'esearch#pattern' do 6 | include Helpers::FileSystem 7 | include VimlValue::SerializationHelpers 8 | include Helpers::Pattern::ConvertFromVim 9 | 10 | describe 'esearch#pattern#vim2literal#convert' do 11 | context 'when filename contains special characters' do 12 | subject(:convert) do 13 | lambda do |input| 14 | editor.echo(func('esearch#pattern#vim2literal#convert', input)) 15 | end 16 | end 17 | 18 | include_examples 'avoid conversion when slashes are escaped' 19 | include_examples 'sanitize match modes atoms' 20 | include_examples 'sanitize position atoms' 21 | 22 | it { expect(convert.call('\\_^')).to eq('') } 23 | it { expect(convert.call('\\_$')).to eq('') } 24 | it { expect(convert.call('\\>')).to eq('') } 25 | it { expect(convert.call('\\<')).to eq('') } 26 | it { expect(convert.call('\\%^')).to eq('') } 27 | it { expect(convert.call('\\%$')).to eq('') } 28 | 29 | it { expect(convert.call('\\^')).to eq('\\^') } 30 | it { expect(convert.call('\\$')).to eq('\\$') } 31 | it { expect(convert.call('\\/')).to eq('/') } 32 | 33 | context 'when preceding slashes are escaped except one' do 34 | it { expect(convert.call('\\\\\\_^')).to eq('\\\\') } 35 | it { expect(convert.call('\\\\\\_$')).to eq('\\\\') } 36 | it { expect(convert.call('\\\\\\>')).to eq('\\\\') } 37 | it { expect(convert.call('\\\\\\<')).to eq('\\\\') } 38 | it { expect(convert.call('\\\\\\%^')).to eq('\\\\') } 39 | it { expect(convert.call('\\\\\\%$')).to eq('\\\\') } 40 | it { expect(convert.call('\\\\\\/')).to eq('\\\\/') } 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/unit/pattern/vim2pcre_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'esearch#pattern' do 6 | include Helpers::FileSystem 7 | include VimlValue::SerializationHelpers 8 | include Helpers::Pattern::ConvertFromVim 9 | 10 | describe 'esearch#pattern#vim2pcre#convert' do 11 | context 'when filename contains special characters' do 12 | subject(:convert) do 13 | lambda do |input| 14 | editor.echo(func('esearch#pattern#vim2pcre#convert', input)) 15 | end 16 | end 17 | 18 | it { expect(convert.call('\\_^')).to eq('^') } 19 | it { expect(convert.call('\\_$')).to eq('$') } 20 | it { expect(convert.call('\\>')).to eq('\b') } 21 | it { expect(convert.call('\\<')).to eq('\b') } 22 | it { expect(convert.call('\\%^')).to eq('^') } 23 | it { expect(convert.call('\\%$')).to eq('$') } 24 | 25 | it { expect(convert.call('\\^')).to eq('\\^') } 26 | it { expect(convert.call('\\$')).to eq('\\$') } 27 | 28 | context 'when preceding slashes are escaped except one' do 29 | it { expect(convert.call('\\\\\\_^')).to eq('\\\\^') } 30 | it { expect(convert.call('\\\\\\_$')).to eq('\\\\$') } 31 | it { expect(convert.call('\\\\\\>')).to eq('\\\\\b') } 32 | it { expect(convert.call('\\\\\\<')).to eq('\\\\\b') } 33 | it { expect(convert.call('\\\\\\%^')).to eq('\\\\^') } 34 | it { expect(convert.call('\\\\\\%$')).to eq('\\\\$') } 35 | end 36 | 37 | include_examples 'avoid conversion when slashes are escaped' 38 | include_examples 'sanitize match modes atoms' 39 | include_examples 'sanitize position atoms' 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/unit/util/clip_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'esearch#util' do 6 | include VimlValue::SerializationHelpers 7 | 8 | describe '#clip' do 9 | subject(:clip) do 10 | lambda do |value, from, to| 11 | editor.echo func('esearch#util#clip', value, from, to) 12 | end 13 | end 14 | 15 | it { expect(clip.call(0, 1, 4)).to eq(1) } 16 | it { expect(clip.call(1, 1, 4)).to eq(1) } 17 | it { expect(clip.call(2, 1, 4)).to eq(2) } 18 | it { expect(clip.call(3, 1, 4)).to eq(3) } 19 | it { expect(clip.call(4, 1, 4)).to eq(4) } 20 | it { expect(clip.call(5, 1, 4)).to eq(4) } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/unit/util/has_upper_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'esearch#util' do 6 | include VimlValue::SerializationHelpers 7 | 8 | describe '#has_upper' do 9 | subject(:has_upper) do 10 | lambda do |text| 11 | editor.echo! func('esearch#util#has_upper', text) 12 | end 13 | end 14 | 15 | shared_examples 'it works with any encoding' do 16 | describe 'detection' do 17 | context 'when ascii' do 18 | it { expect(has_upper.call('aab')).to eq(0) } 19 | it { expect(has_upper.call('aAb')).to eq(1) } 20 | end 21 | 22 | context 'when unicode' do 23 | it { expect(has_upper.call('aσb')).to eq(0) } 24 | it { expect(has_upper.call('aΣb')).to eq(1) } 25 | end 26 | end 27 | 28 | describe 'options restoring' do 29 | it do 30 | expect { has_upper.call('a') } 31 | .not_to change { editor.echo(var('&ignorecase')) } 32 | end 33 | end 34 | end 35 | 36 | context 'when ignorecase' do 37 | before { editor.command! 'set ignorecase' } 38 | 39 | it_behaves_like 'it works with any encoding' 40 | end 41 | 42 | context 'when noignorecase' do 43 | before { editor.command! 'set noignorecase' } 44 | 45 | it_behaves_like 'it works with any encoding' 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/xargs.vader: -------------------------------------------------------------------------------- 1 | Include: helper.vader 2 | 3 | Before: 4 | Save g:esearch 5 | let g:esearch.cwd = 'spec/fixtures/xargs'.g:test_number.next().'/' 6 | let g:esearch.backend = 'system' 7 | After: 8 | Restore g:esearch 9 | 10 | " TODO can be modified to work with submodules as well. --recurse-submodules 11 | " won't work as it's impossible to specify rec:filename for submodules, so 12 | " 'git submodule foreach ...' must be used. 13 | Execute(#git_log with ignoring subprojects): 14 | call delete(g:esearch.cwd) 15 | let g:file1 = Fixture(g:esearch.cwd.'file1.txt', ['ab', 'ac']) 16 | let g:file2 = Fixture(g:esearch.cwd.'nested/file2.txt', ['ad', 'ae']) 17 | call system('git init ' . g:esearch.cwd . 'nested') 18 | call system('git -C ' . g:esearch.cwd . 'nested add -A') 19 | call system('git -C ' . g:esearch.cwd . 'nested commit -m "subproject message"') 20 | call system('git init ' . g:esearch.cwd) 21 | call system('git -C ' . g:esearch.cwd . ' submodule add ./nested') 22 | call system('git -C ' . g:esearch.cwd . ' add -A') 23 | call system('git -C ' . g:esearch.cwd . ' commit -m "parent message"') 24 | call esearch#init({'paths': esearch#xargs#git_log(), 'pattern': 'a'}) 25 | 26 | Assert join(getline(1, '$'), "\n") =~# join([ 27 | \ 'Matches in 3 lines, 2 files. Finished.', 28 | \ '', 29 | \ '\x\{40}:.gitmodules', 30 | \ ' 2 path = nested', 31 | \ '', 32 | \ '\x\{40}:file1.txt', 33 | \ ' 1 ab', 34 | \ ' 2 ac', 35 | \], "\n") 36 | 37 | exe "norm G\" 38 | AssertEqual getline(1, '$'), ['ab', 'ac'] 39 | -------------------------------------------------------------------------------- /syntax/es_ctx_css.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | syn keyword es_cssTagName abbr address area a b base bdo blockquote body br button caption cite code col colgroup dd del dfn div dl dt em fieldset form h1 h2 h3 h4 h5 h6 head hr html img i iframe input ins isindex kbd label legend li link map menu meta noscript ol optgroup option p param pre q s samp script small span strong sub sup tbody td textarea tfoot th thead title tr ul u var object svg article aside audio bdi canvas command data datalist details dialog embed figcaption figure footer header hgroup keygen main mark menuitem meter nav output progress rt rp ruby section source summary time track video wbr 6 | syn match es_cssTagName /\\|\\|\/ 7 | syn match es_cssTagName "\*" 8 | syn match es_cssProp /[^-[:alnum:]][-[:alnum:]]\+\ze\s*:/ 9 | syn region es_cssAttributeSelector start="\[" end="]" 10 | syn region es_cssString start=+"+ skip=+\\\\\|\\"+ end=+"\|^+ 11 | syn region es_cssString start=+'+ skip=+\\\\\|\\'+ end=+'\|^+ 12 | " According to syntax/css.vim id cannot start with -, but this matches are 13 | " merged for performance reasons 14 | syn match es_cssClassOrId "[.#]-\=[A-Za-z_@][A-Za-z0-9_@-]*" 15 | syn match es_sassVariable "$[[:alnum:]_-]\+" 16 | " @include is highlighted as Include in the original syntax. Also a collision 17 | " with vars in less 18 | syn match es_sassPreProc /@\l\+\>/ 19 | 20 | hi def link es_cssTagName Statement 21 | hi def link es_cssProp StorageClass 22 | hi def link es_cssAttributeSelector String 23 | hi def link es_cssString String 24 | hi def link es_cssClassOrId Function 25 | hi def link es_sassVariable Identifier 26 | hi def link es_sassPreProc PreProc 27 | 28 | let b:current_syntax = 'es_ctx_css' 29 | -------------------------------------------------------------------------------- /syntax/es_ctx_dockerfile.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn match es_dockerfileComment "#.*" display 8 | syn region es_dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"|^/ display 9 | " To prevent matching with a commonly used docker-entrypoint.sh only uppercase 10 | " are allowed 11 | syntax keyword es_dockerfileKeyword ADD ARG CMD COPY ENTRYPOINT ENV EXPOSE HEALTHCHECK LABEL MAINTAINER ONBUILD RUN SHELL STOPSIGNAL USER VOLUME WORKDIR AS FROM 12 | 13 | hi def link es_dockerfileComment Comment 14 | hi def link es_dockerfileString String 15 | hi def link es_dockerfileKeyword Keyword 16 | 17 | let b:current_syntax = 'es_ctx_dockerfile' 18 | -------------------------------------------------------------------------------- /syntax/es_ctx_generic.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn keyword es_genericConstant null nil none NULL NIL NONE Null Nil None 8 | syn keyword es_genericBoolean true false TRUE FALSE True False 9 | syn keyword es_genericConditional if unless else elseif case switch select where when default 10 | syn keyword es_genericException throw raise try catch rescue finally ensure 11 | syn keyword es_genericRepeat while until for foreach do 12 | syn keyword es_genericScopeDecl public protected private abstract global shared 13 | syn keyword es_genericInclude include import use require package native 14 | syn keyword es_genericOperator new delete as in 15 | syn keyword es_genericStatement break next continue return goto begin end 16 | syn keyword es_genericKeyword var let this self super yield implement[s] extend[s] 17 | syn keyword es_genericStorageClass const mutable static register volatile 18 | syn keyword es_genericStructure struct class module export union enum interface typedef 19 | 20 | syn keyword es_genericKeyword func function fn def nextgroup=es_genericFunction skipwhite 21 | syn match es_genericFunction "\h\w*" contained 22 | 23 | syn match es_genericComment "//.*" 24 | syn match es_genericComment "#.*" 25 | syn region es_genericComment start="/\*" end="\*/\|$" 26 | syn region es_genericString start=/"/ skip=/\\\\\|\\"/ end=/"\|^/ 27 | syn region es_genericString start=/'/ skip=/\\\\\|\\'/ end=/'\|^/ 28 | 29 | hi def link es_genericConstant Constant 30 | hi def link es_genericBoolean Boolean 31 | hi def link es_genericConditional Conditional 32 | hi def link es_genericException Exception 33 | hi def link es_genericRepeat Repeat 34 | hi def link es_genericScopeDecl StorageClass 35 | hi def link es_genericInclude Include 36 | hi def link es_genericOperator Operator 37 | hi def link es_genericStatement Statement 38 | hi def link es_genericKeyword Keyword 39 | hi def link es_genericStorageClass StorageClass 40 | hi def link es_genericStructure Structure 41 | hi def link es_genericComment Comment 42 | hi def link es_genericString String 43 | hi def link es_genericFunction Function 44 | 45 | let b:current_syntax = 'es_ctx_generic' 46 | -------------------------------------------------------------------------------- /syntax/es_ctx_go.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn keyword es_goDirective package import 8 | syn keyword es_goDeclaration var const type func 9 | syn keyword es_goDeclType struct interface 10 | syn keyword es_goStatement defer go goto return break continue fallthrough 11 | syn keyword es_goConditional if else switch select 12 | syn keyword es_goLabel case default 13 | syn keyword es_goRepeat for range 14 | syn keyword es_goBuiltins append cap close complex copy delete imag len make new panic print println real recover 15 | syn keyword es_goConstants iota nil 16 | syn keyword es_goBool true false 17 | syn keyword es_goType chan map bool string error int int8 int16 int32 int64 rune byte uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex64 complex128 18 | 19 | syn region es_goString start=+"+ skip=+\\\\\|\\"+ end=+"\|^+ 20 | syn region es_goRawString start=+`+ end=+`\|$+ 21 | syn region es_goComment start="/\*" end="\*/\|^" 22 | syn region es_goComment start="//" end="$" 23 | 24 | 25 | hi def link es_goDirective Statement 26 | hi def link es_goDeclaration Keyword 27 | hi def link es_goDeclType Keyword 28 | hi def link es_goConstants Keyword 29 | hi def link es_goBool Boolean 30 | hi def link es_goType Type 31 | hi def link es_goBuiltins Keyword 32 | hi def link es_goStatement Statement 33 | hi def link es_goConditional Conditional 34 | hi def link es_goLabel Label 35 | hi def link es_goRepeat Repeat 36 | hi def link es_goString String 37 | hi def link es_goRawString String 38 | hi def link es_goComment Comment 39 | 40 | let b:current_syntax = 'es_ctx_go' 41 | -------------------------------------------------------------------------------- /syntax/es_ctx_haskell.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | syn match es_hsModule "module" 7 | syn match es_hsImport "import.*"he=s+6 contains=es_hsImportMod,es_hsLineComment,es_hsBlockComment 8 | syn region es_hsString start=+"+ skip=+\\\\\|\\"+ end=+"\|^+ 9 | syn keyword es_hsImportMod as qualified hiding contained 10 | syn keyword es_hsInfix infix infixl infixr 11 | syn keyword es_hsStructure class data deriving instance default where 12 | syn keyword es_hsTypedef type newtype 13 | syn keyword es_hsStatement do case of let in 14 | syn keyword es_hsConditional if then else 15 | syn keyword es_hsDebug undefined error trace 16 | 17 | syn match es_hsLineComment "---*\([^-!#$%&\*\+./<=>\?@\\^|~].*\)\?$" 18 | syn region es_hsBlockComment start="{-" end="-}\|^" 19 | syn region es_hsPragma start="{-#" end="#-}\|^" 20 | syn match es_hsCharacter "[^a-zA-Z0-9_']'\([^\\]\|\\[^']\+\|\\'\)'"lc=1 21 | 22 | hi def link es_hsModule Structure 23 | hi def link es_hsImport Include 24 | hi def link es_hsString String 25 | hi def link es_hsImportMod Include 26 | hi def link es_hsInfix PreProc 27 | hi def link es_hsStructure Structure 28 | hi def link es_hsTypedef Typedef 29 | hi def link es_hsStatement Statement 30 | hi def link es_hsConditional Conditional 31 | hi def link es_hsLineComment Comment 32 | hi def link es_hsBlockComment Comment 33 | hi def link es_hsPragma SpecialComment 34 | hi def link es_hsCharacter Character 35 | hi def link es_hsDebug Debug 36 | 37 | let b:current_syntax = 'es_ctx_haskell' 38 | -------------------------------------------------------------------------------- /syntax/es_ctx_hcl.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " partially based and made coherent with vim-syntax-terraform 6 | 7 | syn match es_hclSection /\w\+\ze\s\+[{"]/ 8 | syn match es_hclBraces /[\[\]]/ 9 | syn region es_hclValueString start=/"/ skip=/\\\+"/ end=/"\|^/ contains=es_hclStringInterp 10 | syn region es_hclStringInterp start=/\${/ end=/}/ contained 11 | syn region es_hclComment start="/\*" end="\*/\|^" 12 | syn match es_hclComment "#.*" 13 | syn match es_hclComment "//.*" 14 | 15 | syn keyword es_hclContent content 16 | syn keyword es_hclRepeat for in 17 | syn keyword es_hclConditional if 18 | syn keyword es_hclPrimitiveType string bool number 19 | syn keyword es_hclStructuralType object tuple 20 | syn keyword es_hclCollectionType list map set 21 | syn keyword es_hclValueNull null 22 | 23 | hi def link es_hclSection Structure 24 | hi def link es_hclBraces Delimiter 25 | hi def link es_hclValueString String 26 | hi def link es_hclStringInterp Identifier 27 | hi def link es_hclComment Comment 28 | hi def link es_hclContent Structure 29 | hi def link es_hclRepeat Repeat 30 | hi def link es_hclConditional Conditional 31 | hi def link es_hclPrimitiveType Type 32 | hi def link es_hclStructuralType Type 33 | hi def link es_hclCollectionType Type 34 | hi def link es_hclValueNull Constant 35 | 36 | let b:current_syntax = 'es_ctx_hcl' 37 | -------------------------------------------------------------------------------- /syntax/es_ctx_html.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn region es_htmlString contained start=+"+ end=+"\|$+ 8 | syn region es_htmlString contained start=+'+ end=+'\|$+ 9 | syn region es_htmlEndTag start=+\|$+ contains=es_htmlTagN 10 | syn region es_htmlTag start=+<[^/]+ end=+>\|$+ contains=es_htmlTagN,es_htmlString keepend 11 | syn match es_htmlTagName "\h[-\w]*" 12 | syn match es_htmlTagN contained +<\s*[-a-zA-Z0-9]\++hs=s+1 contains=es_htmlTagName 13 | syn match es_htmlTagN contained +"ms=s+1 13 | syn keyword es_javaOperator new instanceof 14 | syn keyword es_javaStatement return 15 | syn keyword es_javaStorageClass static synchronized transient volatile final strictfp serializable 16 | syn keyword es_javaExceptions throw try catch finally 17 | syn keyword es_javaAssert assert 18 | syn keyword es_javaClassDecl extends implements interface 19 | syn match es_javaClassDecl "@interface\>" 20 | syn keyword es_javaClassDecl enum 21 | syn keyword es_javaScopeDecl public protected private abstract 22 | 23 | syn region es_javaComment start="//" end="$" 24 | syn region es_javaComment start="/\*" end="\*/\|^" 25 | syn region es_javaString start=+L\="+ skip=+\\\\\|\\"+ end=+"\|^+ 26 | 27 | hi def link es_javaConditional Conditional 28 | hi def link es_javaRepeat Repeat 29 | hi def link es_javaBoolean Boolean 30 | hi def link es_javaConstant Constant 31 | hi def link es_javaTypedef Typedef 32 | hi def link es_javaOperator Operator 33 | hi def link es_javaStatement Statement 34 | hi def link es_javaStorageClass StorageClass 35 | hi def link es_javaExceptions Exception 36 | hi def link es_javaAssert Statement 37 | hi def link es_javaClassDecl StorageClass 38 | hi def link es_javaScopeDecl StorageClass 39 | hi def link es_javaComment Comment 40 | hi def link es_javaString String 41 | 42 | let b:current_syntax = 'es_ctx_java' 43 | -------------------------------------------------------------------------------- /syntax/es_ctx_javascript.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn keyword es_javaScriptConditional if else switch 8 | syn keyword es_javaScriptRepeat while for do in 9 | syn keyword es_javaScriptBranch break continue 10 | syn keyword es_javaScriptOperator new delete instanceof typeof 11 | syn keyword es_javaScriptStatement return with 12 | syn keyword es_javaScriptNull null undefined 13 | syn keyword es_javaScriptBoolean true false 14 | syn keyword es_javaScriptIdentifier arguments this var let 15 | syn keyword es_javaScriptLabel case default 16 | syn keyword es_javaScriptException try catch finally throw 17 | syn keyword es_javaScriptReserved abstract class const debugger export extends import 18 | syn keyword es_javaScriptFunction function 19 | syn region es_javaScriptComment start="//" end="$" 20 | syn region es_javaScriptComment start="/\*" end="\*/\|^" 21 | syn region es_javaScriptString start=+L\="+ skip=+\\\\\|\\"+ end=+"\|^+ 22 | syn region es_javaScriptString start=+L\='+ skip=+\\\\\|\\'+ end=+'\|^+ 23 | 24 | hi def link es_javaScriptConditional Conditional 25 | hi def link es_javaScriptRepeat Repeat 26 | hi def link es_javaScriptBranch Conditional 27 | hi def link es_javaScriptOperator Operator 28 | hi def link es_javaScriptStatement Statement 29 | hi def link es_javaScriptNull Keyword 30 | hi def link es_javaScriptBoolean Boolean 31 | hi def link es_javaScriptIdentifier Identifier 32 | hi def link es_javaScriptLabel Label 33 | hi def link es_javaScriptException Exception 34 | hi def link es_javaScriptReserved Keyword 35 | hi def link es_javaScriptFunction Function 36 | hi def link es_javaScriptComment Comment 37 | hi def link es_javaScriptString String 38 | 39 | let b:current_syntax = 'es_ctx_javascript' 40 | -------------------------------------------------------------------------------- /syntax/es_ctx_json.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn keyword es_jsonBoolean true false 8 | syn keyword es_jsonNull null 9 | syn match es_jsonBraces /[{}\[\]]/ 10 | syn match es_jsonString /"\([^"]\|\\\"\)\+[[:blank:]"\n\r]/hs=s+1,he=e-1 11 | syn match es_jsonKeyword /"\([^"]\|\\\"\)\+["[:blank:]\r\n]*\ze\:/hs=s+1,he=e-1 12 | 13 | hi def link es_jsonBoolean Boolean 14 | hi def link es_jsonNull Function 15 | hi def link es_jsonKeyword Label 16 | hi def link es_jsonString String 17 | hi def link es_jsonBraces Delimiter 18 | 19 | let b:current_syntax = 'es_ctx_json' 20 | -------------------------------------------------------------------------------- /syntax/es_ctx_php.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn keyword es_phpConditional declare else enddeclare endswitch elseif endif if switch 8 | syn keyword es_phpRepeat as do endfor endforeach endwhile for foreach while 9 | syn keyword es_phpLabel case default switch 10 | syn keyword es_phpStatement return break continue exit goto yield 11 | syn keyword es_phpKeyword var const 12 | syn keyword es_phpStructure namespace extends implements instanceof parent self 13 | syn keyword es_phpConstant __LINE__ __FILE__ __FUNCTION__ __METHOD__ __CLASS__ __DIR__ __NAMESPACE__ __TRAIT__ 14 | syn match es_phpIdentifier "$\h\w*" 15 | syn region es_phpComment start="/\*" end="\*/\|^" 16 | syn match es_phpComment "#.\{-}\(?>\|$\)\@=" 17 | syn match es_phpComment "//.\{-}\(?>\|$\)\@=" 18 | syn region es_phpStringDouble start=/\v"/ skip=/\v\\./ end=/\v"|^/ 19 | syn region es_phpStringSingle start=/\v'/ skip=/\v\\./ end=/\v'|^/ 20 | 21 | hi def link es_phpConditional Conditional 22 | hi def link es_phpRepeat Repeat 23 | hi def link es_phpLabel Label 24 | hi def link es_phpStatement Statement 25 | hi def link es_phpKeyword Statement 26 | hi def link es_phpStructure Structure 27 | hi def link es_phpIdentifier Identifier 28 | hi def link es_phpConstant Constant 29 | hi def link es_phpComment Comment 30 | hi def link es_phpStringSingle String 31 | hi def link es_phpStringDouble String 32 | 33 | 34 | let b:current_syntax = 'es_ctx_php' 35 | -------------------------------------------------------------------------------- /syntax/es_ctx_python.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn keyword es_pythonStatement False None True 8 | syn keyword es_pythonStatement as assert break continue del exec global 9 | syn keyword es_pythonStatement lambda nonlocal pass print return with yield 10 | syn keyword es_pythonStatement class def nextgroup=es_pythonFunction skipwhite 11 | syn match es_pythonFunction "\h\w*" display contained 12 | syn keyword es_pythonConditional elif else if 13 | syn keyword es_pythonRepeat for while 14 | syn keyword es_pythonOperator and in is not or 15 | syn keyword es_pythonException except finally raise try 16 | syn keyword es_pythonInclude from import 17 | syn keyword es_pythonAsync async await 18 | syn match es_pythonComment "#.*$" 19 | syn region es_pythonString 20 | \ start=+[uU]\=[rR]\?\z(['"]\)+ end="\z1\|^" skip="\\\\\|\\\z1" 21 | syn region es_pythonString 22 | \ start=+[uU]\=[rR]\?\z('''\|"""\)+ end="\z1\|$" 23 | 24 | hi def link es_pythonStatement Statement 25 | hi def link es_pythonFunction Function 26 | hi def link es_pythonConditional Conditional 27 | hi def link es_pythonRepeat Repeat 28 | hi def link es_pythonOperator Operator 29 | hi def link es_pythonException Exception 30 | hi def link es_pythonInclude Include 31 | hi def link es_pythonAsync Statement 32 | hi def link es_pythonComment Comment 33 | hi def link es_pythonString String 34 | 35 | let b:current_syntax = 'es_ctx_python' 36 | -------------------------------------------------------------------------------- /syntax/es_ctx_ruby.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn keyword es_rubyControl and break in next not or redo rescue retry return 8 | syn keyword es_rubyControl case begin do for if unless while until else elsif ensure then when end 9 | syn keyword es_rubyBoolean true false 10 | syn keyword es_rubyDefine alias def undef nextgroup=es_rubyFunction skipwhite 11 | syn keyword es_rubyDefine class module nextgroup=es_rubyConstant skipwhite 12 | syn match es_rubyFunction "[a-b]*" contained 13 | syn match es_rubyConstant "\<\u\w*" 14 | syn keyword es_rubyKeyword super yield 15 | syn keyword es_rubyMacro include extend prepend 16 | syn keyword es_rubyPseudoVariable nil self __ENCODING__ __dir__ __FILE__ __LINE__ __callee__ __method__ 17 | syn region es_rubyString start=/\v"/ skip=/\v\\./ end=/\v"|^/ 18 | syn region es_rubyString start=/\v'/ skip=/\v\\./ end=/\v'|^/ 19 | syn match es_rubyComment "#.*" 20 | 21 | hi def link es_rubyControl Statement 22 | hi def link es_rubyBoolean Boolean 23 | hi def link es_rubyDefine Define 24 | hi def link es_rubyFunction Function 25 | hi def link es_rubyConstant Type 26 | hi def link es_rubyKeyword Keyword 27 | hi def link es_rubyMacro Macro 28 | hi def link es_rubyPseudoVariable Constant 29 | hi def link es_rubyString String 30 | hi def link es_rubyComment Comment 31 | 32 | let b:current_syntax = 'es_ctx_ruby' 33 | -------------------------------------------------------------------------------- /syntax/es_ctx_sh.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn keyword es_shStatement break cd chdir continue eval exec exit kill newgrp pwd read readonly return shift test trap ulimit umask wait 8 | syn match es_shDerefSimple "\$\%(\h\w*\|\d\)" 9 | syn keyword es_shKeyword case esac do done for in if fi until while 10 | syn keyword es_shSetList declare local export set unset 11 | syn region es_shSingleQuote start=+'+ end=+'\|$+ 12 | syn region es_shDoubleQuote start=+\%(\%(\\\\\)*\\\)\@/ skipwhite nextgroup=es_vimOption 11 | syn match es_vimOption "\w\+" contained 12 | syn match es_vimVar "\<[bwglstav]:\h[a-zA-Z0-9#_]*" 13 | 14 | syn match es_vimFunction "\%(\<[bwglstav]:\)\=\h[a-zA-Z0-9#_]*\ze(" 15 | syn match es_vimFuncName "[^.]\zs\<[_0-9a-z]\+\ze(" 16 | syn region es_vimString start=+"+ skip=+\\\\\|\\"+ end=+"\|^+ 17 | syn region es_vimString start=+'+ end=+'\|^+ 18 | syn match es_vimComment +\s"[^\-:.%#=*].*[^"]$+lc=1 19 | 20 | hi def link es_vimCommand Statement 21 | hi def link es_vimVar Identifier 22 | hi def link es_vimFuncName Function 23 | hi def link es_vimString String 24 | hi def link es_vimComment Comment 25 | hi def link es_vimOption PreProc 26 | 27 | let b:current_syntax = 'es_ctx_vim' 28 | -------------------------------------------------------------------------------- /syntax/es_ctx_xml.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | syn region es_xmlTag start=+<[^ ]+ end=+>\|^+ contains=es_xmlAttrib,es_xmlString 6 | syn region es_xmlProcessing matchgroup=es_xmlProcessingDelim start="" contains=es_xmlString,es_xmlProcessingAttrib 7 | syn region es_xmlEndTag start=+\|^+ 8 | syn match es_xmlAttrib +[^ =]\+\ze\s*=+ contained 9 | syn match es_xmlProcessingAttrib "[^ =]\+" contained 10 | syn region es_xmlString start=+\z(["']\)+ skip=+\\\\\|\\\z1+ end=+\z1\|^+ keepend contained 11 | syn region es_xmlComment start=+\|^+ 12 | 13 | hi def link es_xmlTag Function 14 | hi def link es_xmlProcessingDelim Comment 15 | hi def link es_xmlEndTag Identifier 16 | hi def link es_xmlAttrib Type 17 | hi def link es_xmlProcessingAttrib Type 18 | hi def link es_xmlString String 19 | hi def link es_xmlComment Comment 20 | 21 | let b:current_syntax = 'es_ctx_xml' 22 | -------------------------------------------------------------------------------- /syntax/es_ctx_yaml.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | " based on vim builtin syntax 6 | 7 | syn keyword es_yamlNull null 8 | syn keyword es_yamlBool true false 9 | 10 | syn match es_yamlBlockMappingKey /\zs[^ :]\+\ze\s*:/ nextgroup=es_yamlKeyValueDelimiter 11 | syn match es_yamlMappingMerge /<<\ze\s*:/ nextgroup=es_yamlKeyValueDelimiter 12 | syn match es_yamlKeyValueDelimiter /\s*:/ 13 | syn match es_yamlFlowIndicator /[{}\[\]]/ 14 | syn match es_yamlAnchorOrAnchor /[&*][^ ]\+/ 15 | syn match es_yamlBlockCollectionItemStart /-/ 16 | 17 | syn region es_yamlFlowString start=/"/ skip=/\\"/ end=/"\|^/ 18 | syn region es_yamlFlowString start=/'/ skip=/\\'/ end=/'\|^/ 19 | syn region es_yamlComment start='\%\(^\|\s\)#' end='$' oneline 20 | 21 | hi def link es_yamlBlockMappingKey Identifier 22 | hi def link es_yamlKeyValueDelimiter Special 23 | hi def link es_yamlBool Boolean 24 | hi def link es_yamlNull Constant 25 | hi def link es_yamlAnchorOrAnchor Type 26 | hi def link es_yamlMappingMerge Special 27 | hi def link es_yamlFlowString String 28 | hi def link es_yamlFlowIndicator Special 29 | hi def link es_yamlComment Comment 30 | hi def link es_yamlBlockCollectionItemStart Label 31 | 32 | let b:current_syntax = 'es_ctx_json' 33 | -------------------------------------------------------------------------------- /syntax/esearch.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | if !g:esearch.win_ui_nvim_syntax 6 | syn match esearchLineNr '^\s\+[+^_]\=\s*\d\+\s' 7 | syn match esearchDiffAdd '^\s\+\zs[+^_]' contained containedin=esearchLineNr 8 | syn match esearchFilename '^[^ ].*$' 9 | syn match esearchHeader '\%1l.*' 10 | syn match esearchStatistics '\d\+' contained containedin=esearchHeader 11 | endif 12 | 13 | let b:current_syntax = 'esearch' 14 | -------------------------------------------------------------------------------- /syntax/esearch_test.vim: -------------------------------------------------------------------------------- 1 | if !hlexists('esearchHeader') | call esearch#highlight#init() | endif 2 | syn match esearchLineNr '^\s\s\s\+[+^_]\=\s*\d\+\s' 3 | syn match esearchDiffAdd '^\s\s\s\+\zs[+^_]' contained containedin=esearchLineNr 4 | syn match esearchFilename '^\s\s[^ ].*$' 5 | syn match esearchHeader '\s\sMatches.*' 6 | syn match esearchStatistics '\d\+' contained containedin=esearchStatistics 7 | --------------------------------------------------------------------------------