├── bin └── .gitkeep ├── docs ├── .gitignore ├── custom.css ├── book.toml └── src │ ├── SUMMARY.md │ ├── plugins │ └── intro.md │ ├── providers │ ├── theme.md │ └── search_syntax.md │ └── guide │ ├── installation.md │ └── install_rust.md ├── pythonx └── clap │ ├── __init__.py │ ├── fuzzymatch-rs │ ├── .gitignore │ ├── .cargo │ │ └── config │ └── Cargo.toml │ ├── Makefile │ ├── fzy.py │ └── test_fzy_with_rust.py ├── rustfmt.toml ├── crates ├── matcher │ ├── extracted_fzy │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ │ └── scoring_utils.rs │ ├── src │ │ ├── matchers │ │ │ ├── mod.rs │ │ │ ├── bonus_matcher │ │ │ │ ├── bonus │ │ │ │ │ ├── recent_files.rs │ │ │ │ │ ├── cwd.rs │ │ │ │ │ ├── filename.rs │ │ │ │ │ ├── language.rs │ │ │ │ │ └── mod.rs │ │ │ │ └── mod.rs │ │ │ ├── inverse_matcher.rs │ │ │ └── fuzzy_matcher.rs │ │ └── algo │ │ │ ├── fzf.rs │ │ │ ├── fzy.rs │ │ │ ├── skim.rs │ │ │ ├── nucleo.rs │ │ │ └── mod.rs │ └── Cargo.toml ├── maple_derive │ ├── src │ │ ├── impls │ │ │ └── mod.rs │ │ └── lib.rs │ ├── Cargo.toml │ └── tests │ │ └── tests.rs ├── maple_core │ ├── src │ │ ├── tools │ │ │ ├── mod.rs │ │ │ └── gtags │ │ │ │ └── mod.rs │ │ ├── stdio_server │ │ │ ├── provider │ │ │ │ ├── hooks │ │ │ │ │ └── mod.rs │ │ │ │ └── impls │ │ │ │ │ └── mod.rs │ │ │ └── job.rs │ │ ├── lib.rs │ │ ├── process │ │ │ └── subprocess.rs │ │ ├── types.rs │ │ ├── previewer │ │ │ ├── mod.rs │ │ │ └── vim_help.rs │ │ ├── find_usages │ │ │ └── search_engine │ │ │ │ └── ctags │ │ │ │ └── kinds.rs │ │ ├── helptags.rs │ │ └── searcher │ │ │ └── grep │ │ │ └── mod.rs │ └── Cargo.toml ├── code_tools │ ├── src │ │ ├── lib.rs │ │ ├── linting │ │ │ └── linters │ │ │ │ ├── mod.rs │ │ │ │ ├── go.rs │ │ │ │ └── python.rs │ │ ├── analyzer │ │ │ └── keywords │ │ │ │ ├── viml.rs │ │ │ │ ├── erlang.rs │ │ │ │ ├── golang.rs │ │ │ │ ├── mod.rs │ │ │ │ └── rust.rs │ │ └── formatting │ │ │ └── mod.rs │ └── Cargo.toml ├── dirs │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── cli │ ├── src │ │ └── command │ │ │ ├── mod.rs │ │ │ ├── helptags.rs │ │ │ ├── ctags │ │ │ ├── buffer_tags.rs │ │ │ └── tags_file.rs │ │ │ ├── gtags.rs │ │ │ └── exec.rs │ └── Cargo.toml ├── types │ ├── Cargo.toml │ └── src │ │ └── query.rs ├── pattern │ └── Cargo.toml ├── upgrade │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── paths │ └── Cargo.toml ├── utils │ ├── Cargo.toml │ └── src │ │ └── bytelines.rs ├── printer │ ├── src │ │ └── trimmer │ │ │ └── mod.rs │ └── Cargo.toml ├── xtask │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── rpc │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── sublime_syntax │ └── Cargo.toml ├── maple_config │ ├── Cargo.toml │ └── doc_gen │ │ └── Cargo.toml ├── maple │ ├── build.rs │ └── Cargo.toml ├── filter │ ├── Cargo.toml │ └── src │ │ └── sequential_source.rs ├── icon │ ├── Cargo.toml │ ├── tagkind_map.json │ ├── exactmatch_map.json │ ├── update_constants.py │ └── build.rs ├── maple_lsp │ └── Cargo.toml ├── maple_markdown │ └── Cargo.toml └── tree_sitter │ ├── Cargo.toml │ └── benches │ └── benchmark.rs ├── config.toml ├── rust-toolchain.toml ├── assets ├── syntaxes.bin ├── themes.bin └── create.sh ├── .vintrc.yaml ├── test ├── testdata │ └── test_673.txt ├── bench │ └── python │ │ ├── fetch_testdata.sh │ │ ├── profile.vimrc │ │ └── test_fuzzy_filter.vim └── autoload_should_check_cpo.sh ├── .github ├── FUNDING.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── docs.yml ├── ci ├── run_test.sh └── build_static_binary.sh ├── syntax ├── clap_blines.vim ├── clap_command_history.vim ├── clap_jumps.vim ├── clap_files.vim ├── clap_providers.vim ├── clap_lines.vim ├── clap_marks.vim ├── clap_filer.vim ├── clap_registers.vim ├── clap_dumb_jump.vim ├── clap_buffers.vim ├── clap_tagfiles.vim ├── clap_proj_tags.vim ├── clap_command.vim ├── clap_grep.vim ├── clap_diff.vim └── clap_tags.vim ├── setup_python.py ├── .editorconfig ├── ftplugin ├── clap_grep.vim ├── clap_spinner.vim ├── clap_action.vim └── clap_display.vim ├── .gitignore ├── Makefile ├── autoload └── clap │ ├── provider │ ├── filetypes.vim │ ├── recent_files.vim │ ├── search_history.vim │ ├── command_history.vim │ ├── grep.vim │ ├── git_files.vim │ ├── jumps.vim │ ├── history.vim │ ├── igrep.vim │ ├── proj_tags.vim │ ├── files.vim │ ├── bcommits.vim │ ├── git_diff_files.vim │ ├── dumb_jump.vim │ ├── registers.vim │ ├── filer.vim │ ├── colors.vim │ ├── maps.vim │ ├── tagfiles.vim │ ├── lines.vim │ ├── windows.vim │ └── quickfix.vim │ ├── rpc.vim │ ├── sink.vim │ ├── common_history.vim │ ├── legacy │ ├── highlighter.vim │ ├── filter │ │ └── sync │ │ │ ├── viml.vim │ │ │ ├── lua.vim │ │ │ └── python.vim │ └── state.vim │ ├── themes │ ├── atom_dark.vim │ ├── material_design_dark.vim │ ├── onehalfdark.vim │ ├── onehalflight.vim │ └── nord.vim │ ├── ext.vim │ ├── cache.vim │ ├── action.vim │ ├── maple.vim │ ├── plugin │ ├── word_highlighter.vim │ └── colorizer.vim │ ├── popup │ └── action.vim │ ├── impl │ └── on_move.vim │ └── job.vim ├── install.ps1 ├── README.md ├── LICENSE ├── install.sh └── scripts └── dumb_jump └── generate_pattern.py /bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /pythonx/clap/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Module" 2 | -------------------------------------------------------------------------------- /crates/matcher/extracted_fzy/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | !.cargo -------------------------------------------------------------------------------- /pythonx/clap/fuzzymatch-rs/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | !.cargo -------------------------------------------------------------------------------- /docs/custom.css: -------------------------------------------------------------------------------- 1 | h2, h3 { 2 | margin-block-start: 1.5em; 3 | } 4 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --manifest-path ./crates/xtask/Cargo.toml --" 3 | -------------------------------------------------------------------------------- /crates/maple_derive/src/impls/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod clap_plugin; 2 | pub mod subscriptions; 3 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.91" 3 | components = ["clippy"] 4 | -------------------------------------------------------------------------------- /assets/syntaxes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/vim-clap/HEAD/assets/syntaxes.bin -------------------------------------------------------------------------------- /assets/themes.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/vim-clap/HEAD/assets/themes.bin -------------------------------------------------------------------------------- /.vintrc.yaml: -------------------------------------------------------------------------------- 1 | cmdargs: 2 | severity: style_problem 3 | color: true 4 | env: 5 | neovim: false 6 | -------------------------------------------------------------------------------- /crates/maple_core/src/tools/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ctags; 2 | pub mod git; 3 | pub mod gtags; 4 | pub mod rg; 5 | -------------------------------------------------------------------------------- /test/testdata/test_673.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuchengxu/vim-clap/HEAD/test/testdata/test_673.txt -------------------------------------------------------------------------------- /crates/code_tools/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod analyzer; 2 | pub mod formatting; 3 | pub mod language; 4 | pub mod linting; 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: https://www.paypal.me/liuchengxu 4 | -------------------------------------------------------------------------------- /crates/dirs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dirs" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | directories = { workspace = true } 8 | -------------------------------------------------------------------------------- /test/bench/python/fetch_testdata.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | curl https://raw.githubusercontent.com/liuchengxu/img/master/vim-clap/vim_clap_testdata.txt -o testdata.txt 4 | -------------------------------------------------------------------------------- /crates/code_tools/src/linting/linters/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod go; 2 | pub(crate) mod python; 3 | pub(crate) mod rust; 4 | pub(crate) mod sh; 5 | pub(crate) mod typos; 6 | pub(crate) mod vim; 7 | -------------------------------------------------------------------------------- /ci/run_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd "$(dirname "${BASH_SOURCE[0]}")" || exit 4 | 5 | cd .. 6 | 7 | cargo install ripgrep 8 | 9 | cargo test --verbose --all -- --nocapture 10 | -------------------------------------------------------------------------------- /crates/maple_core/src/stdio_server/provider/hooks/mod.rs: -------------------------------------------------------------------------------- 1 | mod on_initialize; 2 | mod on_move; 3 | 4 | pub use self::on_initialize::initialize_provider; 5 | pub use self::on_move::{CachedPreviewImpl, Preview, PreviewTarget}; 6 | -------------------------------------------------------------------------------- /crates/cli/src/command/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod blines; 2 | pub mod cache; 3 | pub mod ctags; 4 | pub mod dumb_jump; 5 | pub mod exec; 6 | pub mod filter; 7 | pub mod grep; 8 | pub mod gtags; 9 | pub mod helptags; 10 | pub mod rpc; 11 | -------------------------------------------------------------------------------- /syntax/clap_blines.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapBlinesLineNr /^\s*\d\+/ contained 2 | syntax match ClapBlines /^.*$/ contains=ClapBlinesLineNr 3 | 4 | hi default link ClapBlinesLineNr Number 5 | hi default link ClapBlines SpecialComment 6 | -------------------------------------------------------------------------------- /syntax/clap_command_history.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapCommandHistNr /^\s*\d\+/ 2 | syntax match ClapCommandHist /^.$/ contains=ClapCommandHistNr 3 | 4 | hi default link ClapCommandHistNr Number 5 | hi default link ClapCommandHist LineNr 6 | -------------------------------------------------------------------------------- /crates/types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "types" 3 | authors.workspace = true 4 | edition.workspace = true 5 | version.workspace = true 6 | 7 | [dependencies] 8 | icon = { workspace = true } 9 | pattern = { workspace = true } 10 | -------------------------------------------------------------------------------- /crates/pattern/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pattern" 3 | authors.workspace = true 4 | edition.workspace = true 5 | version.workspace = true 6 | 7 | [dependencies] 8 | once_cell = { workspace = true } 9 | regex = { workspace = true } 10 | -------------------------------------------------------------------------------- /setup_python.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from os.path import normpath, join 3 | import vim 4 | plugin_root_dir = vim.eval('g:clap#autoload_dir') 5 | python_root_dir = normpath(join(plugin_root_dir, '..', 'pythonx')) 6 | sys.path.insert(0, python_root_dir) 7 | import clap 8 | -------------------------------------------------------------------------------- /test/bench/python/profile.vimrc: -------------------------------------------------------------------------------- 1 | set nocompatible 2 | 3 | let s:cur_dir = fnamemodify(resolve(expand(':p')), ':h:h:h:h') 4 | execute 'set runtimepath^='.s:cur_dir 5 | 6 | syntax on 7 | filetype plugin indent on 8 | 9 | source test_fuzzy_filter.vim 10 | -------------------------------------------------------------------------------- /crates/upgrade/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate implements the functionality of downloading an asset from GitHub release 2 | //! and provides the feature of upgrading the maple executable on top of it. 3 | 4 | mod github; 5 | mod maple_upgrade; 6 | 7 | pub use maple_upgrade::Upgrade; 8 | -------------------------------------------------------------------------------- /assets/create.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # REPO=sharkdp/bat 4 | 5 | REPO=Aloxaf/silicon 6 | 7 | curl -fLo $PWD/syntaxes.bin https://raw.githubusercontent.com/$REPO/master/assets/syntaxes.bin 8 | curl -fLo $PWD/themes.bin https://raw.githubusercontent.com/$REPO/master/assets/themes.bin 9 | -------------------------------------------------------------------------------- /crates/paths/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "paths" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | dunce = "1.0" 8 | itertools = { workspace = true } 9 | serde = { workspace = true } 10 | shellexpand = { workspace = true } 11 | 12 | dirs = { workspace = true } 13 | -------------------------------------------------------------------------------- /crates/utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "utils" 3 | authors.workspace = true 4 | version.workspace = true 5 | edition.workspace = true 6 | 7 | [dependencies] 8 | bytecount = { workspace = true } 9 | memchr = { workspace = true } 10 | memmap2 = { workspace = true } 11 | simdutf8 = { workspace = true } 12 | -------------------------------------------------------------------------------- /pythonx/clap/fuzzymatch-rs/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-apple-darwin] 2 | rustflags = [ 3 | "-C", "link-arg=-undefined", 4 | "-C", "link-arg=dynamic_lookup", 5 | ] 6 | 7 | [target.aarch64-apple-darwin] 8 | rustflags = [ 9 | "-C", "link-arg=-undefined", 10 | "-C", "link-arg=dynamic_lookup", 11 | ] 12 | -------------------------------------------------------------------------------- /syntax/clap_jumps.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapJump /^\s\+\d\+/ nextgroup=ClapJumpLineCol 2 | syntax match ClapJumpsHeader /jump line col file\/text/ 3 | syntax match ClapJumpLineCol /\s\+\zs\d\+\ze\s\+/ 4 | 5 | hi default link ClapJump Function 6 | hi default link ClapJumpsHeader Title 7 | hi default link ClapJumpLineCol Number 8 | -------------------------------------------------------------------------------- /syntax/clap_files.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapFileName /\v(\/|\s)\zs(\w|[-.])+(\.\w+)?$/ contained 2 | execute 'syntax region ClapFilePath' 'start=/^\s*\S/ end=/(\w|[-.])+(\.\w\+)?$/' 'contains='.join(clap#icon#add_head_hl_groups(), ',').',ClapFileName' 3 | 4 | highlight default link ClapFilePath Normal 5 | highlight default link ClapFileName Special 6 | -------------------------------------------------------------------------------- /syntax/clap_providers.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapProviderColon /:/ contained 2 | syntax match ClapProviderId /^\w\+:\? \?/ contains=ClapProviderColon 3 | syntax match ClapProviderAbout /^.*$/ contains=ClapProviderId 4 | 5 | hi default link ClapProviderId Function 6 | hi default link ClapProviderColon Type 7 | hi default link ClapProviderAbout String 8 | -------------------------------------------------------------------------------- /crates/matcher/extracted_fzy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "extracted_fzy" 3 | authors = ["ImmConCon <43708554+ImmemorConsultrixContrarie@users.noreply.github.com>"] 4 | description = "OS independent fzy algorithm from \"rff\" crate." 5 | edition.workspace = true 6 | version.workspace = true 7 | license.workspace = true 8 | publish.workspace = true 9 | -------------------------------------------------------------------------------- /syntax/clap_lines.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapLinesBufnr /^\[\d\+\]/ nextgroup=ClapLinesBufname 2 | 3 | syntax match ClapLinesBufname / \f*\.\f* / nextgroup=ClapLinesNumber 4 | 5 | syntax match ClapLinesNumber / \d\+ / 6 | 7 | hi default link ClapLinesBufnr Function 8 | hi default link ClapLinesBufname Type 9 | hi default link ClapLinesNumber Number 10 | -------------------------------------------------------------------------------- /crates/printer/src/trimmer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod v0; 2 | pub mod v1; 3 | 4 | pub struct AsciiDots; 5 | 6 | impl AsciiDots { 7 | pub const DOTS: &'static str = ".."; 8 | pub const CHAR_LEN: usize = 2; 9 | } 10 | 11 | pub struct UnicodeDots; 12 | 13 | impl UnicodeDots { 14 | pub const DOTS: char = '…'; 15 | pub const CHAR_LEN: usize = 1; 16 | } 17 | -------------------------------------------------------------------------------- /crates/xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | anyhow = { workspace = true } 8 | clap = { workspace = true } 9 | chrono = { workspace = true } 10 | serde = { workspace = true } 11 | serde_json = { workspace = true } 12 | xshell = "0.2.5" 13 | write-json = "0.1.4" 14 | -------------------------------------------------------------------------------- /crates/rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rpc" 3 | authors.workspace = true 4 | edition.workspace = true 5 | version.workspace = true 6 | 7 | [dependencies] 8 | serde = { workspace = true } 9 | serde_json = { workspace = true } 10 | thiserror = { workspace = true } 11 | tokio = { workspace = true, features = ["rt", "sync"] } 12 | tracing = { workspace = true } 13 | -------------------------------------------------------------------------------- /crates/sublime_syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sublime_syntax" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | colors-transform = { workspace = true } 8 | rgb2ansi256 = { workspace = true } 9 | serde = { workspace = true, features = ["derive"] } 10 | syntect = { workspace = true } 11 | 12 | utils = { workspace = true } 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "21:00" 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: cargo 10 | directory: "/pythonx/clap/fuzzymatch-rs" 11 | schedule: 12 | interval: weekly 13 | time: "21:00" 14 | open-pull-requests-limit: 10 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # Top-most EditorConfig file 4 | root = true 5 | 6 | # Match and apply these rules for all file 7 | # types you open in your code editor 8 | [*] 9 | # Unix-style newlines 10 | end_of_line = lf 11 | indent_size = 2 12 | indent_style = space 13 | insert_final_newline = true 14 | trim_trailing_whitespace = true 15 | -------------------------------------------------------------------------------- /crates/maple_config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "maple_config" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | dirs = { workspace = true } 8 | once_cell = { workspace = true } 9 | serde = { workspace = true } 10 | serde_json = { workspace = true } 11 | toml = { workspace = true } 12 | 13 | paths = { workspace = true } 14 | types = { workspace = true } 15 | -------------------------------------------------------------------------------- /crates/matcher/src/matchers/mod.rs: -------------------------------------------------------------------------------- 1 | mod bonus_matcher; 2 | mod exact_matcher; 3 | mod fuzzy_matcher; 4 | mod inverse_matcher; 5 | mod word_matcher; 6 | 7 | pub use self::bonus_matcher::{Bonus, BonusMatcher}; 8 | pub use self::exact_matcher::ExactMatcher; 9 | pub use self::fuzzy_matcher::FuzzyMatcher; 10 | pub use self::inverse_matcher::InverseMatcher; 11 | pub use self::word_matcher::WordMatcher; 12 | -------------------------------------------------------------------------------- /ftplugin/clap_grep.vim: -------------------------------------------------------------------------------- 1 | setlocal 2 | \ nonumber 3 | \ norelativenumber 4 | \ nopaste 5 | \ nomodeline 6 | \ noswapfile 7 | \ nocursorcolumn 8 | \ colorcolumn= 9 | \ nobuflisted 10 | \ buftype=nofile 11 | \ bufhidden=hide 12 | \ textwidth=0 13 | \ nolist 14 | \ winfixwidth 15 | \ winfixheight 16 | \ nospell 17 | \ nofoldenable 18 | \ foldcolumn=0 19 | \ nowrap 20 | -------------------------------------------------------------------------------- /syntax/clap_marks.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapMark /^\s\+[0-9a-zA-Z`'"[\]\.\^]/ 2 | syntax match ClapMarkLine /\s\+\zs\d\+\s\+\ze\d\+/ 3 | syntax match ClapMarkFileText /\d\+\s\+\zs.*$/ 4 | syntax match ClapMarkHeader /mark line col file\/text/ 5 | 6 | hi default link ClapMark Function 7 | hi default link ClapMarkLine Number 8 | hi default link ClapMarkFileText String 9 | hi default link ClapMarkHeader Title 10 | -------------------------------------------------------------------------------- /ftplugin/clap_spinner.vim: -------------------------------------------------------------------------------- 1 | setlocal 2 | \ nonumber 3 | \ norelativenumber 4 | \ nopaste 5 | \ nomodeline 6 | \ noswapfile 7 | \ nocursorline 8 | \ nocursorcolumn 9 | \ colorcolumn= 10 | \ nobuflisted 11 | \ buftype=nofile 12 | \ bufhidden=hide 13 | \ textwidth=0 14 | \ nolist 15 | \ winfixwidth 16 | \ winfixheight 17 | \ nospell 18 | \ nofoldenable 19 | \ foldcolumn=0 20 | \ nowrap 21 | -------------------------------------------------------------------------------- /test/autoload_should_check_cpo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd "$(dirname "${BASH_SOURCE[0]}")" 4 | 5 | cd .. 6 | 7 | expected="unlet s:save_cpo" 8 | 9 | for entry in $(find autoload -path '*' -type f) 10 | do 11 | last_line=$(tail -n 1 $entry) 12 | if [ "$expected" == "$last_line" ]; then 13 | echo "[PASS] $entry" 14 | else 15 | echo "[ERROR] $entry does not check compatible-options." 16 | exit 1 17 | fi 18 | done 19 | -------------------------------------------------------------------------------- /crates/maple_config/doc_gen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "doc_gen" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Generate config markdown document from the inline docs" 6 | 7 | [dependencies] 8 | inflections = "1.1.1" 9 | maple_config = { path = "../" } 10 | toml = "0.8" 11 | syn = { version = "2", features = ["full"] } 12 | quote = "1" 13 | itertools = "0.12.0" 14 | toml_edit = { version = "0.22", features = [ "parse" ] } 15 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Liu-Cheng Xu"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Vim-Clap Documentation" 7 | 8 | [rust] 9 | edition = "2021" 10 | 11 | [output.html] 12 | mathjax-support = true 13 | additional-css = ["custom.css"] 14 | edit-url-template = "https://github.com/liuchengxu/vim-clap/edit/master/docs/{path}" 15 | git-repository-url = "https://github.com/liuchengxu/vim-clap/tree/master/docs" 16 | -------------------------------------------------------------------------------- /crates/maple_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod cache; 2 | pub mod datastore; 3 | pub mod find_usages; 4 | pub mod helptags; 5 | mod previewer; 6 | pub mod process; 7 | mod recent_files; 8 | pub mod searcher; 9 | pub mod stdio_server; 10 | pub mod tools; 11 | pub(crate) mod types; 12 | 13 | /// For benchmarks. 14 | pub use self::cache::find_largest_cache_digest; 15 | // Re-export 16 | pub use {dirs, paths}; 17 | 18 | pub type UtcTime = chrono::DateTime; 19 | -------------------------------------------------------------------------------- /crates/maple_core/src/process/subprocess.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, Lines}; 2 | use subprocess::Exec; 3 | 4 | #[inline] 5 | pub fn exec(cmd: Exec) -> std::io::Result> { 6 | // We usually have a decent amount of RAM nowadays. 7 | Ok(std::io::BufReader::with_capacity( 8 | 8 * 1024 * 1024, 9 | cmd.stream_stdout() 10 | .map_err(|e| std::io::Error::other(e.to_string()))?, 11 | ) 12 | .lines()) 13 | } 14 | -------------------------------------------------------------------------------- /ftplugin/clap_action.vim: -------------------------------------------------------------------------------- 1 | nnoremap :call clap#floating_win#action#apply_choice() 2 | nnoremap :call clap#floating_win#action#close() 3 | nnoremap q :call clap#floating_win#action#close() 4 | nnoremap j :call clap#floating_win#action#next_item() 5 | nnoremap k :call clap#floating_win#action#prev_item() 6 | -------------------------------------------------------------------------------- /crates/maple_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "maple_derive" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | darling = "0.20.3" 11 | inflections = "1.1.1" 12 | once_cell = { workspace = true } 13 | proc-macro2 = "1.0" 14 | quote = "1.0" 15 | syn = { version = "2", features = ["full", "parsing"] } 16 | types = { workspace = true } 17 | 18 | [dev-dependencies] 19 | async-trait = { workspace = true } 20 | -------------------------------------------------------------------------------- /crates/maple/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::{env, fs}; 3 | 4 | fn main() { 5 | built::write_built_file() 6 | .unwrap_or_else(|e| panic!("Failed to acquire build-time information: {e:?}")); 7 | 8 | let outdir = env::var("OUT_DIR").unwrap(); 9 | let outfile = format!("{outdir}/compiled_at.txt"); 10 | 11 | let mut fh = fs::File::create(outfile).expect("Failed to create compiled_at.txt"); 12 | write!(fh, r#""{}""#, chrono::Local::now()).ok(); 13 | } 14 | -------------------------------------------------------------------------------- /crates/code_tools/src/analyzer/keywords/viml.rs: -------------------------------------------------------------------------------- 1 | use super::KeywordPriority; 2 | 3 | pub struct Viml; 4 | 5 | impl KeywordPriority for Viml { 6 | const DEFINITION: &'static [&'static str] = 7 | &["function", "function!", "command", "command!", "cmd"]; 8 | 9 | const REFERENCE: &'static [&'static str] = &["let"]; 10 | 11 | const STATEMENT: &'static [&'static str] = &[ 12 | "for", "endfor", "while", "endwhile", "if", "elseif", "else", "endif", "call", "in", 13 | ]; 14 | } 15 | -------------------------------------------------------------------------------- /crates/maple_core/src/types.rs: -------------------------------------------------------------------------------- 1 | pub enum Direction { 2 | First, 3 | Last, 4 | Next, 5 | Prev, 6 | } 7 | 8 | pub enum DiagnosticKind { 9 | All, 10 | Error, 11 | Warn, 12 | Hint, 13 | } 14 | 15 | #[derive(Clone, Copy, Debug)] 16 | pub enum Goto { 17 | Definition, 18 | Declaration, 19 | TypeDefinition, 20 | Implementation, 21 | Reference, 22 | } 23 | 24 | #[allow(dead_code)] 25 | pub enum GotoLocationsUI { 26 | Quickfix, 27 | ClapProvider, 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all hidden files everywhere. 2 | # Use `git add -f` to add hidden files. 3 | .* 4 | __pycache__ 5 | *.pyc 6 | *.log 7 | # Ignore all the vim files on the top directory 8 | /*.vim 9 | /*.rc 10 | [1-9]*.vim 11 | /doc/tags 12 | target 13 | /bin 14 | testdata.txt 15 | publish.sh 16 | crates/Cargo.lock 17 | pythonx/clap/fuzzymatch_rs.so 18 | 19 | !.cargo 20 | !.editorconfig 21 | !.travis.yml 22 | !.gitignore 23 | !.github 24 | !.vintrc.yaml 25 | !.dependabot 26 | !bin/.gitkeep 27 | !src/bin 28 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](README.md) 4 | 5 | # User Guide 6 | 7 | - [Installation](guide/installation.md) 8 | - [Install Rust Dependency](guide/install_rust.md) 9 | - [Clap Providers](providers/intro.md) 10 | - [Theme](providers/theme.md) 11 | - [Keybindings](providers/keybindings.md) 12 | - [Search Syntax](providers/search_syntax.md) 13 | - [Clap Plugins](plugins/intro.md) 14 | - [Configuration](plugins/config.md) 15 | - [Available Plugins](plugins/plugins.md) 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | maple: 2 | cargo build --release 3 | 4 | all: maple python-dynamic-module 5 | 6 | MAKE_CMD ?= "make" 7 | 8 | python-dynamic-module: 9 | cd pythonx/clap && $(MAKE_CMD) build 10 | 11 | config-md: 12 | cd crates/maple_config/doc_gen && cargo run 13 | 14 | clippy: 15 | cargo clippy --workspace --all-features --all-targets -- -D warnings 16 | 17 | release: 18 | cargo xtask release 19 | 20 | fmt: 21 | cargo +nightly fmt --all 22 | 23 | .PHONY: all maple python-dynamic-module config-md clippy release fmt 24 | -------------------------------------------------------------------------------- /syntax/clap_filer.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapDir '^.*/$' 2 | hi default link ClapDir Directory 3 | 4 | hi TNormal ctermfg=249 ctermbg=NONE guifg=#b2b2b2 guibg=NONE 5 | execute 'syntax match ClapFile' '/.*[^\/]$/' 'contains='.join(clap#icon#add_head_hl_groups(), ',') 6 | 7 | syntax match ClapFilerNew /\v^.*\[Create new file\].*$/ 8 | 9 | hi default link ClapFile TNormal 10 | hi default link ClapFilerNew Question 11 | 12 | syntax match ClapEmptyDirectory /^.*/ 13 | hi default link ClapEmptyDirectory WarningMsg 14 | -------------------------------------------------------------------------------- /crates/filter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filter" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | 7 | [dependencies] 8 | parking_lot = { workspace = true } 9 | rayon = { workspace = true } 10 | subprocess = { workspace = true } 11 | tracing = { workspace = true } 12 | thiserror = { workspace = true } 13 | serde_json = { workspace = true } 14 | 15 | icon = { workspace = true } 16 | matcher = { workspace = true } 17 | printer = { workspace = true } 18 | types = { workspace = true } 19 | -------------------------------------------------------------------------------- /autoload/clap/provider/filetypes.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the filetypes. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:filetypes = {} 8 | 9 | let s:filetypes.source = uniq(sort(map(split(globpath(&runtimepath, 'syntax/*.vim'), '\n'), 'fnamemodify(v:val, ":t:r")'))) 10 | let s:filetypes.sink = 'setf' 11 | let s:filetypes.mode = 'quick_pick' 12 | 13 | let g:clap#provider#filetypes# = s:filetypes 14 | 15 | let &cpoptions = s:save_cpo 16 | unlet s:save_cpo 17 | -------------------------------------------------------------------------------- /crates/upgrade/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "upgrade" 3 | authors.workspace = true 4 | version.workspace = true 5 | edition.workspace = true 6 | 7 | [dependencies] 8 | indicatif = { workspace = true } 9 | tokio = { workspace = true, features = ["fs", "macros", "rt", "io-util", "rt-multi-thread"] } 10 | # Use `rustls-tls` instead of `default-tls` to not pull in the openssl dep, making the cross-compile easier. 11 | reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false } 12 | serde = { workspace = true } 13 | -------------------------------------------------------------------------------- /crates/icon/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "icon" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | publish.workspace = true 8 | include = ["/Cargo.toml", "src/*.rs"] 9 | homepage.workspace = true 10 | description = """ 11 | icon is a tool for drawing an icon according to the path name for vim-clap. 12 | """ 13 | 14 | [dependencies] 15 | pattern = { workspace = true } 16 | 17 | [build-dependencies] 18 | itertools = { workspace = true } 19 | serde_json = { workspace = true } 20 | -------------------------------------------------------------------------------- /crates/matcher/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "matcher" 3 | authors.workspace = true 4 | version.workspace = true 5 | edition.workspace = true 6 | 7 | [dependencies] 8 | fuzzy-matcher = { workspace = true } 9 | grep-matcher = { workspace = true } 10 | grep-regex = { workspace = true } 11 | 12 | code_tools = { workspace = true } 13 | extracted_fzy = { path = "./extracted_fzy" } 14 | pattern = { workspace = true } 15 | types = { workspace = true } 16 | norm = { version = "0.1.1", features = ["fzf-v1", "fzf-v2", "__into-score"] } 17 | nucleo-matcher = "0.3.1" 18 | -------------------------------------------------------------------------------- /syntax/clap_registers.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapRegistersTitle /^[A-Za-z-]*/ contained 2 | syntax match ClapRegistersTitleColon /^[A-Za-z-]*:/ contains=ClapRegistersTitle 3 | syntax match ClapRegistersReg /^ ./ contained 4 | syntax match ClapRegistersRegColon /^ .:/ contains=ClapRegistersReg 5 | highlight default link ClapRegistersTitle Title 6 | highlight default link ClapRegistersTitleColon SpecialKey 7 | highlight default link ClapRegistersReg Label 8 | highlight default link ClapRegistersRegColon SpecialKey 9 | highlight default link ClapRegistersSelected Todo 10 | -------------------------------------------------------------------------------- /crates/maple_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod impls; 2 | 3 | use proc_macro::TokenStream; 4 | 5 | #[proc_macro_derive(ClapPlugin, attributes(clap_plugin))] 6 | pub fn clap_plugin_derive(input: TokenStream) -> TokenStream { 7 | match syn::parse(input) { 8 | Ok(ast) => impls::clap_plugin::clap_plugin_derive_impl(&ast), 9 | Err(e) => e.to_compile_error().into(), 10 | } 11 | } 12 | 13 | #[proc_macro_attribute] 14 | pub fn subscriptions(_attr: TokenStream, item: TokenStream) -> TokenStream { 15 | impls::subscriptions::subscriptions_impl(item) 16 | } 17 | -------------------------------------------------------------------------------- /crates/matcher/src/matchers/bonus_matcher/bonus/recent_files.rs: -------------------------------------------------------------------------------- 1 | use crate::Score; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct RecentFiles(Vec); 5 | 6 | impl From> for RecentFiles { 7 | fn from(inner: Vec) -> Self { 8 | Self(inner) 9 | } 10 | } 11 | 12 | impl RecentFiles { 13 | pub fn calc_bonus(&self, bonus_text: &str, base_score: Score) -> Score { 14 | if self.0.iter().any(|s| s.contains(bonus_text)) { 15 | base_score / 3 16 | } else { 17 | 0 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crates/code_tools/src/analyzer/keywords/erlang.rs: -------------------------------------------------------------------------------- 1 | use super::KeywordPriority; 2 | 3 | pub struct Erlang; 4 | 5 | impl KeywordPriority for Erlang { 6 | const DEFINITION: &'static [&'static str] = &["fun"]; 7 | 8 | const REFERENCE: &'static [&'static str] = &[]; 9 | 10 | const STATEMENT: &'static [&'static str] = &[ 11 | "after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor", "case", 12 | "catch", "cond", "div", "end", "if", "let", "not", "of", "or", "orelse", "receive", "rem", 13 | "try", "when", "xor", 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /crates/code_tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "code_tools" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | cargo_metadata = { workspace = true } 8 | once_cell = { workspace = true } 9 | regex = { workspace = true } 10 | serde = { workspace = true } 11 | serde_json = { workspace = true } 12 | tokio = { workspace = true, features = ["rt", "process", "sync"] } 13 | toml = { workspace = true } 14 | tracing = { workspace = true } 15 | which = { workspace = true } 16 | 17 | paths = { workspace = true } 18 | maple_config = { workspace = true } 19 | maple_lsp = { workspace = true } 20 | -------------------------------------------------------------------------------- /crates/icon/tagkind_map.json: -------------------------------------------------------------------------------- 1 | {"subroutine": "󰒰", "method": "", "func": "󰊕", "variables": "", "constructor": "略", "field": "", "interface": "", "type": "", "packages": "", "property": "", "implementation": "", "default": "", "augroup": "󰙅", "macro": "󰎤", "enumerator": "", "const": "", "macros": "󰎤", "map": "󰙅", "fields": "", "functions": "󰊕", "enum": "", "function": "󰊕", "target": "󰎔", "typedef": "", "namespace": "", "enummember": "", "variable": "", "modules": "", "constant": "", "struct": "", "types": "", "module": "", "typeParameter": "", "package": "", "class": "", "member": "", "var": "", "union": "󰕤"} 2 | -------------------------------------------------------------------------------- /crates/maple_lsp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "maple_lsp" 3 | version.workspace = true 4 | edition.workspace = true 5 | 6 | [dependencies] 7 | futures-util = "0.3.29" 8 | lsp = { workspace = true } 9 | parking_lot = { workspace = true } 10 | rpc = { workspace = true } 11 | serde = { workspace = true } 12 | serde_json = { workspace = true } 13 | thiserror = { workspace = true } 14 | tokio = { workspace = true, features = ["rt", "process", "macros", "sync", "time"] } 15 | toml = { workspace = true } 16 | tracing = { workspace = true } 17 | which = { workspace = true } 18 | 19 | dirs = { workspace = true } 20 | paths = { workspace = true } 21 | -------------------------------------------------------------------------------- /crates/printer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "printer" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | publish.workspace = true 8 | homepage.workspace = true 9 | 10 | [dependencies] 11 | serde = { workspace = true } 12 | serde_json = { workspace = true } 13 | unicode-width = { workspace = true } 14 | 15 | icon = { workspace = true } 16 | pattern = { workspace = true } 17 | types = { workspace = true } 18 | utils = { workspace = true } 19 | 20 | [target.'cfg(not(windows))'.dev-dependencies] 21 | filter = { workspace = true } 22 | rayon = { workspace = true } 23 | termion = { workspace = true } 24 | -------------------------------------------------------------------------------- /syntax/clap_dumb_jump.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapDumbLinNr /^.*:\zs\d\+\ze:\d\+:/hs=s+1,he=e-1 contained 2 | syntax match ClapDumbColumn /:\d\+:\zs\d\+\ze:/ contains=ClapDumbLinNr contained 3 | syntax match ClapDumbLinNrColumn /\zs:\d\+:\d\+:\ze/ contains=ClapDumbLinNr,ClapDumbColumn contained 4 | 5 | syntax match ClapDumbKind /^\[\a*\]/ contained 6 | 7 | syntax match ClapDumbFpath /^.*:\d\+:\d\+:/ contains=ClapDumbLinNrColumn,ClapDumbKind 8 | 9 | hi default link ClapDumbFpath Keyword 10 | hi default link ClapDumbKind Title 11 | hi default link ClapDumbLinNr LineNr 12 | hi default link ClapDumbColumn Comment 13 | hi default link ClapDumbLinNrColumn Type 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'feature_request' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /crates/matcher/src/algo/fzf.rs: -------------------------------------------------------------------------------- 1 | use crate::{MatchResult, Score}; 2 | use norm::fzf::{FzfParser, FzfV2}; 3 | use norm::Metric; 4 | 5 | pub fn fuzzy_indices_v2(text: &str, query: &str) -> Option { 6 | let mut fzf = FzfV2::new(); 7 | let mut parser = FzfParser::new(); 8 | let query = parser.parse(query); 9 | 10 | let mut ranges: Vec> = Vec::new(); 11 | fzf.distance_and_ranges(query, text, &mut ranges) 12 | .map(|distance| { 13 | // norm use i64 as Score, but we use i32. 14 | MatchResult::new( 15 | distance.into_score() as Score, 16 | ranges.into_iter().flatten().collect(), 17 | ) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | name: Build, Test and Deploy 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write # To push a branch 13 | pull-requests: write # To create a PR from that branch 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: stable 19 | - run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4" mdbook) 20 | - run: mdbook build docs && mdbook test docs 21 | - uses: JamesIves/github-pages-deploy-action@4.1.7 22 | with: 23 | branch: gh-pages 24 | folder: docs/book 25 | -------------------------------------------------------------------------------- /syntax/clap_buffers.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapBuffersNumberBracket /\[\|\]/ contained 2 | syntax match ClapBuffersNumber /^\[\d\+\]/ contains=ClapBuffersNumberBracket 3 | syntax match ClapBuffersFsize /\(\d\|\.\)\+\(K\|B\|G\)/ 4 | syntax match ClapBuffersLnum /line \d\+/ contained 5 | syntax match ClapBuffersExtra /\[\(+\|#\)\]/ contained 6 | syntax match ClapBuffersFname /line.*$/ contains=ClapBuffersLnum,ClapBuffersExtra 7 | 8 | hi default link ClapBuffersNumberBracket Number 9 | hi default link ClapBuffersNumber Function 10 | hi default link ClapBuffersFsize Type 11 | hi default link ClapBuffersLnum CursorLineLineNr 12 | hi default link ClapBuffersExtra SpecialChar 13 | hi default link ClapBuffersFname String 14 | -------------------------------------------------------------------------------- /crates/matcher/src/matchers/inverse_matcher.rs: -------------------------------------------------------------------------------- 1 | use types::InverseTerm; 2 | 3 | #[derive(Debug, Clone, Default)] 4 | pub struct InverseMatcher { 5 | inverse_terms: Vec, 6 | } 7 | 8 | impl InverseMatcher { 9 | pub fn new(inverse_terms: Vec) -> Self { 10 | Self { inverse_terms } 11 | } 12 | 13 | pub fn inverse_terms(&self) -> &[InverseTerm] { 14 | &self.inverse_terms 15 | } 16 | 17 | /// Returns `true` if any inverse matching is satisfied, which means the item should be 18 | /// ignored. 19 | pub fn match_any(&self, match_text: &str) -> bool { 20 | self.inverse_terms 21 | .iter() 22 | .any(|inverse_term| inverse_term.exact_matched(match_text)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /syntax/clap_tagfiles.vim: -------------------------------------------------------------------------------- 1 | setlocal conceallevel=3 2 | 3 | syntax match ClapTagfilesInfo /\v:::.*$/ conceal 4 | 5 | syntax match ClapTagfilesName /\v^.*\ze\s+\[.*\]/ contains=ClapTagfilesLnum 6 | syntax match ClapTagfilesBrackets /\[\|\]/ contained 7 | syntax match ClapTagfilesFilename /\v\f*\/\zs[^\]]+\ze/ contained 8 | syntax match ClapTagfilesFilename /\v\[@<=[^/]+\]@=/ contained 9 | syntax match ClapTagfilesPath /\[\f\+\]/ contains=ClapTagfilesBrackets,ClapTagfilesFilename 10 | 11 | hi default link ClapTagfilesName Special 12 | hi default link ClapTagfilesPath Comment 13 | hi default link ClapTagfilesBrackets Comment 14 | hi default link ClapTagfilesFilename Directory 15 | -------------------------------------------------------------------------------- /crates/maple_markdown/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "maple_markdown" 3 | authors.workspace = true 4 | version.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | publish.workspace = true 10 | 11 | [dependencies] 12 | axum = { version = "0.7.5", features = ["ws"] } 13 | axum-extra = { version = "0.9.3", features = ["typed-header"] } 14 | once_cell = { workspace = true } 15 | percent-encoding = { workspace = true } 16 | pulldown-cmark = { workspace = true } 17 | regex = { workspace = true } 18 | serde_json = { workspace = true } 19 | tokio = { workspace = true, features = ["full"] } 20 | tracing = { workspace = true } 21 | webbrowser = { workspace = true } 22 | 23 | utils = { workspace = true } 24 | -------------------------------------------------------------------------------- /crates/matcher/src/matchers/bonus_matcher/bonus/cwd.rs: -------------------------------------------------------------------------------- 1 | //! Add a bonus score for the file matching current working directory. 2 | 3 | use crate::Score; 4 | 5 | /// Used for recent_files provider. 6 | /// 7 | /// Each entry of recent_files provider is an absolute path String. 8 | #[derive(Clone, Debug)] 9 | pub struct Cwd { 10 | /// Absolute path String. 11 | pub abs_path: String, 12 | } 13 | 14 | impl From for Cwd { 15 | fn from(abs_path: String) -> Self { 16 | Self { abs_path } 17 | } 18 | } 19 | 20 | impl Cwd { 21 | pub fn calc_bonus(&self, bonus_text: &str, base_score: Score) -> Score { 22 | if bonus_text.starts_with(&self.abs_path) { 23 | base_score / 2 24 | } else { 25 | 0 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ci/build_static_binary.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROFILE=production 4 | 5 | # Install Docker 6 | sudo apt-get update 7 | sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common 8 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 9 | sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 10 | sudo apt-get update 11 | sudo apt-get install -y docker-ce docker-ce-cli containerd.io 12 | docker --version 13 | 14 | docker pull clux/muslrust 15 | docker run -v $PWD:/volume --rm -t clux/muslrust cargo build --profile $PROFILE --locked 16 | 17 | mkdir -p target/release 18 | sudo cp target/x86_64-unknown-linux-musl/$PROFILE/maple target/$PROFILE/maple 19 | 20 | ./target/$PROFILE/maple version 21 | -------------------------------------------------------------------------------- /autoload/clap/provider/recent_files.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Persistent recent files, ordered by the Mozilla's Frecency algorithm. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:recent_files = {} 8 | 9 | let s:recent_files.init = { -> clap#client#notify_on_init() } 10 | let s:recent_files.on_typed = { -> clap#client#notify_provider('on_typed') } 11 | let s:recent_files.on_move_async = { -> clap#client#notify_provider('on_move') } 12 | let s:recent_files.sink = function('clap#provider#files#sink_impl') 13 | let s:recent_files.support_open_action = v:true 14 | let s:recent_files.icon = 'File' 15 | let s:recent_files.syntax = 'clap_files' 16 | 17 | let g:clap#provider#recent_files# = s:recent_files 18 | 19 | let &cpoptions = s:save_cpo 20 | unlet s:save_cpo 21 | -------------------------------------------------------------------------------- /crates/matcher/src/algo/fzy.rs: -------------------------------------------------------------------------------- 1 | // Re-export the fzy algorithm 2 | pub use extracted_fzy::match_and_score_with_positions; 3 | 4 | use crate::{MatchResult, Score}; 5 | use extracted_fzy::CaseMatching; 6 | 7 | /// Make the arguments order same to Skim's `fuzzy_indices()`. 8 | pub fn fuzzy_indices( 9 | line: &str, 10 | query: &str, 11 | case_sensitive: types::CaseMatching, 12 | ) -> Option { 13 | let case_sensitive = match case_sensitive { 14 | types::CaseMatching::Ignore => CaseMatching::Ignore, 15 | types::CaseMatching::Respect => CaseMatching::Respect, 16 | types::CaseMatching::Smart => CaseMatching::Smart, 17 | }; 18 | match_and_score_with_positions(query, line, case_sensitive) 19 | .map(|(score, indices)| MatchResult::new(score as Score, indices)) 20 | } 21 | -------------------------------------------------------------------------------- /crates/maple/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "maple" 3 | build = "build.rs" 4 | description = "Rust backend for the vim plugin vim-clap" 5 | version = "0.1.51" 6 | include = ["/Cargo.toml", "src/*.rs"] 7 | rust-version = "1.70" 8 | authors.workspace = true 9 | edition.workspace = true 10 | license.workspace = true 11 | publish.workspace = true 12 | homepage.workspace = true 13 | 14 | [dependencies] 15 | clap = { workspace = true, features = ["derive"] } 16 | tokio = { workspace = true, features = ["rt"] } 17 | cli = { workspace = true } 18 | upgrade = { workspace = true } 19 | 20 | [target.'cfg(not(any(target_env = "msvc", target_os = "android")))'.dependencies] 21 | tikv-jemallocator = "0.6" 22 | 23 | [build-dependencies] 24 | built = { package = "built", version = "0.6", features = ["git2"] } 25 | chrono = { workspace = true } 26 | -------------------------------------------------------------------------------- /autoload/clap/provider/search_history.vim: -------------------------------------------------------------------------------- 1 | " Author: Mark Wu 2 | " Description: List the search history. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | " Derived from fzf.vim 8 | function! s:search_history_source() abort 9 | return clap#common_history#source('/') 10 | endfunction 11 | 12 | function! s:search_history_sink(selected) abort 13 | call clap#common_history#sink('/', a:selected) 14 | endfunction 15 | 16 | let s:search_history = {} 17 | let s:search_history.sink = function('s:search_history_sink') 18 | let s:search_history.source = function('s:search_history_source') 19 | let s:search_history.syntax = 'clap_command_history' 20 | let s:search_history.mode = 'quick_pick' 21 | 22 | let g:clap#provider#search_history# = s:search_history 23 | 24 | let &cpoptions = s:save_cpo 25 | unlet s:save_cpo 26 | -------------------------------------------------------------------------------- /autoload/clap/provider/command_history.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the command history. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | " Derived from fzf.vim 8 | function! s:command_history_source() abort 9 | return clap#common_history#source(':') 10 | endfunction 11 | 12 | function! s:command_history_sink(selected) abort 13 | call clap#common_history#sink(':', a:selected) 14 | endfunction 15 | 16 | let s:command_history = {} 17 | let s:command_history.sink = function('s:command_history_sink') 18 | let s:command_history.source = function('s:command_history_source') 19 | let s:command_history.syntax = 'clap_command_history' 20 | let s:command_history.mode = 'quick_pick' 21 | 22 | let g:clap#provider#command_history# = s:command_history 23 | 24 | let &cpoptions = s:save_cpo 25 | unlet s:save_cpo 26 | -------------------------------------------------------------------------------- /crates/matcher/src/algo/skim.rs: -------------------------------------------------------------------------------- 1 | use crate::MatchResult; 2 | use fuzzy_matcher::skim::SkimMatcherV2; 3 | use fuzzy_matcher::FuzzyMatcher; 4 | use types::{CaseMatching, Score}; 5 | 6 | // TODO: do not have to create an instance of SkimMatcherV2 each time. 7 | #[inline] 8 | pub fn fuzzy_indices(text: &str, query: &str, case_matching: CaseMatching) -> Option { 9 | let skim_matcher = SkimMatcherV2::default(); 10 | let skim_matcher = match case_matching { 11 | CaseMatching::Ignore => skim_matcher.ignore_case(), 12 | CaseMatching::Respect => skim_matcher.respect_case(), 13 | CaseMatching::Smart => skim_matcher.smart_case(), 14 | }; 15 | // skim uses i64 as Score, but we use i32. 16 | skim_matcher 17 | .fuzzy_indices(text, query) 18 | .map(|(score, indices)| MatchResult::new(score as Score, indices)) 19 | } 20 | -------------------------------------------------------------------------------- /pythonx/clap/fuzzymatch-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzzymatch-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Liu-Cheng Xu "] 6 | homepage = "https://github.com/liuchengxu/vim-clap" 7 | description = "Speeding up Python using dynamic module written in Rust" 8 | license = "MIT" 9 | publish = false 10 | 11 | [lib] 12 | name = "fuzzymatch_rs" 13 | crate-type = ["cdylib"] 14 | 15 | [dependencies] 16 | pyo3 = { version = "0.23", features = ["auto-initialize"] } 17 | 18 | printer = { path = "../../../crates/printer" } 19 | matcher = { path = "../../../crates/matcher" } 20 | types = { path = "../../../crates/types" } 21 | 22 | # https://github.com/PyO3/pyo3/issues/340 23 | # For running PyO3 test using `cargo test --no-default-features` 24 | [features] 25 | default = ["extension-module"] 26 | extension-module = ["pyo3/extension-module"] 27 | -------------------------------------------------------------------------------- /syntax/clap_proj_tags.vim: -------------------------------------------------------------------------------- 1 | syntax match ClapProjTagLnum /:\zs\d\+/ contained 2 | syntax match ClapProjTagName /^\(.*\):\d\+.*\ze\[.*@.*\]/ contains=ClapProjTagLnum 3 | syntax match ClapProjTagKindPathSeperator /@/ contained 4 | syntax match ClapProjTagBrackets /\[\|\]/ contained 5 | syntax match ClapProjTagKind /\[\zs.*\ze@\f*\]/ contained 6 | syntax match ClapProjTagPath /\[.*@\f*\]/ contains=ClapProjTagKind,ClapProjTagKindPathSeperator 7 | syntax match ClapProjTagPattern /^.*$/ contains=ClapTagName,ClapProjTagKind,ClapProjTagPath,ClapProjTagLnum 8 | 9 | hi default link ClapProjTagName Type 10 | hi default link ClapProjTagKind Function 11 | hi default link ClapProjTagPath Directory 12 | hi default link ClapProjTagPattern Identifier 13 | hi default link ClapProjTagKindPathSeperator String 14 | hi default link ClapProjTagLnum Number 15 | hi default link ClapProjTagBrackets Comment 16 | -------------------------------------------------------------------------------- /crates/code_tools/src/analyzer/keywords/golang.rs: -------------------------------------------------------------------------------- 1 | use super::KeywordPriority; 2 | 3 | pub struct Golang; 4 | 5 | impl KeywordPriority for Golang { 6 | const DEFINITION: &'static [&'static str] = &[ 7 | "enum", 8 | "interface", 9 | "struct", 10 | "func", 11 | "const", 12 | "type", 13 | "package", 14 | ]; 15 | 16 | const REFERENCE: &'static [&'static str] = &["import"]; 17 | 18 | const STATEMENT: &'static [&'static str] = &[ 19 | "break", 20 | "case", 21 | "chan", 22 | "continue", 23 | "default", 24 | "defer", 25 | "else", 26 | "fallthrough", 27 | "for", 28 | "go", 29 | "goto", 30 | "if", 31 | "map", 32 | "range", 33 | "return", 34 | "select", 35 | "switch", 36 | "var", 37 | ]; 38 | } 39 | -------------------------------------------------------------------------------- /syntax/clap_command.vim: -------------------------------------------------------------------------------- 1 | 2 | syntax match ClapCommand /\v^\s+\S+\s+\S+\s+/ contains=ClapCommandArgs,ClapCommandArgsNone 3 | syntax match ClapCommandArgs /\v\[[1?*+]\]\s+/ contained nextgroup=ClapCommandName 4 | syntax match ClapCommandArgsNone /\v\[0\]\s+/ contained nextgroup=ClapCommandNameI 5 | syntax match ClapCommandName /\v\u\w*/ contained skipwhite nextgroup=ClapCommandRest 6 | syntax match ClapCommandNameI /\v\u\w*/ contained skipwhite nextgroup=ClapCommandRest 7 | syntax match ClapCommandRest /\v.+$/ contained skipwhite 8 | 9 | hi default link ClapCommandArgs Keyword 10 | hi default link ClapCommandArgsNone Keyword 11 | hi default link ClapCommandName ModeMsg 12 | hi default link ClapCommandNameI WarningMsg 13 | hi default link ClapCommandRest Comment 14 | -------------------------------------------------------------------------------- /crates/code_tools/src/analyzer/keywords/mod.rs: -------------------------------------------------------------------------------- 1 | mod erlang; 2 | mod golang; 3 | mod rust; 4 | mod viml; 5 | 6 | pub use erlang::Erlang; 7 | pub use golang::Golang; 8 | pub use rust::Rust; 9 | pub use viml::Viml; 10 | 11 | pub trait KeywordPriority { 12 | /// Definition/Decleration keywords. 13 | const DEFINITION: &'static [&'static str]; 14 | 15 | /// Dummy reference keywords. 16 | const REFERENCE: &'static [&'static str]; 17 | 18 | /// Keywords for simple & compund statement. 19 | const STATEMENT: &'static [&'static str]; 20 | 21 | fn keyword_priority(token: &str) -> Option { 22 | if Self::DEFINITION.contains(&token) { 23 | Some(4) 24 | } else if Self::REFERENCE.contains(&token) { 25 | Some(6) 26 | } else if Self::STATEMENT.contains(&token) { 27 | Some(8) 28 | } else { 29 | None 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pwsh 2 | 3 | $version = 'v0.55' 4 | $APP = 'maple' 5 | $url = "https://github.com/liuchengxu/vim-clap/releases/download/$version/$APP-" 6 | $output = "$PSScriptRoot\bin\$APP.exe" 7 | 8 | [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 9 | 10 | if ([Environment]::Is64BitOperatingSystem) { 11 | $url += 'x86_64-pc-windows-msvc' 12 | } else { 13 | echo 'No prebuilt maple binary for 32-bit Windows system' 14 | Exit 1 15 | } 16 | 17 | if (Test-Path -LiteralPath $output) { 18 | Remove-Item -Force -LiteralPath $output 19 | } 20 | 21 | echo "Downloading $url, please wait a second......" 22 | 23 | $start_time = Get-Date 24 | 25 | (New-Object System.Net.WebClient).DownloadFile($url, $output) 26 | 27 | Write-Output "Download the maple binary successfully, time taken: $((Get-Date).Subtract($start_time).Seconds) second(s)" 28 | -------------------------------------------------------------------------------- /crates/maple_core/src/previewer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod text_file; 2 | pub mod vim_help; 3 | 4 | /// Truncates the lines that are awfully long as vim can not handle them properly. 5 | /// 6 | /// Ref https://github.com/liuchengxu/vim-clap/issues/543 7 | pub fn truncate_lines( 8 | lines: impl Iterator, 9 | max_width: usize, 10 | ) -> impl Iterator { 11 | lines.map(move |line| { 12 | if line.len() > max_width { 13 | let mut line = line; 14 | // https://github.com/liuchengxu/vim-clap/pull/544#discussion_r506281014 15 | let replace_start = (0..max_width + 1) 16 | .rev() 17 | .find(|idx| line.is_char_boundary(*idx)) 18 | .unwrap_or_default(); // truncate to 0 19 | line.replace_range(replace_start.., "……"); 20 | line 21 | } else { 22 | line 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /autoload/clap/provider/grep.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Grep on the fly using vim-clap fuzzy matcher. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:grep = {} 8 | 9 | function! s:grep.init() abort 10 | let cwd = clap#rooter#working_dir() 11 | call clap#client#notify_on_init({'cwd': cwd}) 12 | endfunction 13 | 14 | let s:grep.sink = g:clap#provider#live_grep#.sink 15 | let s:grep['sink*'] = g:clap#provider#live_grep#['sink*'] 16 | let s:grep.on_move = g:clap#provider#live_grep#.on_move 17 | let s:grep.on_move_async = function('clap#impl#on_move#async') 18 | let s:grep.on_typed = { -> clap#client#notify_provider('on_typed') } 19 | let s:grep.enable_rooter = v:true 20 | let s:grep.support_open_action = v:true 21 | let s:grep.icon = 'Grep' 22 | let s:grep.syntax = 'clap_grep' 23 | 24 | let g:clap#provider#grep# = s:grep 25 | 26 | let &cpoptions = s:save_cpo 27 | unlet s:save_cpo 28 | -------------------------------------------------------------------------------- /autoload/clap/provider/git_files.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the files managed by git. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:git_files = {} 8 | 9 | if executable('git') 10 | let s:git_files.source = 'git ls-files '.(has('win32') ? '' : ' | uniq') 11 | else 12 | let s:git_files.source = ['git executable not found'] 13 | endif 14 | 15 | let s:git_files.sink = function('clap#provider#files#sink_impl') 16 | let s:git_files['sink*'] = function('clap#provider#files#sink_star_impl') 17 | let s:git_files.on_move = function('clap#provider#files#on_move_impl') 18 | let s:git_files.on_move_async = function('clap#impl#on_move#async') 19 | let s:git_files.icon = 'File' 20 | let s:git_files.syntax = 'clap_files' 21 | let s:git_files.enable_rooter = v:true 22 | let s:git_files.support_open_action = v:true 23 | 24 | let g:clap#provider#git_files# = s:git_files 25 | 26 | let &cpoptions = s:save_cpo 27 | unlet s:save_cpo 28 | -------------------------------------------------------------------------------- /crates/matcher/src/matchers/bonus_matcher/bonus/filename.rs: -------------------------------------------------------------------------------- 1 | use types::Score; 2 | 3 | /// Returns a bonus score if the match indices of an item include the file name part. 4 | /// 5 | /// Formula: 6 | /// bonus_score = base_score * len(matched_elements_in_file_name) / len(file_name) 7 | pub(crate) fn calc_bonus_file_name(file_path: &str, score: Score, indices: &[usize]) -> Score { 8 | match pattern::extract_file_name(file_path) { 9 | Some((file_name, idx)) if !file_name.is_empty() => { 10 | let hits_in_file_name = indices.iter().filter(|x| **x >= idx).count(); 11 | score * hits_in_file_name as Score / file_name.len() as Score 12 | } 13 | _ => 0, 14 | } 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | use super::*; 20 | 21 | #[test] 22 | fn test_calc_bonus_file_name() { 23 | let bonus_score = calc_bonus_file_name("autoload/clap/action.vim", 10, &[1, 2, 3, 20, 25]); 24 | assert_eq!(bonus_score, 2); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /syntax/clap_grep.vim: -------------------------------------------------------------------------------- 1 | " No usual did_ftplugin_loaded check 2 | scriptencoding utf-8 3 | 4 | syntax case ignore 5 | 6 | execute 'syntax match GrepFile' '/^\f*[^:]*/' 'nextgroup=GrepSeparator1' 'contains='.join(clap#icon#add_head_hl_groups(), ',') 7 | syntax match GrepSeparator1 /:/ contained nextgroup=GrepLineNr 8 | syntax match GrepLineNr /\d\+/ contained nextgroup=GrepSeparator2 9 | syntax match GrepSeparator2 /:/ contained nextgroup=GrepColumnNr 10 | syntax match GrepColumnNr /\d\+/ contained nextgroup=GrepSeparator3 11 | syntax match GrepSeparator3 /:/ contained nextgroup=GrepPattern 12 | syntax match GrepPattern /.*/ contained 13 | 14 | hi default link GrepFile Keyword 15 | hi default link GrepSeperator1 Label 16 | hi default link GrepSeperator2 Label 17 | hi default link GrepSeperator3 Label 18 | hi default link GrepLineNr Character 19 | hi default link GrepColumnNr Type 20 | hi default link GrepPattern Normal 21 | -------------------------------------------------------------------------------- /crates/maple_core/src/tools/gtags/mod.rs: -------------------------------------------------------------------------------- 1 | use dirs::Dirs; 2 | use once_cell::sync::Lazy; 3 | use std::path::PathBuf; 4 | 5 | pub static GTAGS_EXISTS: Lazy = Lazy::new(|| gtags_executable_exists().unwrap_or(false)); 6 | 7 | /// Directory for `GTAGS`/`GRTAGS`. 8 | pub static GTAGS_DIR: Lazy = Lazy::new(|| { 9 | let gtags_dir = Dirs::project().data_dir().join("gtags"); 10 | 11 | std::fs::create_dir_all(>ags_dir).expect("Couldn't create gtags directory for vim-clap"); 12 | 13 | gtags_dir 14 | }); 15 | 16 | fn gtags_executable_exists() -> std::io::Result { 17 | let output = std::process::Command::new("gtags") 18 | .arg("--version") 19 | .stderr(std::process::Stdio::inherit()) 20 | .output()?; 21 | let stdout = String::from_utf8_lossy(&output.stdout); 22 | if let Some(line) = stdout.split('\n').next() { 23 | Ok(line.starts_with("gtags")) 24 | } else { 25 | Err(std::io::Error::other("gtags executable not found")) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/matcher/src/matchers/bonus_matcher/bonus/language.rs: -------------------------------------------------------------------------------- 1 | //! Add a bonus to the comment line or the line that can have a declaration. 2 | //! 3 | //! Ref: https://github.com/jacktasia/dumb-jump/blob/master/dumb-jump.el 4 | 5 | use crate::Score; 6 | 7 | pub type FileExtension = String; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct Language(FileExtension); 11 | 12 | impl> From for Language { 13 | fn from(s: T) -> Self { 14 | Self(s.as_ref().into()) 15 | } 16 | } 17 | 18 | impl Language { 19 | pub fn calc_bonus(&self, bonus_text: &str, base_score: Score) -> Score { 20 | let trimmed = bonus_text.trim_start(); 21 | 22 | if code_tools::language::is_comment(trimmed, &self.0) { 23 | -(base_score / 5) 24 | } else { 25 | match code_tools::analyzer::calculate_pattern_priority(trimmed, &self.0) { 26 | Some(priority) => base_score / priority.as_i32(), 27 | None => 0, 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /autoload/clap/rpc.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Abstracted RPC interfaces. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | function! clap#rpc#request(id, method, params) abort 8 | call clap#job#daemon#send_raw(json_encode({ 9 | \ 'id': a:id, 10 | \ 'method': a:method, 11 | \ 'params': a:params, 12 | \ })) 13 | endfunction 14 | 15 | function! clap#rpc#notify(method, params) abort 16 | call clap#job#daemon#send_raw(json_encode({ 17 | \ 'method': a:method, 18 | \ 'params': a:params, 19 | \ })) 20 | endfunction 21 | 22 | function! clap#rpc#send_ok_response(id, result) abort 23 | call clap#job#daemon#send_raw(json_encode({ 'id': a:id, 'result': a:result })) 24 | endfunction 25 | 26 | function! clap#rpc#send_error_response(id, error_msg) abort 27 | call clap#job#daemon#send_raw(json_encode({ 'id': a:id, 'error': {'code': -32603, 'message': a:error_msg }})) 28 | endfunction 29 | 30 | let &cpoptions = s:save_cpo 31 | unlet s:save_cpo 32 | -------------------------------------------------------------------------------- /pythonx/clap/Makefile: -------------------------------------------------------------------------------- 1 | OS := $(shell uname -s | tr A-Z a-z) 2 | 3 | LIB := fuzzymatch_rs 4 | 5 | help: 6 | @echo "usage: make [OPTIONS]" 7 | @echo " help Show this message" 8 | @echo " test Run the fuzzy match benchmark using pytest" 9 | @echo " build Build the Rust extension and copy to the right place" 10 | 11 | tools: 12 | pip3 install pytest pytest-benchmark 13 | 14 | test: 15 | python3 -m pytest test_fzy_with_rust.py 16 | 17 | run-cargo: 18 | @echo "\033[1;34m==>\033[0m Trying to build rust extension"; \ 19 | cd fuzzymatch-rs; \ 20 | cargo build --release 21 | 22 | ifeq ($(OS),darwin) 23 | move-so: run-cargo 24 | cp -f fuzzymatch-rs/target/release/lib$(LIB).dylib $(LIB).so 25 | else 26 | move-so: run-cargo 27 | cp -f fuzzymatch-rs/target/release/lib$(LIB).so $(LIB).so 28 | endif 29 | 30 | post-check: 31 | @python3 -c 'import fuzzymatch_rs' >/dev/null && echo 'Build successfully!' || echo 'Build failed!' 32 | 33 | build: move-so post-check 34 | 35 | .PHONY: help test build tools run-cargo move-so post-check 36 | -------------------------------------------------------------------------------- /autoload/clap/sink.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Utilities for sink function. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | function! clap#sink#edit_with_open_action(fpath) abort 8 | if has_key(g:clap, 'open_action') 9 | execute g:clap.open_action a:fpath 10 | else 11 | " Cannot use noautocmd here as it would lose syntax, and ... 12 | execute 'edit' fnameescape(a:fpath) 13 | endif 14 | endfunction 15 | 16 | function! clap#sink#open_file(fpath, lnum, col) abort 17 | normal! m' 18 | call clap#sink#edit_with_open_action(a:fpath) 19 | noautocmd call cursor(a:lnum, a:col) 20 | normal! zz 21 | endfunction 22 | 23 | function! clap#sink#open_quickfix(qf_entries) abort 24 | let entries_len = len(a:qf_entries) 25 | call setqflist(a:qf_entries) 26 | " If there are only a few items, open the qf window at exact size. 27 | if entries_len < 15 28 | execute 'copen' entries_len 29 | else 30 | copen 31 | endif 32 | cc 33 | endfunction 34 | 35 | let &cpoptions = s:save_cpo 36 | unlet s:save_cpo 37 | -------------------------------------------------------------------------------- /autoload/clap/common_history.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Common utilities for command/search history. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | " Derived from fzf.vim 8 | function! s:get_history_list(type) abort 9 | let max = histnr(a:type) 10 | let fmt = ' %'.len(string(max)).'d ' 11 | let list = filter(map(range(1, max), 'histget("'. a:type .'", - v:val)'), '!empty(v:val)') 12 | return list 13 | endfunction 14 | 15 | function! clap#common_history#source(type) abort 16 | let hist_list = s:get_history_list(a:type) 17 | let hist_len = len(hist_list) 18 | return map(hist_list, 'printf("%4d", hist_len - v:key)." ".v:val') 19 | endfunction 20 | 21 | function! clap#common_history#sink(type, selected) abort 22 | let item = matchstr(a:selected, '\d\+\s\+\zs\(.*\)') 23 | if a:type ==# ':' 24 | call histadd(':', item) 25 | call execute(item, '') 26 | elseif a:type ==# '/' 27 | let @/ = item 28 | normal! n 29 | endif 30 | endfunction 31 | 32 | let &cpoptions = s:save_cpo 33 | unlet s:save_cpo 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [![CI](https://github.com/liuchengxu/vim-clap/workflows/ci/badge.svg)](https://github.com/liuchengxu/vim-clap/actions?workflow=ci) 6 | [![Gitter][g1]][g2] 7 | 8 | [g1]: https://badges.gitter.im/liuchengxu/vim-clap.svg 9 | [g2]: https://gitter.im/liuchengxu/vim-clap?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge 10 | 11 | Vim-clap stands as a comprehensive and efficient solution, providing powerful fuzzy pickers and replacements for various established Vim plugins, designed to support both Vim and NeoVim. 12 | 13 |

14 | 15 |

16 | 17 | [User facing documentation and guide](https://liuchengxu.github.io/vim-clap) 18 | 19 | ## Contribution 20 | 21 | Vim-clap is still in beta. Any kinds of contributions are highly welcome. 22 | 23 | ## [License](LICENSE) 24 | 25 | MIT 26 | -------------------------------------------------------------------------------- /autoload/clap/legacy/highlighter.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Functions for adding highlights to the display window. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:default_priority = 10 8 | 9 | " Add highlight for the substring matches using `matchadd()`. 10 | function! clap#legacy#highlighter#highlight_substring(patterns) abort 11 | let w:clap_match_ids = [] 12 | " Clap grep 13 | " \{ -> E888 14 | try 15 | call add(w:clap_match_ids, matchadd('ClapMatches', a:patterns[0], s:default_priority)) 16 | catch 17 | " Sometimes we may run into some pattern errors in that the query is not a 18 | " valid vim pattern. Just ignore them as the highlight is not critical, we 19 | " care more about the searched results IMO. 20 | return 21 | endtry 22 | 23 | " As most 8 submatches, ClapMatches[1-8] 24 | try 25 | call map(a:patterns[1:8], 'add(w:clap_match_ids, matchadd("ClapMatches".(v:key+1), v:val, s:default_priority - 1))') 26 | catch 27 | return 28 | endtry 29 | endfunction 30 | 31 | let &cpoptions = s:save_cpo 32 | unlet s:save_cpo 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Liu-Cheng Xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /crates/icon/exactmatch_map.json: -------------------------------------------------------------------------------- 1 | { 2 | ".bashprofile": "\ue615", 3 | ".bashrc": "\ue615", 4 | ".ds_store": "\ue615", 5 | ".editorconfig": "\ue615", 6 | ".gitconfig": "\ue615", 7 | ".gitignore": "\ue615", 8 | ".gitlab-ci.yml": "\uf296", 9 | ".gvimrc": "\ue62b", 10 | ".vimrc": "\ue62b", 11 | ".zshrc": "\ue615", 12 | "_gvimrc": "\ue62b", 13 | "_vimrc": "\ue62b", 14 | "docker-compose.yml": "\ue7b0", 15 | "dockerfile": "\ue7b0", 16 | "dropbox": "\ue707", 17 | "favicon.ico": "\ue623", 18 | "gemfile": "\ue791", 19 | "go.mod": "\ue627", 20 | "go.sum": "\ue627", 21 | "gruntfile.coffee": "\ue611", 22 | "gruntfile.js": "\ue611", 23 | "gruntfile.ls": "\ue611", 24 | "gulpfile.coffee": "\ue610", 25 | "gulpfile.js": "\ue610", 26 | "gulpfile.ls": "\ue610", 27 | "license": "\ue60a", 28 | "license-gpl3": "\ue60a", 29 | "header-gpl3": "\ue60a", 30 | "license-apache2": "\ue60a", 31 | "header-apache2": "\ue60a", 32 | "makefile": "\ue615", 33 | "mix.lock": "\ue62d", 34 | "node_modules": "\ue718", 35 | "procfile": "\ue607", 36 | "react.jsx": "\ue625", 37 | "readme": "\uf15c", 38 | "rust-toolchain": "\ue7a8" 39 | } 40 | -------------------------------------------------------------------------------- /crates/maple_core/src/find_usages/search_engine/ctags/kinds.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::OnceCell; 2 | use std::collections::HashMap; 3 | 4 | fn rs_kind_alias() -> HashMap<&'static str, &'static str> { 5 | HashMap::from([ 6 | ("module", "mod"), 7 | ("typedef", "type"), 8 | ("function", "fn"), 9 | ("interface", "trait"), 10 | ("enumerator", "enum"), 11 | ("implementation", "impl"), 12 | ]) 13 | } 14 | 15 | fn get_kind_alias<'a>(extension: &'a str, kind: &'a str) -> Option<&'a &'static str> { 16 | static KIND_MAP: OnceCell>> = OnceCell::new(); 17 | 18 | let map = KIND_MAP.get_or_init(|| HashMap::from([("rs", rs_kind_alias())])); 19 | 20 | map.get(extension).and_then(|m| m.get(kind)) 21 | } 22 | 23 | /// Returns the compact kind given the original form. 24 | /// 25 | /// Make the kind field shorter to save more spaces for the other fields. 26 | pub fn compact_kind(maybe_extension: Option<&str>, kind: &str) -> String { 27 | maybe_extension 28 | .and_then(|extension| get_kind_alias(extension, kind)) 29 | .unwrap_or(&kind) 30 | .to_string() 31 | } 32 | -------------------------------------------------------------------------------- /crates/cli/src/command/helptags.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use maple_core::helptags::generate_tag_lines; 4 | use maple_core::paths::AbsPathBuf; 5 | use std::io::Write; 6 | use utils::io::read_lines; 7 | 8 | /// Parse and display Vim helptags. 9 | #[derive(Parser, Debug, Clone)] 10 | pub struct Helptags { 11 | /// Tempfile containing the info of vim helptags. 12 | #[clap(index = 1)] 13 | meta_info: AbsPathBuf, 14 | } 15 | 16 | impl Helptags { 17 | pub fn run(self) -> Result<()> { 18 | let mut lines = read_lines(self.meta_info.as_ref())?; 19 | // line 1:/doc/tags,/doc/tags-cn 20 | // line 2:&runtimepath 21 | if let Some(Ok(doc_tags)) = lines.next() { 22 | if let Some(Ok(runtimepath)) = lines.next() { 23 | let lines = 24 | generate_tag_lines(doc_tags.split(',').map(|s| s.to_string()), &runtimepath); 25 | let stdout = std::io::stdout(); 26 | let mut lock = stdout.lock(); 27 | for line in lines { 28 | writeln!(lock, "{line}")?; 29 | } 30 | } 31 | } 32 | Ok(()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/dirs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use directories::{BaseDirs, ProjectDirs}; 2 | use std::path::PathBuf; 3 | use std::sync::OnceLock; 4 | 5 | pub struct Dirs; 6 | 7 | impl Dirs { 8 | /// Project directory specifically for Vim Clap. 9 | /// 10 | /// All the files created by vim-clap are stored there. 11 | pub fn project() -> &'static ProjectDirs { 12 | static CELL: OnceLock = OnceLock::new(); 13 | 14 | CELL.get_or_init(|| { 15 | ProjectDirs::from("org", "vim", "Vim Clap") 16 | .expect("Couldn't create project directory for vim-clap") 17 | }) 18 | } 19 | 20 | /// Provides access to the standard directories that the operating system uses. 21 | pub fn base() -> &'static BaseDirs { 22 | static CELL: OnceLock = OnceLock::new(); 23 | 24 | CELL.get_or_init(|| BaseDirs::new().expect("Failed to construct BaseDirs")) 25 | } 26 | 27 | /// Cache directory for Vim Clap project. 28 | pub fn clap_cache_dir() -> std::io::Result { 29 | let cache_dir = Self::project().cache_dir(); 30 | std::fs::create_dir_all(cache_dir)?; 31 | Ok(cache_dir.to_path_buf()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/code_tools/src/analyzer/keywords/rust.rs: -------------------------------------------------------------------------------- 1 | use super::KeywordPriority; 2 | 3 | pub struct Rust; 4 | 5 | impl KeywordPriority for Rust { 6 | const DEFINITION: &'static [&'static str] = &[ 7 | "enum", "trait", "struct", "fn", "const", "static", "crate", "mod", 8 | ]; 9 | 10 | const REFERENCE: &'static [&'static str] = &["use", "impl", "let"]; 11 | 12 | const STATEMENT: &'static [&'static str] = &[ 13 | "as", "break", "continue", "else", "extern", "false", "for", "if", "impl", "in", "let", 14 | "loop", "match", "move", "mut", "pub", "ref", "return", "self", "Self", "static", "super", 15 | "true", "type", "unsafe", "where", "while", 16 | ]; 17 | 18 | fn keyword_priority(token: &str) -> Option { 19 | if Self::DEFINITION.contains(&token) { 20 | Some(3) 21 | } else if Self::REFERENCE.contains(&token) { 22 | Some(6) 23 | } else if token.starts_with("pub") { 24 | Some(7) 25 | } else if Self::STATEMENT.contains(&token) { 26 | Some(8) 27 | } else if token.starts_with("[cfg") { 28 | Some(10) 29 | } else { 30 | None 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/matcher/src/matchers/bonus_matcher/mod.rs: -------------------------------------------------------------------------------- 1 | mod bonus; 2 | 3 | pub use self::bonus::Bonus; 4 | 5 | use std::sync::Arc; 6 | use types::{ClapItem, Score}; 7 | 8 | /// [`BonusMatcher`] only tweaks the match score. 9 | #[derive(Debug, Clone, Default)] 10 | pub struct BonusMatcher { 11 | bonuses: Vec, 12 | } 13 | 14 | impl BonusMatcher { 15 | pub fn new(bonuses: Vec) -> Self { 16 | Self { bonuses } 17 | } 18 | 19 | /// Returns the sum of bonus score. 20 | pub fn calc_item_bonus( 21 | &self, 22 | item: &Arc, 23 | base_score: Score, 24 | base_indices: &[usize], 25 | ) -> Score { 26 | self.bonuses 27 | .iter() 28 | .map(|b| b.item_bonus_score(item, base_score, base_indices)) 29 | .sum() 30 | } 31 | 32 | /// Returns the sum of bonus score. 33 | pub fn calc_text_bonus( 34 | &self, 35 | bonus_text: &str, 36 | base_score: Score, 37 | base_indices: &[usize], 38 | ) -> Score { 39 | self.bonuses 40 | .iter() 41 | .map(|b| b.text_bonus_score(bonus_text, base_score, base_indices)) 42 | .sum() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | mod release; 2 | mod update_release_notes; 3 | 4 | use anyhow::Result; 5 | use clap::Parser; 6 | use std::path::{Path, PathBuf}; 7 | use xshell::Shell; 8 | 9 | #[derive(Parser, Debug)] 10 | enum Cmd { 11 | /// Publish a new release on GitHub. 12 | #[clap(name = "release")] 13 | Release { 14 | #[clap(long)] 15 | dry_run: bool, 16 | }, 17 | /// Update the release notes of latest GitHub release. 18 | #[clap(name = "update-release-notes")] 19 | UpdateReleaseNotes { 20 | #[clap(long)] 21 | dry_run: bool, 22 | }, 23 | } 24 | 25 | fn main() -> Result<()> { 26 | let sh = &Shell::new()?; 27 | 28 | let project_root = project_root(); 29 | 30 | match Cmd::parse() { 31 | Cmd::Release { dry_run } => release::run(sh, dry_run, project_root)?, 32 | Cmd::UpdateReleaseNotes { dry_run } => { 33 | update_release_notes::run(sh, dry_run, project_root)?; 34 | } 35 | } 36 | 37 | Ok(()) 38 | } 39 | 40 | // ~/.vim/plugged/vim-clap 41 | fn project_root() -> PathBuf { 42 | Path::new(&env!("CARGO_MANIFEST_DIR")) 43 | .ancestors() 44 | .nth(2) 45 | .unwrap() 46 | .to_path_buf() 47 | } 48 | -------------------------------------------------------------------------------- /crates/matcher/src/algo/nucleo.rs: -------------------------------------------------------------------------------- 1 | use nucleo_matcher::pattern::{AtomKind, CaseMatching, Normalization, Pattern}; 2 | use nucleo_matcher::{Config, Matcher, Utf32Str}; 3 | use types::{MatchResult, Score}; 4 | 5 | /// Make the arguments order same to Skim's `fuzzy_indices()`. 6 | pub fn fuzzy_indices( 7 | line: &str, 8 | query: &str, 9 | case_sensitive: types::CaseMatching, 10 | ) -> Option { 11 | let mut matcher = Matcher::new(Config::DEFAULT.match_paths()); 12 | let mut indices = Vec::new(); 13 | 14 | let case_matching = match case_sensitive { 15 | types::CaseMatching::Ignore => CaseMatching::Ignore, 16 | types::CaseMatching::Respect => CaseMatching::Respect, 17 | types::CaseMatching::Smart => CaseMatching::Smart, 18 | }; 19 | 20 | let mut char_buf = Vec::new(); 21 | let haystack = Utf32Str::new(line, &mut char_buf); 22 | Pattern::new(query, case_matching, Normalization::Smart, AtomKind::Fuzzy) 23 | .indices(haystack, &mut matcher, &mut indices) 24 | .map(|score| { 25 | MatchResult::new( 26 | score as Score, 27 | indices.into_iter().map(|idx| idx as usize).collect(), 28 | ) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /autoload/clap/themes/atom_dark.vim: -------------------------------------------------------------------------------- 1 | " Author: GoldsteinE 2 | " Description: Clap theme based on the atom-dark theme. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:palette = {} 8 | 9 | let s:palette.display = { 'ctermbg': '239', 'guibg': '#2d3135', 'guifg': '#f8f8f2', 'ctermfg': '123' } 10 | 11 | " Let ClapInput, ClapSpinner and ClapSearchText use the same backgound. 12 | let s:bg0 = { 'ctermbg': '59', 'guibg': '#403d3d' } 13 | let s:palette.input = s:bg0 14 | let s:palette.spinner = extend({ 'ctermfg': '229', 'guifg':'#dad085', 'cterm': 'bold', 'gui': 'bold'}, s:bg0) 15 | let s:palette.search_text = extend({ 'ctermfg': '249', 'guifg': '#f8f8f2', 'cterm': 'bold', 'gui': 'bold' }, s:bg0) 16 | 17 | let s:palette.preview = { 'ctermbg': '238', 'guibg': '#292b2d' } 18 | 19 | let s:palette.selected = { 'ctermbg': '59', 'guibg': '#2d3a3d', 'cterm': 'bold', 'gui': 'bold' } 20 | let s:palette.selected_sign = s:palette.selected 21 | let s:palette.current_selection = { 'ctermbg': '242', 'guibg': '#334043', 'cterm': 'bold', 'gui': 'bold' } 22 | let s:palette.current_selection_sign = s:palette.current_selection 23 | 24 | let g:clap#themes#atom_dark#palette = s:palette 25 | 26 | let &cpoptions = s:save_cpo 27 | unlet s:save_cpo 28 | -------------------------------------------------------------------------------- /crates/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | publish.workspace = true 8 | homepage.workspace = true 9 | description = "CLI for vim-clap Rust backend" 10 | 11 | [dependencies] 12 | anyhow = { workspace = true } 13 | clap = { workspace = true } 14 | itertools = { workspace = true } 15 | num_cpus = { workspace = true } 16 | tokio = { workspace = true, features = ["fs", "rt", "process", "macros", "rt-multi-thread", "sync", "time"] } 17 | rayon = { workspace = true } 18 | serde = { workspace = true } 19 | serde_json = { workspace = true } 20 | subprocess = { workspace = true } 21 | tracing = { workspace = true } 22 | tracing-appender = { workspace = true } 23 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 24 | 25 | filter = { workspace = true } 26 | icon = { workspace = true } 27 | matcher = { workspace = true } 28 | maple_config = { workspace = true } 29 | maple_core = { workspace = true } 30 | printer = { workspace = true } 31 | types = { workspace = true } 32 | utils = { workspace = true } 33 | 34 | [dev-dependencies] 35 | criterion = { workspace = true } 36 | 37 | [[bench]] 38 | name = "benchmark" 39 | harness = false 40 | -------------------------------------------------------------------------------- /docs/src/plugins/intro.md: -------------------------------------------------------------------------------- 1 | # Clap Plugins 2 | 3 | > WARN: This is an experimental feature, use at your own risk! 4 | 5 | Vim-Clap was originally a mere Vim fuzzy picker plugin, however, the integration of a robust Rust backend unveiled the potential to implement various additional functionalities effortlessly, for enjoyable experimentation and potential performance enhancements. 6 | 7 | Note that the vim-Clap plugins were mainly created for the plugin author's personal uses, thus they may not be feature-complete as their alternatives. Bugs are expected as these plugins are not extensively tested, feel free to use if you are brave enough. 8 | 9 | You must enable the `g:clap_plugin_experimental` flag in your `.vimrc` and create a [config file](./config.md) beforehand. 10 | 11 | ```vim 12 | " Specify this variable to enable the plugin feature. 13 | let g:clap_plugin_experimental = v:true 14 | ``` 15 | 16 | All the non-system plugins are disabled by default. To enable the plugins, you need to set `enable = true` explicitly for the plugins in the config file. 17 | 18 | ```toml 19 | [plugin.git] 20 | enable = true 21 | ``` 22 | 23 | Check out [Available Plugins](./plugins.md) for detailed introduction to the plugins. Try `:Clap clap_actions` to take a look at the existing actions. 24 | -------------------------------------------------------------------------------- /docs/src/providers/theme.md: -------------------------------------------------------------------------------- 1 | # Theme 2 | 3 | By default vim-clap would use the colors extracted from your current colorscheme, which is not guaranteed to suitable for all the colorschemes. You can try the built-in `material_design_dark` theme if the default theme does not work well: 4 | 5 | ```vim 6 | let g:clap_theme = 'material_design_dark' 7 | ``` 8 | 9 | ![material_design_dark-theme](https://user-images.githubusercontent.com/8850248/74818883-6cfdc380-533a-11ea-81fb-d09d90498c96.png) 10 | 11 | You could also set `g:clap_theme` to be a `Dict` to specify the palette: 12 | 13 | ```vim 14 | " Change the CamelCase of related highlight group name to under_score_case. 15 | let g:clap_theme = { 'search_text': {'guifg': 'red', 'ctermfg': 'red'} } 16 | ``` 17 | 18 | `ClapDisplay` and `ClapPreview` are the most basic highlight groups for the display and preview window, which can be overridden if the provider has its own syntax highlight, then checkout the related [syntax](syntax) file for more granular highlights directly. 19 | 20 | If you are keen to explore and even want to write your own clap theme, take [autoload/clap/themes/material_design_dark.vim](../../../autoload/clap/themes/material_design_dark.vim) as a reference. 21 | 22 | See `:help clap-highlights` for more information. 23 | -------------------------------------------------------------------------------- /crates/cli/src/command/ctags/buffer_tags.rs: -------------------------------------------------------------------------------- 1 | use crate::app::Args; 2 | use anyhow::{Context, Result}; 3 | use clap::Parser; 4 | use maple_core::paths::AbsPathBuf; 5 | use maple_core::tools::ctags::{buffer_tags_lines, current_context_tag}; 6 | 7 | /// Prints the tags for a specific file. 8 | #[derive(Parser, Debug, Clone)] 9 | pub struct BufferTags { 10 | /// Show the nearest function/method to a specific line. 11 | #[clap(long)] 12 | current_context: Option, 13 | 14 | /// Use the raw output format even json output is supported, for testing purpose. 15 | #[clap(long)] 16 | force_raw: bool, 17 | 18 | #[clap(long)] 19 | file: AbsPathBuf, 20 | } 21 | 22 | impl BufferTags { 23 | pub fn run(&self, _args: Args) -> Result<()> { 24 | if let Some(at) = self.current_context { 25 | let context_tag = current_context_tag(self.file.as_path(), at) 26 | .context("Error at finding the context tag info")?; 27 | println!("Context: {context_tag:?}"); 28 | return Ok(()); 29 | } 30 | 31 | let lines = buffer_tags_lines(self.file.as_ref(), self.force_raw)?; 32 | 33 | for line in lines { 34 | println!("{line}"); 35 | } 36 | 37 | Ok(()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /crates/maple_core/src/stdio_server/provider/impls/mod.rs: -------------------------------------------------------------------------------- 1 | mod blines; 2 | mod dumb_jump; 3 | pub mod filer; 4 | mod files; 5 | mod generic_provider; 6 | mod grep; 7 | mod igrep; 8 | pub mod lsp; 9 | mod recent_files; 10 | mod tagfiles; 11 | 12 | use crate::stdio_server::provider::{ClapProvider, Context, ProviderResult}; 13 | 14 | pub async fn create_provider(ctx: &Context) -> ProviderResult> { 15 | let provider: Box = match ctx.env.provider_id.as_str() { 16 | "blines" => Box::new(blines::BlinesProvider::new(ctx).await?), 17 | "dumb_jump" => Box::new(dumb_jump::DumbJumpProvider::new(ctx).await?), 18 | "filer" => Box::new(filer::FilerProvider::new(ctx).await?), 19 | "files" => Box::new(files::FilesProvider::new(ctx).await?), 20 | "grep" => Box::new(grep::GrepProvider::new(ctx).await?), 21 | "igrep" => Box::new(igrep::IgrepProvider::new(ctx).await?), 22 | "recent_files" => Box::new(recent_files::RecentFilesProvider::new(ctx).await?), 23 | "tagfiles" => Box::new(tagfiles::TagfilesProvider::new(ctx).await?), 24 | "lsp" => Box::new(lsp::LspProvider::new(ctx)), 25 | _ => Box::new(generic_provider::GenericProvider::new(ctx).await?), 26 | }; 27 | Ok(provider) 28 | } 29 | -------------------------------------------------------------------------------- /crates/code_tools/src/formatting/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::process::Stdio; 3 | 4 | pub async fn run_cargo_fmt(workspace_root: &Path) -> std::io::Result<()> { 5 | let exit_status = tokio::process::Command::new("cargo") 6 | .arg("fmt") 7 | .arg("--all") 8 | .current_dir(workspace_root) 9 | .kill_on_drop(true) 10 | .spawn()? 11 | .wait() 12 | .await?; 13 | 14 | if exit_status.success() { 15 | Ok(()) 16 | } else { 17 | Err(std::io::Error::other(format!( 18 | "error: {:?}", 19 | exit_status.code() 20 | ))) 21 | } 22 | } 23 | 24 | pub async fn run_rustfmt(source_file: &Path, workspace_root: &Path) -> std::io::Result<()> { 25 | let exit_status = tokio::process::Command::new("rustfmt") 26 | .arg("--edition") 27 | .arg("2021") 28 | .arg(source_file) 29 | .current_dir(workspace_root) 30 | .stderr(Stdio::null()) 31 | .kill_on_drop(true) 32 | .spawn()? 33 | .wait() 34 | .await?; 35 | 36 | if exit_status.success() { 37 | Ok(()) 38 | } else { 39 | Err(std::io::Error::other(format!( 40 | "error: {:?}", 41 | exit_status.code() 42 | ))) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/cli/src/command/gtags.rs: -------------------------------------------------------------------------------- 1 | use crate::app::Args; 2 | use anyhow::Result; 3 | use clap::Parser; 4 | use maple_core::find_usages::GtagsSearcher; 5 | use maple_core::paths::AbsPathBuf; 6 | 7 | /// Fuzzy filter the current vim buffer given the query. 8 | #[derive(Parser, Debug, Clone)] 9 | pub struct Gtags { 10 | /// Initial query string 11 | #[clap(index = 1)] 12 | query: String, 13 | 14 | /// File path of current vim buffer. 15 | #[clap(index = 2)] 16 | cwd: AbsPathBuf, 17 | 18 | /// Search the reference tags. 19 | #[clap(short, long)] 20 | reference: bool, 21 | } 22 | 23 | impl Gtags { 24 | pub fn run(&self, _args: Args) -> Result<()> { 25 | let gtags_searcher = GtagsSearcher::new(self.cwd.as_ref().to_path_buf()); 26 | 27 | gtags_searcher.create_or_update_tags()?; 28 | 29 | if self.reference { 30 | for line in gtags_searcher.search_references(&self.query)? { 31 | println!("{:?}", line.grep_format_gtags("refs", &self.query, false)); 32 | } 33 | } else { 34 | for line in gtags_searcher.search_definitions(&self.query)? { 35 | println!("{:?}", line.grep_format_gtags("defs", &self.query, false)); 36 | } 37 | } 38 | 39 | Ok(()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/maple_core/src/helptags.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use utils::io::read_lines; 3 | 4 | #[inline] 5 | fn strip_trailing_slash(x: &str) -> &str { 6 | x.strip_suffix('/').unwrap_or(x) 7 | } 8 | 9 | pub fn generate_tag_lines( 10 | doc_tags: impl Iterator, 11 | runtimepath: &str, 12 | ) -> Vec { 13 | let mut lines = Vec::new(); 14 | for doc_tag in doc_tags { 15 | let tags_files = runtimepath 16 | .split(',') 17 | .map(|x| format!("{}{doc_tag}", strip_trailing_slash(x))); 18 | let mut seen = HashMap::new(); 19 | let mut v: Vec = Vec::new(); 20 | for tags_file in tags_files { 21 | if let Ok(lines) = read_lines(tags_file) { 22 | lines.for_each(|line| { 23 | if let Ok(helptag) = line { 24 | v = helptag.split('\t').map(Into::into).collect(); 25 | if !seen.contains_key(&v[0]) { 26 | seen.insert(v[0].clone(), format!("{:<60}\t{}", v[0], v[1])); 27 | } 28 | } 29 | }); 30 | } 31 | } 32 | let mut tag_lines = seen.into_values().collect::>(); 33 | tag_lines.sort(); 34 | 35 | lines.extend(tag_lines); 36 | } 37 | 38 | lines 39 | } 40 | -------------------------------------------------------------------------------- /crates/rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod jsonrpc; 2 | pub mod vim; 3 | 4 | use thiserror::Error; 5 | use tokio::sync::mpsc::error::SendError; 6 | use tokio::sync::oneshot; 7 | 8 | pub use self::jsonrpc::{ 9 | Error, ErrorCode, Failure, Id, Params, RpcMessage, RpcNotification, RpcRequest, RpcResponse, 10 | Success, Version, 11 | }; 12 | 13 | #[derive(Debug, Error)] 14 | pub enum RpcError { 15 | #[error("failed to send raw message: {0}")] 16 | SendRawMessage(#[from] SendError), 17 | #[error("failed to send call: {0}")] 18 | SendCall(#[from] SendError), 19 | #[error("failed to send request: {0}")] 20 | SendRequest(#[from] SendError<(Id, oneshot::Sender)>), 21 | #[error("failed to send response: {0:?}")] 22 | SendResponse(RpcResponse), 23 | #[error("sender is dropped: {0}")] 24 | OneshotRecv(#[from] tokio::sync::oneshot::error::RecvError), 25 | #[error(transparent)] 26 | SerdeJson(#[from] serde_json::Error), 27 | #[error(transparent)] 28 | IO(#[from] std::io::Error), 29 | #[error("request failure: {0}")] 30 | Request(String), 31 | #[error("invalid server message: {0}")] 32 | ServerMessage(String), 33 | #[error("stream closed")] 34 | StreamClosed, 35 | #[error("{0}")] 36 | DeserializeFailure(String), 37 | #[error(transparent)] 38 | JsonRpc(#[from] Error), 39 | } 40 | -------------------------------------------------------------------------------- /autoload/clap/themes/material_design_dark.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Clap theme based on the material_design_dark theme. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:palette = {} 8 | 9 | let s:palette.display = { 'ctermbg': '235', 'guibg': '#272d3D' } 10 | 11 | " Let ClapInput, ClapSpinner and ClapSearchText use the same background. 12 | let s:bg0 = { 'ctermbg': '60', 'guibg': '#3e4461' } 13 | let s:palette.input = s:bg0 14 | let s:palette.indicator = extend({ 'ctermfg': '238', 'guifg':'#676b83' }, s:bg0) 15 | let s:palette.spinner = extend({ 'ctermfg': '11', 'guifg':'#ffe500', 'cterm': 'bold', 'gui': 'bold'}, s:bg0) 16 | let s:palette.search_text = extend({ 'ctermfg': '195', 'guifg': '#CADFF3', 'cterm': 'bold', 'gui': 'bold' }, s:bg0) 17 | 18 | let s:palette.preview = { 'ctermbg': '238', 'guibg': '#363c55' } 19 | 20 | let s:palette.selected = { 'ctermfg': '81', 'guifg': '#5FD7d7', 'cterm': 'bold,underline', 'gui': 'bold,underline' } 21 | let s:palette.current_selection = { 'ctermbg': '236', 'guibg': '#31364D', 'cterm': 'bold', 'gui': 'bold' } 22 | 23 | let s:palette.selected_sign = { 'ctermfg': '196', 'guifg': '#f2241f' } 24 | let s:palette.current_selection_sign = s:palette.selected_sign 25 | 26 | let g:clap#themes#material_design_dark#palette = s:palette 27 | 28 | let &cpoptions = s:save_cpo 29 | unlet s:save_cpo 30 | -------------------------------------------------------------------------------- /syntax/clap_diff.vim: -------------------------------------------------------------------------------- 1 | syn match gitInfo /^[^0-9]*\zs[0-9-]\+\s\+[a-f0-9]\+ / contains=gitDate,gitSha nextgroup=gitMessage,gitMeta 2 | syn match gitDate /\S\+ / contained 3 | syn match gitSha /[a-f0-9]\{6,}/ contained 4 | syn match gitMessage /.* \ze(.\{-})$/ contained contains=gitTag,gitGitHub,gitJira nextgroup=gitAuthor 5 | syn match gitAuthor /.*$/ contained 6 | syn match gitMeta /([^)]\+) / contained contains=gitTag nextgroup=gitMessage 7 | syn match gitTag /(tag:[^)]\+)/ contained 8 | syn match gitGitHub /\<#[0-9]\+\>/ contained 9 | hi def link gitDate Number 10 | hi def link gitSha Identifier 11 | hi def link gitTag Constant 12 | hi def link gitGitHub Label 13 | hi def link gitJira Label 14 | hi def link gitMeta Conditional 15 | hi def link gitAuthor String 16 | 17 | syn match gitAdded "^\W*\zsA\t.*" 18 | syn match gitDeleted "^\W*\zsD\t.*" 19 | hi def link gitAdded diffAdded 20 | hi def link gitDeleted diffRemoved 21 | 22 | 23 | syn match diffAdded "^+.*" 24 | syn match diffRemoved "^-.*" 25 | syn match diffLine "^@.*" 26 | syn match diffFile "^diff\>.*" 27 | syn match diffFile "^+++ .*" 28 | syn match diffNewFile "^--- .*" 29 | hi def link diffFile Type 30 | hi def link diffNewFile diffFile 31 | hi def link diffAdded Identifier 32 | hi def link diffRemoved Special 33 | hi def link diffFile Type 34 | hi def link diffLine Statement 35 | -------------------------------------------------------------------------------- /autoload/clap/provider/jumps.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the jump list with the preview. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:jumps = {} 8 | 9 | function! s:jumps.source() abort 10 | let cout = clap#api#win_execute(g:clap.start.winid, 'jumps') 11 | let s:jumplist = split(cout, '\n') 12 | return [s:jumplist[0]] + reverse(s:jumplist[1:]) 13 | endfunction 14 | 15 | function! s:jumps.sink(line) abort 16 | if empty(a:line) 17 | return 18 | endif 19 | let idx = index(s:jumplist, a:line) 20 | if idx == -1 21 | return 22 | endif 23 | let pointer = match(s:jumplist, '\v^\s*\>') 24 | if pointer ==# a:line 25 | return 26 | endif 27 | let delta = idx - pointer 28 | let cmd = delta < 0 ? abs(delta)."\" : delta."\" 29 | execute 'normal!' cmd 30 | normal! zz 31 | endfunction 32 | 33 | function! s:jumps.on_move() abort 34 | let curline = g:clap.display.getcurline() 35 | let matched = matchlist(curline, '^\s\+\(\d\+\)\s\+\(\d\+\)\s\+\(\d\+\)\s\+\(.*\)$') 36 | if len(matched) < 5 37 | return 38 | endif 39 | call clap#provider#marks#preview_impl(matched[2], matched[3], matched[4]) 40 | endfunction 41 | 42 | " TODO: add on_move_async() 43 | 44 | let s:jumps.syntax = 'clap_jumps' 45 | let g:clap#provider#jumps# = s:jumps 46 | 47 | let &cpoptions = s:save_cpo 48 | unlet s:save_cpo 49 | -------------------------------------------------------------------------------- /crates/tree_sitter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tree_sitter" 3 | edition.workspace = true 4 | version.workspace = true 5 | 6 | [build-dependencies] 7 | cc = "*" 8 | 9 | [dependencies] 10 | once_cell = { workspace = true } 11 | serde = { workspace = true, features = [ "derive" ] } 12 | toml = { workspace = true } 13 | tracing = { workspace = true } 14 | tree-sitter-core = { package = "tree-sitter", version = "0.23" } 15 | tree-sitter-highlight = "0.23" 16 | # Languages 17 | tree-sitter-bash = "0.23" 18 | tree-sitter-c = "0.23" 19 | tree-sitter-cpp = "0.23" 20 | tree-sitter-go = "0.23" 21 | tree-sitter-javascript = "0.23" 22 | tree-sitter-json = "0.23" 23 | tree-sitter-md = "0.3.2" 24 | tree-sitter-python = "0.23" 25 | tree-sitter-rust = "0.23" 26 | tree-sitter-swift = "0.6" 27 | # tree-sitter-traversal = "0.1" 28 | 29 | # Forked languages. 30 | tree-sitter-dockerfile = { git = "https://github.com/liuchengxu/tree-sitter-dockerfile", rev = "0f648be5fd233a9be3428ec12de66a8be2dce0b6" } 31 | tree-sitter-toml = { git = "https://github.com/liuchengxu/tree-sitter-toml", rev = "8be356ed7b18541808d4ed5ca379834f0e0e94b4" } 32 | tree-sitter-vim = { git = "https://github.com/liuchengxu/tree-sitter-vim", rev = "a0abc5f3dd20cdc61c1a18136f9ed559d87b9a00" } 33 | 34 | [dev-dependencies] 35 | criterion = { workspace = true } 36 | rand = "0.8" 37 | tree-sitter-tags = "0.23" 38 | 39 | [[bench]] 40 | name = "benchmark" 41 | harness = false 42 | -------------------------------------------------------------------------------- /autoload/clap/provider/history.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the open buffers and oldfiles in order. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:history = {} 8 | 9 | function! s:raw_history() abort 10 | let history = uniq(map( 11 | \ filter([expand('%')], 'len(v:val)') 12 | \ + filter(map(clap#util#buflisted_sorted(v:false), 'bufname(v:val)'), 'len(v:val)') 13 | \ + filter(copy(v:oldfiles), "filereadable(fnamemodify(v:val, ':p'))"), 14 | \ 'fnamemodify(v:val, ":~:.")')) 15 | if exists('*g:ClapProviderHistoryCustomFilter') 16 | return filter(history, 'g:ClapProviderHistoryCustomFilter(v:val)') 17 | else 18 | return history 19 | endif 20 | endfunction 21 | 22 | function! s:history_sink(selected) abort 23 | let fpath = g:clap_enable_icon ? a:selected[4:] : a:selected 24 | call clap#sink#edit_with_open_action(fpath) 25 | endfunction 26 | 27 | let s:history.icon = 'File' 28 | let s:history.syntax = 'clap_files' 29 | let s:history.sink = function('s:history_sink') 30 | let s:history.on_move = function('clap#provider#files#on_move_impl') 31 | let s:history.on_move_async = function('clap#impl#on_move#async') 32 | let s:history.source = function('s:raw_history') 33 | let s:history.support_open_action = v:true 34 | 35 | let g:clap#provider#history# = s:history 36 | 37 | let &cpoptions = s:save_cpo 38 | unlet s:save_cpo 39 | -------------------------------------------------------------------------------- /docs/src/providers/search_syntax.md: -------------------------------------------------------------------------------- 1 | # Search Syntax 2 | 3 | ## Fzf search syntax 4 | 5 | vim-clap adopts most of [fzf search syntax](https://github.com/junegunn/fzf#search-syntax). Note that the OR operator defined by a single bar character is unsupported, but you can achieve that by using multiple exact matches. 6 | 7 | | Token | Match type | Description | 8 | | ------ | ---------- | ---------------------------------- | 9 | | `sbtrkt` | fuzzy-match | Items that match sbtrkt | 10 | | `'wild` | exact-match (quoted) | Items that include wild | 11 | | `^music` | prefix-exact-match | Items that start with music | 12 | | `.mp3$` | suffix-exact-match | Items that end with .mp3 | 13 | | `!fire` | inverse-exact-match | Items that do not include fire | 14 | | `!^music` | inverse-prefix-exact-match | Items that do not start with music | 15 | | `!.mp3$` | inverse-suffix-exact-match | Items that do not end with .mp3 | 16 | 17 | 18 | ### Extended search syntax 19 | 20 | Apart from the basic fzf search syntax, more search syntax are supported: 21 | 22 | | Token | Match type | Description | 23 | | ------ | ---------- | ------------------------------------------------------------ | 24 | | `"cli` | word-match | Items that match word `cli` (`clippy` does not match `"cli`) | 25 | -------------------------------------------------------------------------------- /autoload/clap/provider/igrep.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Grep using the filer-like interface. 3 | 4 | scriptencoding utf-8 5 | 6 | let s:save_cpo = &cpoptions 7 | set cpoptions&vim 8 | 9 | let s:igrep = {} 10 | 11 | let s:CREATE_FILE = ' [Create new file]' 12 | 13 | function! s:igrep.on_move_async() abort 14 | if stridx(g:clap.display.getcurline(), s:CREATE_FILE) > -1 15 | call g:clap.preview.hide() 16 | return 17 | endif 18 | call clap#client#notify_provider('on_move') 19 | endfunction 20 | 21 | function! s:start_rpc_service() abort 22 | let current_dir = clap#file_explorer#init_current_dir() 23 | call clap#file_explorer#set_prompt(current_dir, winwidth(g:clap.display.winid)) 24 | call clap#client#notify_on_init({'cwd': current_dir}) 25 | endfunction 26 | 27 | let s:igrep.init = function('s:start_rpc_service') 28 | let s:igrep.icon = 'File' 29 | let s:igrep.syntax = 'clap_grep' 30 | let s:igrep.source_type = g:__t_rpc 31 | let s:igrep.on_typed = { -> clap#client#notify_provider('on_typed') } 32 | let s:igrep.mappings = { 33 | \ "": { -> clap#client#notify_provider('cr') }, 34 | \ "": { -> clap#client#notify_provider('backspace') }, 35 | \ "": { -> clap#client#notify_provider('tab') }, 36 | \ "": { -> clap#client#notify_provider('backspace') }, 37 | \ } 38 | let g:clap#provider#igrep# = s:igrep 39 | 40 | let &cpoptions = s:save_cpo 41 | unlet s:save_cpo 42 | -------------------------------------------------------------------------------- /crates/maple_derive/tests/tests.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use types::AutocmdEventType; 3 | 4 | #[async_trait::async_trait] 5 | trait Plugin { 6 | fn subscriptions(&self) -> &[AutocmdEventType] { 7 | &[] 8 | } 9 | 10 | async fn handle_autocmd(&self, event_type: AutocmdEventType); 11 | } 12 | 13 | struct TestSubscriptions; 14 | 15 | #[async_trait::async_trait] 16 | impl Plugin for TestSubscriptions { 17 | #[maple_derive::subscriptions] 18 | async fn handle_autocmd(&self, event_type: AutocmdEventType) { 19 | use AutocmdEventType::{BufEnter, BufLeave, CursorMoved, InsertEnter}; 20 | 21 | match event_type { 22 | BufEnter | BufLeave => {} 23 | CursorMoved => {} 24 | InsertEnter if true => {} 25 | _unknown => {} 26 | } 27 | } 28 | } 29 | 30 | struct NoSubscriptions; 31 | 32 | #[async_trait::async_trait] 33 | impl Plugin for NoSubscriptions { 34 | #[maple_derive::subscriptions] 35 | async fn handle_autocmd(&self, _event_type: AutocmdEventType) {} 36 | } 37 | 38 | #[test] 39 | fn test_subscriptions_macro() { 40 | assert_eq!( 41 | TestSubscriptions.subscriptions(), 42 | &[ 43 | AutocmdEventType::BufEnter, 44 | AutocmdEventType::BufLeave, 45 | AutocmdEventType::CursorMoved, 46 | AutocmdEventType::InsertEnter 47 | ] 48 | ); 49 | 50 | assert_eq!(NoSubscriptions.subscriptions(), &[]); 51 | } 52 | -------------------------------------------------------------------------------- /docs/src/guide/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Requirement 4 | 5 | - Vim: `:echo has('patch-8.1.2114')`. 6 | - NeoVim: `:echo has('nvim-0.4.2')`. 7 | 8 | ## Installation 9 | 10 | ### [vim-plug](https://github.com/junegunn/vim-plug) 11 | 12 | ```vim 13 | " Build the Rust binary if `cargo` exists on your system. 14 | Plug 'liuchengxu/vim-clap', { 'do': ':Clap install-binary' } 15 | 16 | " The bang version will try to download the prebuilt binary if `cargo` does not exist. 17 | Plug 'liuchengxu/vim-clap', { 'do': ':Clap install-binary!' } 18 | 19 | " `:Clap install-binary[!]` will always try to compile the binary locally. 20 | " If you do care about the disk used for the compilation, use the way of force download, 21 | " which will directly download the prebuilt binary even if `cargo` is available. 22 | Plug 'liuchengxu/vim-clap', { 'do': { -> clap#installer#force_download() } } 23 | 24 | " `:Clap install-binary[!]` will run using the terminal feature which is inherently async. 25 | " If you don't want that and hope to run the hook synchorously: 26 | Plug 'liuchengxu/vim-clap', { 'do': has('win32') ? 'cargo build --release' : 'make' } 27 | ``` 28 | 29 | Employing the `do` hook of the Vim plugin manager typically facilitates the automatic installation of the additional Rust binary, offering a convenient and recommended solution. However, if this process encounters any issues, manual compilation of the Rust dependency is required, as outlined in [the subsequent section](./install_rust.md). 30 | -------------------------------------------------------------------------------- /autoload/clap/ext.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Get filetype based on the fname's extension. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | " This is not complete, but should be enough to cover the most extensions. 8 | " https://vi.stackexchange.com/questions/9962/get-filetype-by-extension-or-filename-in-vimscript 9 | " 10 | " This function can takes 0.1s in some machines so we offer to initialize it 11 | " on the Rust side too. 12 | function! s:init_ext2ft() abort 13 | let matched = [] 14 | for line in split(execute('autocmd filetypedetect'), "\n") 15 | if line =~? '\*\.\a\+\s*setf' 16 | call add(matched, line) 17 | endif 18 | endfor 19 | 20 | let s:ext2ft = {'c': 'c', 'h': 'c', 'hpp': 'cpp', 'vimrc': 'vim'} 21 | for line in matched 22 | let splitted = split(line) 23 | let ext = split(splitted[0], '\.')[-1] 24 | let ft = splitted[-1] 25 | let s:ext2ft[ext] = ft 26 | endfor 27 | endfunction 28 | 29 | function! clap#ext#set(ext_map) abort 30 | let s:ext2ft = get(s:, 'ext2ft', {}) 31 | call extend(s:ext2ft, a:ext_map) 32 | endfunction 33 | 34 | function! clap#ext#into_filetype(fname) abort 35 | if !exists('s:ext2ft') 36 | call s:init_ext2ft() 37 | endif 38 | 39 | let ext = fnamemodify(a:fname, ':e') 40 | if !empty(ext) && has_key(s:ext2ft, ext) 41 | return s:ext2ft[ext] 42 | else 43 | return '' 44 | endif 45 | endfunction 46 | 47 | let &cpoptions = s:save_cpo 48 | unlet s:save_cpo 49 | -------------------------------------------------------------------------------- /autoload/clap/cache.vim: -------------------------------------------------------------------------------- 1 | " Author: Mark Wu 2 | " Description: Cache API for clap. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:path_separator = has('win32') ? '\' : '/' 8 | 9 | function! s:default_cache_dir() abort 10 | if has('nvim') 11 | let user_cache = stdpath('cache') 12 | elseif exists('$XDG_CACHE_HOME') 13 | let user_cache = $XDG_CACHE_HOME 14 | else 15 | let user_cache = $HOME . s:path_separator . '.cache' 16 | endif 17 | return user_cache . s:path_separator . 'clap' 18 | endfunction 19 | 20 | if exists('g:clap_cache_directory') 21 | let s:clap_cache_directory = expand(g:clap_cache_directory) 22 | else 23 | let s:clap_cache_directory = s:default_cache_dir() 24 | endif 25 | 26 | function! clap#cache#directory() abort 27 | if !isdirectory(s:clap_cache_directory) 28 | call mkdir(s:clap_cache_directory, 'p') 29 | endif 30 | return s:clap_cache_directory 31 | endf 32 | 33 | function! clap#cache#location_for(provider_id, fname) abort 34 | if empty(a:provider_id) 35 | call clap#helper#echo_error('provider_id can not be empty.') 36 | return v:null 37 | endif 38 | 39 | let provider_cache_directory = clap#cache#directory() . s:path_separator . a:provider_id 40 | 41 | if !isdirectory(provider_cache_directory) 42 | call mkdir(provider_cache_directory, 'p') 43 | endif 44 | 45 | return provider_cache_directory . s:path_separator . a:fname 46 | endfunction 47 | 48 | let &cpoptions = s:save_cpo 49 | unlet s:save_cpo 50 | -------------------------------------------------------------------------------- /autoload/clap/themes/onehalfdark.vim: -------------------------------------------------------------------------------- 1 | " Author: kerunaru 2 | " Description: Clap theme based on the onehalfdark theme. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:palette = {} 8 | 9 | let s:palette.display = { 'ctermbg': '237', 'guibg': '#313640' } " cursor_line 10 | 11 | " Let ClapInput, ClapSpinner and ClapSearchText use the same background. 12 | let s:bg0 = { 'ctermbg': '239', 'guibg': '#373C45' } " non_text 13 | let s:palette.input = s:bg0 14 | let s:palette.indicator = extend({ 'ctermfg': '247', 'guifg':'#919baa' }, s:bg0) " gutter_fg 15 | let s:palette.spinner = extend({ 'ctermfg': '180', 'guifg':'#e5c07b', 'cterm': 'bold', 'gui': 'bold'}, s:bg0) " yellow 16 | let s:palette.search_text = extend({ 'ctermfg': '188', 'guifg': '#dcdfe4', 'cterm': 'bold', 'gui': 'bold' }, s:bg0) " white 17 | 18 | let s:palette.preview = { 'ctermbg': '239', 'guibg': '#373C45' } " non_text 19 | 20 | let s:palette.selected = { 'ctermfg': '73', 'guifg': '#56b6c2', 'cterm': 'bold,underline', 'gui': 'bold,underline' } " cyan 21 | let s:palette.current_selection = { 'ctermbg': '236', 'guibg': '#282c34', 'cterm': 'bold', 'gui': 'bold' } " gutter_bg 22 | 23 | let s:palette.selected_sign = { 'ctermfg': '168', 'guifg': '#e06c75' } " red 24 | let s:palette.current_selection_sign = s:palette.selected_sign 25 | 26 | " blue 27 | let g:clap_fuzzy_match_hl_groups = [ 28 | \ ['75', '#61afef'], 29 | \ ] 30 | 31 | let g:clap#themes#onehalfdark#palette = s:palette 32 | 33 | let &cpoptions = s:save_cpo 34 | unlet s:save_cpo 35 | -------------------------------------------------------------------------------- /autoload/clap/themes/onehalflight.vim: -------------------------------------------------------------------------------- 1 | " Author: kerunaru 2 | " Description: Clap theme based on the onehalflight theme. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:palette = {} 8 | 9 | let s:palette.display = { 'ctermbg': '255', 'guibg': '#f0f0f0' } " cursor_line 10 | 11 | " Let ClapInput, ClapSpinner and ClapSearchText use the same background. 12 | let s:bg0 = { 'ctermbg': '252', 'guibg': '#e5e5e5' } " non_text 13 | let s:palette.input = s:bg0 14 | let s:palette.indicator = extend({ 'ctermfg': '247', 'guifg':'#a0a1a7' }, s:bg0) " comment_fg 15 | let s:palette.spinner = extend({ 'ctermfg': '166', 'guifg':'#c18401', 'cterm': 'bold', 'gui': 'bold'}, s:bg0) " yellow 16 | let s:palette.search_text = extend({ 'ctermfg': '237', 'guifg': '#383a42', 'cterm': 'bold', 'gui': 'bold' }, s:bg0) " black 17 | 18 | let s:palette.preview = { 'ctermbg': '252', 'guibg': '#e5e5e5' } " non_text 19 | 20 | let s:palette.selected = { 'ctermfg': '31', 'guifg': '#0997b3', 'cterm': 'bold,underline', 'gui': 'bold,underline' } " cyan 21 | let s:palette.current_selection = { 'ctermbg': '231', 'guibg': '#fafafa', 'cterm': 'bold', 'gui': 'bold' } " gutter_bg 22 | 23 | let s:palette.selected_sign = { 'ctermfg': '167', 'guifg': '#e45649' } " red 24 | let s:palette.current_selection_sign = s:palette.selected_sign 25 | 26 | " blue 27 | let g:clap_fuzzy_match_hl_groups = [ 28 | \ ['75', '#61afef'], 29 | \ ] 30 | 31 | let g:clap#themes#onehalflight#palette = s:palette 32 | 33 | let &cpoptions = s:save_cpo 34 | unlet s:save_cpo 35 | -------------------------------------------------------------------------------- /ftplugin/clap_display.vim: -------------------------------------------------------------------------------- 1 | if !has('nvim') 2 | finish 3 | endif 4 | 5 | setlocal 6 | \ nowrap 7 | \ nonumber 8 | \ norelativenumber 9 | \ nopaste 10 | \ nocursorline 11 | \ nocursorcolumn 12 | \ foldcolumn=0 13 | \ nomodeline 14 | \ noswapfile 15 | \ colorcolumn= 16 | \ nobuflisted 17 | \ buftype=nofile 18 | \ bufhidden=hide 19 | \ signcolumn=yes 20 | \ textwidth=0 21 | \ nolist 22 | \ winfixwidth 23 | \ winfixheight 24 | \ nospell 25 | \ nofoldenable 26 | 27 | inoremap =clap#navigation#linewise_scroll('down') 28 | inoremap =clap#navigation#linewise_scroll('up') 29 | 30 | inoremap =clap#handler#handle_mapping("\") 31 | inoremap =clap#handler#handle_mapping("\") 32 | 33 | nnoremap :call clap#handler#exit() 34 | nnoremap :call clap#handler#exit() 35 | nnoremap :call clap#handler#handle_mapping("\") 36 | 37 | nnoremap :call clap#navigation#linewise_scroll('down') 38 | nnoremap :call clap#navigation#linewise_scroll('up') 39 | 40 | nnoremap :call clap#handler#handle_mapping("\") 41 | nnoremap :call clap#handler#handle_mapping("\") 42 | -------------------------------------------------------------------------------- /autoload/clap/action.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Perform provider action against current entry 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:ACTIONS_TITLE_KEY = 'title' 8 | 9 | " `confirm()` based action menu, this is deprecated now. 10 | function! s:invoke_action() abort 11 | let provider_action = g:clap.provider._().action 12 | if has_key(provider_action, s:ACTIONS_TITLE_KEY) 13 | let title = provider_action[s:ACTIONS_TITLE_KEY]() 14 | else 15 | let title = 'Choose action:' 16 | endif 17 | let choices = filter(keys(provider_action), 'v:val !~# s:ACTIONS_TITLE_KEY') 18 | let choice_num = confirm(title, join(choices, "\n")) 19 | " User aborts the dialog 20 | if choice_num == 0 21 | return 22 | endif 23 | let choice = choices[choice_num-1] 24 | if has_key(provider_action, choice) 25 | " TODO: add `action*` for performing actions against multi-selected entries? 26 | call provider_action[choice]() 27 | else 28 | call clap#helper#echo_error('Invalid action choice: '.choice) 29 | endif 30 | endfunction 31 | 32 | function! clap#action#invoke() abort 33 | if !has_key(g:clap.provider._(), 'action') 34 | call clap#helper#echo_warn('action not implemented in provider '.g:clap.provider.id) 35 | return '' 36 | endif 37 | if has('nvim') 38 | call clap#floating_win#action#create() 39 | else 40 | call clap#popup#action#invoke() 41 | endif 42 | return '' 43 | endfunction 44 | 45 | let &cpoptions = s:save_cpo 46 | unlet s:save_cpo 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | _Instructions: Replace the template text and remove irrelevant text (including this line)_ 11 | **Warning: if you don't fill this issue template and provide the info of `:Clap debug` the issue could be closed directly.** 12 | 13 | **Environment (please complete the following information):** 14 | - OS: ??? 15 | - vim-clap version: ??? 16 | - Have you reproduced with a minimal vimrc: ??? 17 | - Have you updated to the latest plugin version: ??? 18 | - Have you upgraded to/compiled the latest Rust binary: ??? 19 | 20 | **Describe the bug** 21 | A clear and concise description of what the bug is. 22 | 23 | **Clap debug** 24 | 25 | 26 | 27 | ``` 28 | ``` 29 | 30 | **To Reproduce** 31 | Steps to reproduce the behavior: 32 | 33 | 1. Create the minimal vimrc `min.vim`: 34 | 35 | ```vim 36 | set nocompatible 37 | set runtimepath^=/path/to/vim-clap 38 | syntax on 39 | filetype plugin indent on 40 | ``` 41 | 42 | 2. Start (neo)vim with command: `vim -u min.vim` 43 | 44 | 3. Type '....' 45 | 46 | 4. See error 47 | 48 | **Expected behavior** 49 | A clear and concise description of what you expected to happen. 50 | 51 | **Screenshots** 52 | If applicable, add screenshots to help explain your problem. 53 | 54 | **Additional context** 55 | Add any other context about the problem here. 56 | -------------------------------------------------------------------------------- /autoload/clap/provider/proj_tags.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Project-wide tags 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:proj_tags = {} 8 | 9 | let s:support_json_format = 10 | \ len(filter(systemlist('ctags --list-features'), 'v:val =~# ''^json''')) > 0 11 | 12 | if !s:support_json_format 13 | call clap#helper#echo_error('Ensure ctags executable is in your PATH and has the JSON output feature') 14 | finish 15 | endif 16 | 17 | function! s:proj_tags.init() abort 18 | call clap#client#notify_on_init() 19 | endfunction 20 | 21 | function! s:extract(tag_row) abort 22 | let lnum = matchstr(a:tag_row, '^.*:\zs\(\d\+\)') 23 | let path = matchstr(a:tag_row, '\[.*@\zs\(\f*\)\ze\]') 24 | return [lnum, path] 25 | endfunction 26 | 27 | function! s:proj_tags.sink(selected) abort 28 | let [lnum, path] = s:extract(a:selected) 29 | call clap#sink#open_file(path, lnum, 1) 30 | endfunction 31 | 32 | function! s:proj_tags.on_move() abort 33 | let [lnum, path] = s:extract(g:clap.display.getcurline()) 34 | call clap#preview#file_at(path, lnum) 35 | endfunction 36 | 37 | let s:proj_tags.on_move_async = function('clap#impl#on_move#async') 38 | let s:proj_tags.on_typed = { -> clap#client#notify_provider('on_typed') } 39 | let s:proj_tags.enable_rooter = v:true 40 | let s:proj_tags.support_open_action = v:true 41 | let s:proj_tags.icon = 'ProjTags' 42 | let s:proj_tags.syntax = 'clap_proj_tags' 43 | 44 | let g:clap#provider#proj_tags# = s:proj_tags 45 | 46 | let &cpoptions = s:save_cpo 47 | unlet s:save_cpo 48 | -------------------------------------------------------------------------------- /crates/types/src/query.rs: -------------------------------------------------------------------------------- 1 | use crate::search_term::{ExactTerm, FuzzyTerm, InverseTerm, SearchTerm, TermType, WordTerm}; 2 | 3 | /// [`Query`] represents the structural search info parsed from the initial user input. 4 | #[derive(Debug, Clone)] 5 | pub struct Query { 6 | pub word_terms: Vec, 7 | pub exact_terms: Vec, 8 | pub fuzzy_terms: Vec, 9 | pub inverse_terms: Vec, 10 | } 11 | 12 | impl> From for Query { 13 | fn from(query: T) -> Self { 14 | let query = query.as_ref(); 15 | 16 | let mut word_terms = Vec::new(); 17 | let mut exact_terms = Vec::new(); 18 | let mut fuzzy_terms = Vec::new(); 19 | let mut inverse_terms = Vec::new(); 20 | 21 | for token in query.split_whitespace() { 22 | let SearchTerm { ty, text } = token.into(); 23 | 24 | match ty { 25 | TermType::Word => word_terms.push(WordTerm { text }), 26 | TermType::Exact(term_ty) => exact_terms.push(ExactTerm::new(term_ty, text)), 27 | TermType::Fuzzy(term_ty) => fuzzy_terms.push(FuzzyTerm::new(term_ty, text)), 28 | TermType::Inverse(term_ty) => inverse_terms.push(InverseTerm::new(term_ty, text)), 29 | } 30 | } 31 | 32 | Self { 33 | word_terms, 34 | exact_terms, 35 | fuzzy_terms, 36 | inverse_terms, 37 | } 38 | } 39 | } 40 | 41 | impl Query { 42 | pub fn fuzzy_len(&self) -> usize { 43 | self.fuzzy_terms.iter().map(|f| f.len()).sum() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/matcher/extracted_fzy/src/scoring_utils.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | pub(crate) type Score = i32; 4 | 5 | pub(crate) const SCORE_STARTER: Score = 0; 6 | 7 | pub(crate) const SCORE_DEFAULT_BONUS: Score = 0; 8 | pub(crate) const SCORE_MAX: Score = Score::MAX; 9 | pub(crate) const SCORE_MIN: Score = Score::MIN; 10 | pub(crate) const SCORE_GAP_LEADING: Score = -1; 11 | pub(crate) const SCORE_GAP_TRAILING: Score = -1; 12 | pub(crate) const SCORE_GAP_INNER: Score = -2; 13 | pub(crate) const SCORE_MATCH_CONSECUTIVE: Score = 200; 14 | pub(crate) const SCORE_MATCH_SLASH: Score = 180; 15 | pub(crate) const SCORE_MATCH_WORD: Score = 160; 16 | pub(crate) const SCORE_MATCH_CAPITAL: Score = 140; 17 | pub(crate) const SCORE_MATCH_DOT: Score = 120; 18 | 19 | /// Returns `true` if scores can be considered equal 20 | /// and `false` if not. 21 | #[inline] 22 | pub(crate) fn score_eq(score: Score, rhs: Score) -> bool { 23 | score == rhs 24 | } 25 | 26 | /// Adds `rhs` to the score and returns the result. 27 | #[inline] 28 | pub(crate) fn score_add(score: Score, rhs: Score) -> Score { 29 | score.saturating_add(rhs) 30 | } 31 | 32 | /// Subs `rhs` from the score and returns the result. 33 | #[inline] 34 | #[allow(dead_code)] 35 | pub(crate) fn score_sub(score: Score, rhs: Score) -> Score { 36 | score.saturating_sub(rhs) 37 | } 38 | 39 | /// Multiplies `score` by `rhs`. 40 | #[inline] 41 | pub(crate) fn score_mul(score: Score, rhs: Score) -> Score { 42 | score.saturating_mul(rhs) 43 | } 44 | 45 | #[inline] 46 | pub(crate) fn score_from_usize(u: usize) -> Score { 47 | Score::try_from(u).unwrap_or(SCORE_MAX) 48 | } 49 | -------------------------------------------------------------------------------- /crates/maple_core/src/stdio_server/job.rs: -------------------------------------------------------------------------------- 1 | //! This module ensures the process of same command won't be spawned multiple times simultaneously. 2 | 3 | use futures::Future; 4 | use once_cell::sync::Lazy; 5 | use parking_lot::Mutex; 6 | use std::collections::HashSet; 7 | use std::sync::Arc; 8 | 9 | static JOBS: Lazy>>> = 10 | Lazy::new(|| Arc::new(Mutex::new(HashSet::default()))); 11 | 12 | /// Spawn a new task to run the job if it's not reserved. 13 | #[allow(unused)] 14 | pub fn try_start(job_future: impl Future + Send + Sync + 'static, job_id: u64) { 15 | if reserve(job_id) { 16 | tokio::spawn(async move { 17 | job_future.await; 18 | unreserve(job_id) 19 | }); 20 | } 21 | } 22 | 23 | pub fn reserve(job_id: u64) -> bool { 24 | let mut jobs = JOBS.lock(); 25 | if jobs.contains(&job_id) { 26 | false 27 | } else { 28 | jobs.insert(job_id); 29 | true 30 | } 31 | } 32 | 33 | pub fn unreserve(job_id: u64) { 34 | let mut jobs = JOBS.lock(); 35 | jobs.remove(&job_id); 36 | } 37 | 38 | // Define a function to spawn a new thread and run a future 39 | #[allow(unused)] 40 | pub fn spawn_on_new_thread(future: F) -> std::thread::JoinHandle<()> 41 | where 42 | F: Future + Send + 'static, 43 | { 44 | std::thread::spawn(move || { 45 | let tokio_runtime = tokio::runtime::Builder::new_current_thread() 46 | .enable_all() 47 | .max_blocking_threads(32) 48 | .build() 49 | .unwrap(); 50 | tokio_runtime.block_on(future); 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /autoload/clap/provider/files.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the files. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:files = {} 8 | 9 | function! s:into_filename(line) abort 10 | if g:clap_enable_icon && clap#maple#is_available() 11 | return a:line[4:] 12 | else 13 | return a:line 14 | endif 15 | endfunction 16 | 17 | function! clap#provider#files#sink_impl(selected) abort 18 | let fpath = s:into_filename(a:selected) 19 | call clap#sink#edit_with_open_action(fpath) 20 | endfunction 21 | 22 | function! clap#provider#files#sink_star_impl(lines) abort 23 | call clap#sink#open_quickfix(map(map(a:lines, 's:into_filename(v:val)'), 24 | \ '{'. 25 | \ '"filename": v:val,'. 26 | \ '"text": strftime("Modified %b,%d %Y %H:%M:%S", getftime(v:val))." ".getfperm(v:val)'. 27 | \ '}')) 28 | endfunction 29 | 30 | function! clap#provider#files#on_move_impl() abort 31 | call clap#preview#file(s:into_filename(g:clap.display.getcurline())) 32 | endfunction 33 | 34 | let s:files.sink = function('clap#provider#files#sink_impl') 35 | let s:files['sink*'] = function('clap#provider#files#sink_star_impl') 36 | let s:files.on_move = function('clap#provider#files#on_move_impl') 37 | let s:files.on_move_async = function('clap#impl#on_move#async') 38 | let s:files.on_typed = { -> clap#client#notify_provider('on_typed') } 39 | let s:files.enable_rooter = v:true 40 | let s:files.support_open_action = v:true 41 | let s:files.icon = 'File' 42 | let s:files.syntax = 'clap_files' 43 | 44 | let g:clap#provider#files# = s:files 45 | 46 | let &cpoptions = s:save_cpo 47 | unlet s:save_cpo 48 | -------------------------------------------------------------------------------- /syntax/clap_tags.vim: -------------------------------------------------------------------------------- 1 | scriptencoding utf-8 2 | 3 | if !exists('s:groups') 4 | let s:groups = ['Character', 'Float', 'Identifier', 'Statement', 'Label', 'Boolean', 'Delimiter', 'Constant', 'String', 'Operator', 'PreCondit', 'Include', 'Conditional', 'PreProc', 'TypeDef',] 5 | let s:len_groups = len(s:groups) 6 | endif 7 | 8 | function! s:hi() abort 9 | if !exists('s:joined_icon_groups') 10 | let icons = clap#icon#get_all() 11 | 12 | let idx = 0 13 | let hi_idx = 0 14 | 15 | let icon_groups = [] 16 | for icon in icons 17 | let cur_group = 'ClapVistaIcon'.idx 18 | call add(icon_groups, cur_group) 19 | execute 'syntax match' cur_group '/^\s*'.icon.'/' 'contained' 20 | execute 'hi default link' cur_group s:groups[hi_idx] 21 | let hi_idx += 1 22 | let hi_idx = hi_idx % s:len_groups 23 | let idx += 1 24 | endfor 25 | 26 | let s:joined_icon_groups = join(icon_groups, ',') 27 | endif 28 | 29 | execute 'syntax match ClapVistaTag /\s*.*\(:\d\)\@=/' 'contains=ClapVistaIcon,'.s:joined_icon_groups 30 | execute 'syntax match ClapVistaNumber /^[^\[]*\(\s\s\[\)\@=/' 'contains=ClapVistaTag,ClapVistaIcon,'.s:joined_icon_groups 31 | syntax match ClapVistaScope /^[^]]*]/ contains=ClapVistaNumber,ClapVistaBracket 32 | syntax match ClapVista /^[^│┌└]*/ contains=ClapVistaBracket,ClapVistaTag,ClapVistaNumber,ClapVistaScope 33 | syntax match ClapVistaBracket /\s\s\[\|\]\s\s/ contained 34 | 35 | hi default link ClapVistaBracket SpecialKey 36 | hi default link ClapVistaNumber Number 37 | hi default link ClapVistaTag Tag 38 | hi default link ClapVistaScope Function 39 | hi default link ClapVista Type 40 | endfunction 41 | 42 | call s:hi() 43 | -------------------------------------------------------------------------------- /autoload/clap/maple.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Locate and invoke maple binary. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:bin_suffix = has('win32') ? '.exe' : '' 8 | 9 | let s:maple_bin_localbuilt = fnamemodify(g:clap#autoload_dir, ':h').'/target/release/maple'.s:bin_suffix 10 | let s:maple_bin_prebuilt = fnamemodify(g:clap#autoload_dir, ':h').'/bin/maple'.s:bin_suffix 11 | 12 | function! s:find_executable() abort 13 | " Check the locally built binary. 14 | if executable(s:maple_bin_localbuilt) 15 | let s:maple_bin = s:maple_bin_localbuilt 16 | " Check the prebuilt binary. 17 | elseif executable(s:maple_bin_prebuilt) 18 | let s:maple_bin = s:maple_bin_prebuilt 19 | " Check the binary in PATH. 20 | elseif executable('maple') 21 | let s:maple_bin = 'maple' 22 | " Binary not found 23 | else 24 | let s:maple_bin = v:null 25 | endif 26 | endfunction 27 | 28 | call s:find_executable() 29 | 30 | function! clap#maple#reload() abort 31 | call s:find_executable() 32 | endfunction 33 | 34 | function! clap#maple#notify_exit_provider() abort 35 | if s:maple_bin isnot v:null 36 | call clap#client#notify_provider('exit_provider') 37 | endif 38 | endfunction 39 | 40 | function! clap#maple#binary() abort 41 | return s:maple_bin 42 | endfunction 43 | 44 | function! clap#maple#is_available() abort 45 | return s:maple_bin isnot v:null 46 | endfunction 47 | 48 | function! clap#maple#build_cmd(...) abort 49 | return [s:maple_bin] + a:000 50 | endfunction 51 | 52 | function! clap#maple#build_cmd_list(cmd_list) abort 53 | return insert(a:cmd_list, s:maple_bin) 54 | endfunction 55 | 56 | let &cpoptions = s:save_cpo 57 | unlet s:save_cpo 58 | -------------------------------------------------------------------------------- /crates/maple_core/src/previewer/vim_help.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use utils::io::{read_lines, read_lines_from_small}; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct HelpTagPreview<'a> { 6 | /// Help tag name. 7 | subject: &'a str, 8 | /// Filename of the help text. 9 | doc_filename: &'a str, 10 | /// Output of `:echo &runtimepath` 11 | runtimepath: &'a str, 12 | } 13 | 14 | fn find_tag_line(p: &Path, subject: &str) -> Option { 15 | if let Ok(doc_lines) = read_lines(p) { 16 | for (idx, doc_line) in doc_lines.enumerate() { 17 | if let Ok(d_line) = doc_line { 18 | if d_line.trim().contains(subject) { 19 | return Some(idx); 20 | } 21 | } 22 | } 23 | } 24 | None 25 | } 26 | 27 | impl<'a> HelpTagPreview<'a> { 28 | pub fn new(subject: &'a str, doc_filename: &'a str, runtimepath: &'a str) -> Self { 29 | Self { 30 | subject, 31 | doc_filename, 32 | runtimepath, 33 | } 34 | } 35 | 36 | pub fn get_help_lines(&self, size: usize) -> Option<(String, Vec)> { 37 | let target_tag = format!("*{}*", self.subject); 38 | for r in self.runtimepath.split(',') { 39 | let p = Path::new(r).join("doc").join(self.doc_filename); 40 | if p.exists() { 41 | if let Some(line_number) = find_tag_line(&p, &target_tag) { 42 | if let Ok(lines_iter) = read_lines_from_small(&p, line_number, size) { 43 | return Some((format!("{}", p.display()), lines_iter.collect())); 44 | } 45 | } 46 | } 47 | } 48 | 49 | None 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /autoload/clap/provider/bcommits.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the buffer commits. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:bcommits = {} 8 | 9 | function! s:bcommits.source() abort 10 | return clap#provider#commits#source_common(v:true) 11 | endfunction 12 | 13 | function! s:into_git_diff_cmd(line) abort 14 | let rev = clap#provider#commits#parse_rev(a:line) 15 | let prev = s:find_prev(rev) 16 | return printf('git diff --color=never %s %s -- %s', rev, prev, bufname(g:clap.start.bufnr)) 17 | endfunction 18 | 19 | function! s:bcommits.on_move() abort 20 | let cur_line = g:clap.display.getcurline() 21 | call clap#provider#commits#on_move_common(s:into_git_diff_cmd(cur_line)) 22 | endfunction 23 | 24 | function! s:bcommits.sink(line) abort 25 | call clap#provider#commits#sink_inner('!'.s:into_git_diff_cmd(a:line)) 26 | endfunction 27 | 28 | function! s:bcommits.on_exit() abort 29 | if exists('s:shas') 30 | unlet s:shas 31 | endif 32 | endfunction 33 | 34 | function! s:find_prev(cur_rev) abort 35 | if !exists('s:shas') 36 | let s:shas = systemlist('git log --format=format:%h') 37 | let s:shas_len = len(s:shas) 38 | endif 39 | let idx = 0 40 | let prev = 'master' 41 | for commit in s:shas 42 | if commit == a:cur_rev 43 | if idx + 1 < s:shas_len 44 | let prev = s:shas[idx+1] 45 | endif 46 | return prev 47 | endif 48 | let idx += 1 49 | endfor 50 | return prev 51 | endfunction 52 | 53 | let s:bcommits.on_move_async = { -> clap#client#notify_provider('on_move') } 54 | let s:bcommits.syntax = 'clap_diff' 55 | let g:clap#provider#bcommits# = s:bcommits 56 | 57 | let &cpoptions = s:save_cpo 58 | unlet s:save_cpo 59 | -------------------------------------------------------------------------------- /autoload/clap/provider/git_diff_files.vim: -------------------------------------------------------------------------------- 1 | " Author: KITAGAWA Yasutaka 2 | " Description: List the files which is managed by git and have uncommitted changes. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:git_diff_files = {} 8 | 9 | function! s:git_diff_files.source() abort 10 | if !executable('git') 11 | call clap#helper#echo_error('git executable not found') 12 | return [] 13 | endif 14 | 15 | let changed = systemlist('git status -s -uno') 16 | if v:shell_error 17 | call clap#helper#echo_error('Error occurs on calling `git status -s -uno`, maybe you are not in a git repo.') 18 | return [] 19 | else 20 | return map(changed, 'split(v:val)[-1]') 21 | endif 22 | endfunction 23 | 24 | function! s:git_diff_files_on_move() abort 25 | let diff = 'git --no-pager diff -U0' 26 | let filediff = g:clap.display.getcurline() 27 | let difflist = systemlist(diff.' '.filediff) 28 | 29 | if !empty(difflist) 30 | if difflist[0]=~#'^fatal' 31 | let difflist = systemlist(diff.' -- '.filediff) 32 | if empty(difflist) 33 | let difflist = systemlist(diff.' --cached -- '.filediff) 34 | endif 35 | endif 36 | else 37 | let difflist = systemlist(diff.' --cached '.filediff) 38 | endif 39 | 40 | if !empty(difflist) 41 | call g:clap.preview.show(difflist[:15]) 42 | else 43 | call g:clap.preview.show(['No preview contents']) 44 | endif 45 | call g:clap.preview.set_syntax('diff') 46 | endfunction 47 | 48 | let s:git_diff_files.sink = 'e' 49 | let s:git_diff_files.enable_rooter = v:false 50 | let s:git_diff_files.on_move = function('s:git_diff_files_on_move') 51 | 52 | let g:clap#provider#git_diff_files# = s:git_diff_files 53 | 54 | let &cpoptions = s:save_cpo 55 | unlet s:save_cpo 56 | -------------------------------------------------------------------------------- /autoload/clap/provider/dumb_jump.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Jump to definition/reference based on the regexp. 3 | 4 | scriptencoding utf-8 5 | 6 | let s:save_cpo = &cpoptions 7 | set cpoptions&vim 8 | 9 | let s:dumb_jump = {} 10 | 11 | function! s:dumb_jump.sink(selected) abort 12 | let pattern = '^\[\(\a\+\)\]\zs\(.*\):\(\d\+\):\(\d\+\):' 13 | let matched = matchlist(a:selected, pattern) 14 | let [fpath, linenr, column] = [matched[2], str2nr(matched[3]), str2nr(matched[4])] 15 | call clap#sink#open_file(fpath, linenr, column) 16 | endfunction 17 | 18 | function! s:into_qf_item(line) abort 19 | let pattern = '^\[\(\a\+\)\]\zs\(.*\):\(\d\+\):\(\d\+\):\(.*\)' 20 | let matched = matchlist(a:line, pattern) 21 | let [fpath, linenr, column, text] = [matched[2], str2nr(matched[3]), str2nr(matched[4]), matched[5]] 22 | return {'filename': fpath, 'lnum': linenr, 'col': column, 'text': text} 23 | endfunction 24 | 25 | function! s:dumb_jump_sink_star(lines) abort 26 | call clap#sink#open_quickfix(map(a:lines, 's:into_qf_item(v:val)')) 27 | endfunction 28 | 29 | function! s:dumb_jump.on_typed() abort 30 | let query = g:clap.input.get() 31 | if empty(query) 32 | call clap#highlighter#clear_display() 33 | else 34 | call clap#client#notify_provider('on_typed') 35 | endif 36 | endfunction 37 | 38 | function! s:dumb_jump.init() abort 39 | call clap#client#notify_on_init() 40 | endfunction 41 | 42 | function! s:dumb_jump.on_move_async() abort 43 | call clap#client#notify_provider('on_move') 44 | endfunction 45 | 46 | let s:dumb_jump['sink*'] = function('s:dumb_jump_sink_star') 47 | let s:dumb_jump.syntax = 'clap_dumb_jump' 48 | let s:dumb_jump.enable_rooter = v:true 49 | let g:clap#provider#dumb_jump# = s:dumb_jump 50 | 51 | let &cpoptions = s:save_cpo 52 | unlet s:save_cpo 53 | -------------------------------------------------------------------------------- /autoload/clap/provider/registers.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the register list. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:registers = {} 8 | 9 | " Credit: https://github.com/junegunn/vim-peekaboo 10 | function! s:append_group(title, regs) abort 11 | call add(s:lines, a:title.':') 12 | for r in a:regs 13 | let val = eval('@'.r)[:&columns] 14 | if !empty(val) 15 | call add(s:lines, printf(' %s: %s', r, val)) 16 | endif 17 | endfor 18 | endfunction 19 | 20 | function! s:registers.source() abort 21 | let s:lines = [] 22 | call s:append_group('Special', ['"', '*', '+', '-']) 23 | call add(s:lines, '') 24 | call s:append_group('Last-search-pattern', ['/']) 25 | call add(s:lines, '') 26 | call s:append_group('Read-only', ['.', ':']) 27 | call add(s:lines, '') 28 | call s:append_group('Numbered', map(range(0, 9), 'string(v:val)')) 29 | call add(s:lines, '') 30 | call s:append_group('Named', map(range(97, 97 + 25), 'nr2char(v:val)')) 31 | return s:lines 32 | endfunction 33 | 34 | function! s:extract_reg(line) abort 35 | return matchstr(a:line, '^\s*\zs\(.\)\ze: ') 36 | endfunction 37 | 38 | function! s:registers.on_move() abort 39 | let curline = g:clap.display.getcurline() 40 | let reg = s:extract_reg(curline) 41 | if !empty(reg) 42 | let lines = split(eval('@'.reg), "\n") 43 | let preview_title = 'Content for register ['.s:extract_reg(curline).']:' 44 | call g:clap.preview.show([preview_title] + lines) 45 | call clap#preview#highlight_header() 46 | endif 47 | endfunction 48 | 49 | function! s:registers.sink(selected) abort 50 | let reg = s:extract_reg(a:selected) 51 | execute 'normal!' '"'.reg.'p' 52 | endfunction 53 | 54 | let s:registers.syntax = 'clap_registers' 55 | 56 | let g:clap#provider#registers# = s:registers 57 | 58 | let &cpoptions = s:save_cpo 59 | unlet s:save_cpo 60 | -------------------------------------------------------------------------------- /crates/matcher/src/algo/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod fzf; 2 | pub mod fzy; 3 | pub mod nucleo; 4 | pub mod skim; 5 | pub mod substring; 6 | 7 | use crate::MatchResult; 8 | use types::{CaseMatching, FuzzyText}; 9 | 10 | // TODO: Integrate https://github.com/nomad/norm for fzf algo. 11 | #[derive(Debug, Clone, Copy, Default)] 12 | pub enum FuzzyAlgorithm { 13 | #[default] 14 | Fzy, 15 | Skim, 16 | FzfV2, 17 | Nucleo, 18 | } 19 | 20 | impl std::str::FromStr for FuzzyAlgorithm { 21 | type Err = (); 22 | fn from_str(s: &str) -> Result { 23 | Ok(s.into()) 24 | } 25 | } 26 | 27 | impl> From for FuzzyAlgorithm { 28 | fn from(algo: T) -> Self { 29 | match algo.as_ref().to_lowercase().as_str() { 30 | "skim" => Self::Skim, 31 | "fzy" => Self::Fzy, 32 | "fzf-v2" => Self::FzfV2, 33 | "nucleo" => Self::Nucleo, 34 | _ => Self::Fzy, 35 | } 36 | } 37 | } 38 | 39 | impl FuzzyAlgorithm { 40 | pub fn fuzzy_match( 41 | &self, 42 | query: &str, 43 | fuzzy_text: &FuzzyText, 44 | case_matching: CaseMatching, 45 | ) -> Option { 46 | let FuzzyText { 47 | text, 48 | matching_start, 49 | } = fuzzy_text; 50 | 51 | let fuzzy_result = match self { 52 | Self::Fzy => fzy::fuzzy_indices(text, query, case_matching), 53 | Self::Skim => skim::fuzzy_indices(text, query, case_matching), 54 | Self::FzfV2 => fzf::fuzzy_indices_v2(text, query), 55 | Self::Nucleo => nucleo::fuzzy_indices(text, query, case_matching), 56 | }; 57 | fuzzy_result.map(|MatchResult { score, indices }| { 58 | let mut indices = indices; 59 | indices.iter_mut().for_each(|x| *x += matching_start); 60 | MatchResult::new(score, indices) 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /autoload/clap/legacy/filter/sync/viml.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Native VimL implementation of filter. 3 | " Used when there is no +python3 and external binary. 4 | 5 | let s:save_cpo = &cpoptions 6 | set cpoptions&vim 7 | 8 | let s:pattern_builder = {} 9 | 10 | function! s:pattern_builder._force_case() abort 11 | " Smart case 12 | return self.input =~? '\u' ? '\C' : '\c' 13 | endfunction 14 | 15 | function! s:pattern_builder.smartcase() abort 16 | let l:_force_case = self._force_case() 17 | let s:matchadd_pattern = l:_force_case.self.input 18 | return l:_force_case.self.input 19 | endfunction 20 | 21 | function! s:pattern_builder.substring() abort 22 | let l:_force_case = self._force_case() 23 | let l:filter_pattern = ['\V\^', l:_force_case] 24 | let s:matchadd_pattern = [] 25 | for l:s in split(self.input) 26 | call add(filter_pattern, printf('\.\*\zs%s\ze', l:s)) 27 | " FIXME can not distinguish `f f` highlight 28 | " these two f should be highlighed with different colors 29 | call add(s:matchadd_pattern, l:_force_case.l:s) 30 | endfor 31 | return join(l:filter_pattern, '') 32 | endfunction 33 | 34 | function! s:pattern_builder.build() abort 35 | if stridx(self.input, ' ') != -1 36 | return self.substring() 37 | else 38 | return self.smartcase() 39 | endif 40 | endfunction 41 | 42 | function! s:filter(line, pattern) abort 43 | return a:line =~ a:pattern 44 | endfunction 45 | 46 | " Return substring pattern or the smartcase input pattern. 47 | function! clap#legacy#filter#sync#viml#matchadd_pattern() abort 48 | return get(s:, 'matchadd_pattern', '') 49 | endfunction 50 | 51 | function! clap#legacy#filter#sync#viml#(query, candidates) abort 52 | let s:pattern_builder.input = a:query 53 | let l:filter_pattern = s:pattern_builder.build() 54 | return filter(copy(a:candidates), 's:filter(v:val, l:filter_pattern)') 55 | endfunction 56 | 57 | let &cpoptions = s:save_cpo 58 | unlet s:save_cpo 59 | -------------------------------------------------------------------------------- /crates/icon/update_constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # This script is now depreated due to icon/build.rs. 5 | # 6 | # Whenever you change exactmatch_map.json or extension_map.json, 7 | # rerun this script to regenerate src/constants.rs: 8 | # ./update_constants.py 9 | import json 10 | import os 11 | 12 | lines = [ 13 | '// This file is generated by ../update_constants.py.', 14 | '// Do not edit this file manually.', 15 | '', 16 | ] 17 | 18 | with open('extension_map.json', 'r') as f: 19 | disordered = json.load(f) 20 | sorted_dict = {k: disordered[k] for k in sorted(disordered)} 21 | 22 | joined_tuples = ','.join( 23 | map(lambda kv: '("%s", \'%s\')' % (kv[0], kv[1]), sorted_dict.items())) 24 | lines.append('pub static EXTENSION_ICON_TABLE: &[(&str, char)] = &[%s];' % 25 | joined_tuples) 26 | 27 | with open('exactmatch_map.json', 'r') as f: 28 | disordered = json.load(f) 29 | sorted_dict = {k: disordered[k] for k in sorted(disordered)} 30 | 31 | joined_tuples = ','.join( 32 | map(lambda kv: '("%s", \'%s\')' % (kv[0], kv[1]), sorted_dict.items())) 33 | lines.append('pub static EXACTMATCH_ICON_TABLE: &[(&str, char)] = &[%s];' % 34 | joined_tuples) 35 | 36 | with open('tagkind_map.json', 'r') as f: 37 | disordered = json.load(f) 38 | sorted_dict = {k: disordered[k] for k in sorted(disordered)} 39 | 40 | joined_tuples = ','.join( 41 | map(lambda kv: '("%s", \'%s\')' % (kv[0], kv[1]), sorted_dict.items())) 42 | lines.append('pub static TAGKIND_ICON_TABLE: &[(&str, char)] = &[%s];' % 43 | joined_tuples) 44 | 45 | lines.append(''' 46 | pub fn bsearch_icon_table(c: &str, table: &[(&str, char)]) ->Option { 47 | table.binary_search_by(|&(key, _)| key.cmp(&c)).ok() 48 | } 49 | ''') 50 | 51 | with open('src/constants.rs', 'w') as f: 52 | f.write('\n'.join(lines)) 53 | 54 | os.system('rustfmt src/constants.rs') 55 | -------------------------------------------------------------------------------- /pythonx/clap/fzy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import vim 5 | from clap.scorer import fzy_scorer, substr_scorer 6 | 7 | 8 | def str2bool(v): 9 | # For neovim, vim.eval("a:enable_icon") is str 10 | # For vim, vim.eval("a:enable_icon") is bool 11 | if isinstance(v, bool): 12 | return v 13 | else: 14 | return v.lower() in ("yes", "true", "t", "1") 15 | 16 | 17 | def apply_score(scorer, query, candidates, enable_icon): 18 | scored = [] 19 | 20 | for c in candidates: 21 | # Skip two chars 22 | if enable_icon: 23 | candidate = c[2:] 24 | else: 25 | candidate = c 26 | score, indices = scorer(query, candidate) 27 | if score != float("-inf"): 28 | if enable_icon: 29 | indices = [x + 4 for x in indices] 30 | scored.append({'score': score, 'indices': indices, 'text': c}) 31 | 32 | return scored 33 | 34 | 35 | def fuzzy_match_py(query, candidates, enable_icon): 36 | if ' ' in query: 37 | scorer = substr_scorer 38 | else: 39 | scorer = fzy_scorer 40 | 41 | scored = apply_score(scorer, query, candidates, enable_icon) 42 | ranked = sorted(scored, key=lambda x: x['score'], reverse=True) 43 | 44 | indices = [] 45 | filtered = [] 46 | for r in ranked: 47 | filtered.append(r['text']) 48 | indices.append(r['indices']) 49 | 50 | return (indices, filtered) 51 | 52 | 53 | def clap_fzy_py(): 54 | return fuzzy_match_py(vim.eval("a:query"), vim.eval("a:candidates"), 55 | str2bool(vim.eval("a:context")['enable_icon'])) 56 | 57 | 58 | try: 59 | from clap.fuzzymatch_rs import fuzzy_match as fuzzy_match_rs 60 | 61 | def clap_fzy_rs(): 62 | return fuzzy_match_rs(vim.eval("a:query"), vim.eval("a:candidates"), 63 | vim.eval("a:recent_files"), 64 | vim.eval("a:context")) 65 | except Exception: 66 | pass 67 | -------------------------------------------------------------------------------- /crates/code_tools/src/linting/linters/go.rs: -------------------------------------------------------------------------------- 1 | use crate::linting::{Code, Diagnostic, DiagnosticSpan, Linter, Severity}; 2 | use once_cell::sync::Lazy; 3 | use regex::Regex; 4 | use std::path::Path; 5 | 6 | // /home/xlc/Data0/src/github.com/ethereum-optimism/optimism/op-node/rollup/superchain.go:38:27-43: undefined: eth.XXXXSystemConfig 7 | static RE: Lazy = Lazy::new(|| { 8 | Regex::new(r"(?m)^([^:]+):([0-9]+):([0-9]+)-([0-9]+): (.+)$") 9 | .expect("Regex for parsing gopls output must be correct otherwise the upstream format must have been changed") 10 | }); 11 | 12 | fn parse_line_gopls(line: &[u8]) -> Option { 13 | let line = String::from_utf8_lossy(line); 14 | 15 | if let Some(caps) = RE.captures_iter(&line).next() { 16 | // [path, line, column_start, column_end, message] 17 | let (Some(line), Some(column_start), Some(column_end), Some(message)) = ( 18 | caps.get(2).and_then(|m| m.as_str().parse::().ok()), 19 | caps.get(3).and_then(|m| m.as_str().parse::().ok()), 20 | caps.get(4).and_then(|m| m.as_str().parse::().ok()), 21 | caps.get(5).map(|m| m.as_str().to_string()), 22 | ) else { 23 | return None; 24 | }; 25 | 26 | return Some(Diagnostic { 27 | spans: vec![DiagnosticSpan { 28 | line_start: line, 29 | line_end: line, 30 | column_start, 31 | column_end, 32 | }], 33 | code: Code::default(), 34 | severity: Severity::Error, 35 | message, 36 | }); 37 | } 38 | 39 | None 40 | } 41 | 42 | pub struct Gopls; 43 | 44 | impl Linter for Gopls { 45 | const EXE: &'static str = "gopls"; 46 | 47 | fn add_args(cmd: &mut tokio::process::Command, source_file: &Path) { 48 | cmd.arg("check").arg(source_file); 49 | } 50 | 51 | fn parse_line(&self, line: &[u8]) -> Option { 52 | parse_line_gopls(line) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /autoload/clap/provider/filer.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Ivy-like file explorer. 3 | 4 | scriptencoding utf-8 5 | 6 | let s:save_cpo = &cpoptions 7 | set cpoptions&vim 8 | 9 | let s:filer = {} 10 | 11 | let s:CREATE_FILE = ' [Create new file]' 12 | 13 | function! clap#provider#filer#handle_error(error) abort 14 | call g:clap.preview.show([a:error]) 15 | endfunction 16 | 17 | function! clap#provider#filer#set_create_file_entry() abort 18 | call clap#highlighter#clear_display() 19 | let input = g:clap.input.get() 20 | let create_file_line = (g:clap_enable_icon ? ' ' : '') . input . s:CREATE_FILE 21 | call g:clap.display.set_lines([create_file_line]) 22 | endfunction 23 | 24 | function! clap#provider#filer#sink(entry) abort 25 | call clap#handler#sink_with({ -> execute('edit '.fnameescape(a:entry))}) 26 | endfunction 27 | 28 | function! s:filer.on_move_async() abort 29 | if stridx(g:clap.display.getcurline(), s:CREATE_FILE) > -1 30 | call g:clap.preview.hide() 31 | return 32 | endif 33 | call clap#client#notify_provider('on_move') 34 | endfunction 35 | 36 | function! s:start_rpc_service() abort 37 | let current_dir = clap#file_explorer#init_current_dir() 38 | call clap#file_explorer#set_prompt(current_dir, winwidth(g:clap.display.winid)) 39 | call clap#client#notify_on_init({'cwd': current_dir}) 40 | endfunction 41 | 42 | let s:filer.init = function('s:start_rpc_service') 43 | let s:filer.icon = 'File' 44 | let s:filer.syntax = 'clap_filer' 45 | let s:filer.source_type = g:__t_rpc 46 | let s:filer.on_typed = { -> clap#client#notify_provider('on_typed') } 47 | let s:filer.mappings = { 48 | \ "": { -> clap#client#notify_provider('cr') }, 49 | \ "": { -> clap#client#notify_provider('backspace') }, 50 | \ "": { -> clap#client#notify_provider('tab') }, 51 | \ "": { -> clap#client#notify_provider('backspace') }, 52 | \ } 53 | let g:clap#provider#filer# = s:filer 54 | 55 | let &cpoptions = s:save_cpo 56 | unlet s:save_cpo 57 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -u 4 | 5 | REPO=https://github.com/liuchengxu/vim-clap 6 | APP=maple 7 | 8 | exists() { 9 | command -v "$1" >/dev/null 2>&1 10 | } 11 | 12 | download() { 13 | local from=$1 14 | local to=$2 15 | if exists "curl"; then 16 | curl -fLo "$to" "$from" 17 | elif exists 'wget'; then 18 | wget --output-document="$to" "$from" 19 | else 20 | echo 'curl or wget is required' 21 | exit 1 22 | fi 23 | } 24 | 25 | remote_latest_tag() { 26 | git -c 'versionsort.suffix=-' ls-remote --exit-code --refs --sort='version:refname' --tags "$REPO" 'v0.*' \ 27 | | tail --lines=1 \ 28 | | awk -F "/" '{print $NF}' 29 | } 30 | 31 | try_download() { 32 | local remote_latest_tag=$(remote_latest_tag) 33 | echo "bin/$APP is empty, try downloading the latest prebuilt binary $APP $remote_latest_tag from GitHub ..." 34 | 35 | local DOWNLOAD_URL="$REPO/releases/download/$remote_latest_tag" 36 | local asset=$1 37 | if [ -z "${TMPDIR+x}" ]; then 38 | rm -f bin/$APP 39 | download "$DOWNLOAD_URL/$asset" bin/$APP 40 | else 41 | local temp=${TMPDIR}/maple 42 | download "$DOWNLOAD_URL/$asset" "$temp" 43 | mv "$temp" bin/$APP 44 | fi 45 | chmod a+x "bin/$APP" 46 | } 47 | 48 | download_prebuilt_binary() { 49 | arch=$(uname -sm) 50 | case "${arch}" in 51 | "Linux x86_64") 52 | try_download "$APP"-x86_64-unknown-linux-musl ;; 53 | "Linux aarch64") 54 | try_download "$APP"-aarch64-unknown-linux-musl ;; 55 | "Darwin x86_64") 56 | try_download "$APP"-x86_64-apple-darwin ;; 57 | "Darwin arm64") 58 | try_download "$APP"-aarch64-apple-darwin ;; 59 | *) 60 | echo "No prebuilt maple binary available for this platform ${arch}." 61 | echo "You can compile the binary locally by running `make` or `cargo build --release` if Rust has been installed." 62 | exit 1 63 | ;; 64 | esac 65 | } 66 | 67 | if [ ! -f "bin/$APP" ]; then 68 | download_prebuilt_binary 69 | else 70 | "bin/$APP" upgrade 71 | fi 72 | -------------------------------------------------------------------------------- /autoload/clap/plugin/word_highlighter.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Highlight the cursor word and the occurrences 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | hi ClapUnderline gui=underline cterm=underline 8 | 9 | hi default link ClapCursorWord IncSearch 10 | hi default link ClapCursorWordTwins ClapUnderline 11 | 12 | augroup VimClapWordHighlighter 13 | autocmd! 14 | 15 | autocmd ColorScheme * call clap#client#notify('word-highlighter.__defineHighlights', [+expand('')]) 16 | augroup END 17 | 18 | function! clap#plugin#word_highlighter#add_keyword_highlights(keyword_highlights) abort 19 | let match_ids = [] 20 | for hl in a:keyword_highlights 21 | let match_id = matchaddpos(hl.hl_group, [[hl.line_number, hl.col+1, hl.length]]) 22 | if match_id > -1 23 | call add(match_ids, match_id) 24 | endif 25 | endfor 26 | return match_ids 27 | endfunction 28 | 29 | function! clap#plugin#word_highlighter#add_highlights(word_highlights) abort 30 | let cword_len = a:word_highlights.cword_len 31 | let match_ids = [] 32 | let [lnum, col] = a:word_highlights.cword_highlight 33 | let match_id = matchaddpos('ClapCursorWord', [[lnum, col+1, cword_len]]) 34 | if match_id > -1 35 | call add(match_ids, match_id) 36 | endif 37 | for [lnum, col] in a:word_highlights.twins_words_highlight 38 | let match_id = matchaddpos('ClapCursorWordTwins', [[lnum, col+1, cword_len]]) 39 | if match_id > -1 40 | call add(match_ids, match_id) 41 | endif 42 | endfor 43 | return match_ids 44 | endfunction 45 | 46 | function! clap#plugin#word_highlighter#define_highlights(highlights, twins_highlights) abort 47 | let [ctermbg, guibg] = a:highlights 48 | let [twins_ctermbg, twins_guibg] = a:twins_highlights 49 | 50 | execute printf('highlight ClapCursorWord ctermbg=%d guibg=%s', ctermbg, guibg) 51 | execute printf('highlight ClapCursorWordTwins ctermbg=%d guibg=%s', twins_ctermbg, twins_guibg) 52 | endfunction 53 | 54 | let &cpoptions = s:save_cpo 55 | unlet s:save_cpo 56 | -------------------------------------------------------------------------------- /crates/matcher/src/matchers/fuzzy_matcher.rs: -------------------------------------------------------------------------------- 1 | use crate::algo::FuzzyAlgorithm; 2 | use std::sync::Arc; 3 | use types::{CaseMatching, ClapItem, FuzzyTerm, FuzzyText, MatchResult, MatchScope, Score}; 4 | 5 | #[derive(Debug, Clone, Default)] 6 | pub struct FuzzyMatcher { 7 | pub match_scope: MatchScope, 8 | pub fuzzy_algo: FuzzyAlgorithm, 9 | pub fuzzy_terms: Vec, 10 | pub case_matching: CaseMatching, 11 | } 12 | 13 | impl FuzzyMatcher { 14 | pub fn new( 15 | match_scope: MatchScope, 16 | fuzzy_algo: FuzzyAlgorithm, 17 | fuzzy_terms: Vec, 18 | case_matching: CaseMatching, 19 | ) -> Self { 20 | Self { 21 | match_scope, 22 | fuzzy_algo, 23 | fuzzy_terms, 24 | case_matching, 25 | } 26 | } 27 | 28 | pub fn is_empty(&self) -> bool { 29 | self.fuzzy_terms.is_empty() 30 | } 31 | 32 | pub fn find_matches(&self, item: &Arc) -> Option<(Score, Vec)> { 33 | item.fuzzy_text(self.match_scope) 34 | .as_ref() 35 | .and_then(|fuzzy_text| self.match_fuzzy_text(fuzzy_text)) 36 | } 37 | 38 | pub fn match_fuzzy_text(&self, fuzzy_text: &FuzzyText) -> Option<(Score, Vec)> { 39 | let fuzzy_len = self.fuzzy_terms.iter().map(|f| f.len()).sum(); 40 | 41 | // Try the fuzzy terms against the matched text. 42 | let mut fuzzy_indices = Vec::with_capacity(fuzzy_len); 43 | let mut fuzzy_score = Score::default(); 44 | 45 | for term in self.fuzzy_terms.iter() { 46 | let query = &term.text; 47 | if let Some(MatchResult { score, indices }) = 48 | self.fuzzy_algo 49 | .fuzzy_match(query, fuzzy_text, self.case_matching) 50 | { 51 | fuzzy_score += score; 52 | fuzzy_indices.extend(indices); 53 | } else { 54 | return None; 55 | } 56 | } 57 | 58 | Some((fuzzy_score, fuzzy_indices)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /autoload/clap/provider/colors.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the colorschemes. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:colors = {} 8 | 9 | function! s:ignore_default_colors(colors) abort 10 | if !exists('s:default_colors') 11 | let s:default_colors = split(globpath($VIMRUNTIME, 'colors/*.vim'), "\n") 12 | endif 13 | return filter(a:colors, 'index(s:default_colors, v:val) == -1') 14 | endfunction 15 | 16 | " Derived from fzf.vim 17 | function! s:colors.source() abort 18 | let colors = split(globpath(&runtimepath, 'colors/*.vim'), "\n") 19 | if has('packages') 20 | let colors += split(globpath(&packpath, 'pack/*/opt/*/colors/*.vim'), "\n") 21 | endif 22 | if get(g:, 'clap_provider_colors_ignore_default', 0) 23 | let colors = s:ignore_default_colors(colors) 24 | endif 25 | return map(colors, "substitute(fnamemodify(v:val, ':t'), '\\..\\{-}$', '', '')") 26 | endfunction 27 | 28 | function! s:colors.on_enter() abort 29 | let s:old_color = execute('colorscheme') 30 | let s:old_bg = &background 31 | endfunction 32 | 33 | " Preview the colorscheme on move 34 | function! s:colors.on_move() abort 35 | " This is neccessary 36 | noautocmd call g:clap.start.goto_win() 37 | execute 'color' g:clap.display.getcurline() 38 | do Syntax 39 | noautocmd call g:clap.input.goto_win() 40 | endfunction 41 | 42 | function! s:colors.sink(selected) abort 43 | execute 'color' a:selected 44 | " Reload syntax 45 | " https://stackoverflow.com/questions/8674387/vim-how-to-reload-syntax-highlighting 46 | do Syntax 47 | let s:should_restore_color = v:false 48 | endfunction 49 | 50 | function! s:colors.on_exit() abort 51 | if get(s:, 'should_restore_color', v:true) 52 | noautocmd call g:clap.start.goto_win() 53 | execute 'color' trim(s:old_color) 54 | let &background = s:old_bg 55 | let s:should_restore_color = v:true 56 | endif 57 | endfunction 58 | 59 | let s:colors.mode = 'quick_pick' 60 | let g:clap#provider#colors# = s:colors 61 | 62 | let &cpoptions = s:save_cpo 63 | unlet s:save_cpo 64 | -------------------------------------------------------------------------------- /autoload/clap/provider/maps.vim: -------------------------------------------------------------------------------- 1 | " Author: Mark Wu 2 | " Description: List the maps. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | " Derived from fzf.vim 8 | let s:allowed_mode = ['n', 'i', 'x', 'o'] 9 | 10 | function! s:align_pairs(list) abort 11 | let maxlen = 0 12 | let pairs = [] 13 | for elem in a:list 14 | let match = matchlist(elem, '^\(\S*\)\s*\(.*\)$') 15 | let [_, k, v] = match[0:2] 16 | let maxlen = max([maxlen, len(k)]) 17 | call add(pairs, [k, substitute(v, '^\*\?[@ ]\?', '', '')]) 18 | endfor 19 | let maxlen = min([maxlen, 35]) 20 | return map(pairs, "printf('%-'.maxlen.'s', v:val[0]).' '.v:val[1]") 21 | endfunction 22 | 23 | function! s:maps_source() abort 24 | let mode = get(g:clap.context, 'mode', 'n') 25 | if index(s:allowed_mode, mode) == -1 26 | let mode = 'n' 27 | endif 28 | 29 | let s:map_gv = mode ==# 'x' ? 'gv' : '' 30 | let s:map_cnt = v:count == 0 ? '' : v:count 31 | let s:map_reg = empty(v:register) ? '' : ('"'.v:register) 32 | let s:map_op = mode ==# 'o' ? v:operator : '' 33 | 34 | let cout = clap#api#win_execute(g:clap.start.winid, 'verbose '.mode.'map') 35 | let list = [] 36 | let curr = '' 37 | for line in split(cout, "\n") 38 | if line =~# "^\t" 39 | let src = ' '.join(reverse(reverse(split(split(line)[-1], '/'))[0:2]), '/') 40 | let list[-1] = printf('%s %s', curr, src) 41 | let curr = '' 42 | else 43 | let curr = line[3:] 44 | call add(list, printf('%s', curr)) 45 | endif 46 | endfor 47 | 48 | return sort(s:align_pairs(list)) 49 | endfunction 50 | 51 | function! s:maps_sink(selected) abort 52 | let key = matchstr(a:selected, '^\S*') 53 | redraw 54 | call feedkeys(s:map_gv.s:map_cnt.s:map_reg, 'n') 55 | call feedkeys(s:map_op. 56 | \ substitute(key, '<[^ >]\+>', '\=eval("\"\\".submatch(0)."\"")', 'g')) 57 | endfunction 58 | 59 | let s:maps = {} 60 | let s:maps.sink = function('s:maps_sink') 61 | let s:maps.source = function('s:maps_source') 62 | 63 | let g:clap#provider#maps# = s:maps 64 | 65 | let &cpoptions = s:save_cpo 66 | unlet s:save_cpo 67 | -------------------------------------------------------------------------------- /crates/maple_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "maple_core" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | publish.workspace = true 8 | homepage.workspace = true 9 | description = "Core of vim-clap Rust backend" 10 | 11 | [dependencies] 12 | async-trait = { workspace = true } 13 | base64 = { workspace = true } 14 | chrono = { workspace = true } 15 | chrono-humanize = { workspace = true } 16 | clap = { workspace = true } 17 | colors-transform = { workspace = true } 18 | content_inspector = { workspace = true } 19 | copypasta = { version = "0.10.0", default-features = false, features = [ "x11" ] } 20 | futures = { workspace = true } 21 | # ripgrep for global search 22 | grep-searcher = { workspace = true } 23 | grep-matcher = { workspace = true } 24 | ignore = { workspace = true } 25 | itertools = { workspace = true } 26 | tokio = { workspace = true, features = ["fs", "rt", "process", "macros", "rt-multi-thread", "sync", "time"] } 27 | once_cell = { workspace = true } 28 | parking_lot = { workspace = true } 29 | rayon = { workspace = true } 30 | regex = { workspace = true } 31 | rgb2ansi256 = { workspace = true } 32 | serde = { workspace = true } 33 | serde_json = { workspace = true } 34 | strsim = { workspace = true } 35 | subprocess = { workspace = true } 36 | thiserror = { workspace = true } 37 | toml = { workspace = true } 38 | tracing = { workspace = true } 39 | webbrowser = { workspace = true } 40 | 41 | code_tools = { workspace = true } 42 | dirs = { workspace = true } 43 | maple_config = { workspace = true } 44 | maple_derive = { workspace = true } 45 | maple_markdown = { workspace = true } 46 | filter = { workspace = true } 47 | icon = { workspace = true } 48 | maple_lsp = { workspace = true } 49 | matcher = { workspace = true } 50 | paths = { workspace = true } 51 | pattern = { workspace = true } 52 | printer = { workspace = true } 53 | rpc = { workspace = true } 54 | sublime_syntax = { workspace = true } 55 | types = { workspace = true } 56 | tree_sitter = { workspace = true } 57 | utils = { workspace = true } 58 | 59 | [dev-dependencies] 60 | git = { package = "git2", version = "0.15" } 61 | -------------------------------------------------------------------------------- /crates/cli/src/command/ctags/tags_file.rs: -------------------------------------------------------------------------------- 1 | use super::CtagsCommonArgs; 2 | use crate::app::Args; 3 | use anyhow::Result; 4 | use clap::Parser; 5 | use maple_core::find_usages::{CtagsSearcher, QueryType}; 6 | use maple_core::tools::ctags::TagsGenerator; 7 | 8 | #[derive(Parser, Debug, Clone)] 9 | struct TagsFileArgs { 10 | /// Same with the `--kinds-all` option of ctags. 11 | #[clap(long, default_value = "*")] 12 | kinds_all: String, 13 | 14 | /// Same with the `--fields` option of ctags. 15 | #[clap(long, default_value = "*")] 16 | fields: String, 17 | 18 | /// Same with the `--extras` option of ctags. 19 | #[clap(long, default_value = "*")] 20 | extras: String, 21 | } 22 | 23 | /// Manipulate the tags file. 24 | #[derive(Parser, Debug, Clone)] 25 | pub struct TagsFile { 26 | /// Arguments for creating tags file. 27 | #[clap(flatten)] 28 | t_args: TagsFileArgs, 29 | 30 | /// Ctags common arguments. 31 | #[clap(flatten)] 32 | c_args: CtagsCommonArgs, 33 | 34 | /// Search the tag matching the given query. 35 | #[clap(long)] 36 | query: Option, 37 | 38 | /// Generate the tags file whether the tags file exists or not. 39 | #[clap(long)] 40 | force_generate: bool, 41 | } 42 | 43 | impl TagsFile { 44 | pub fn run(&self, _args: Args) -> Result<()> { 45 | let dir = self.c_args.dir()?; 46 | 47 | let exclude_opt = self.c_args.exclude_opt(); 48 | let tags_generator = TagsGenerator::new( 49 | self.c_args.languages.clone(), 50 | &self.t_args.kinds_all, 51 | &self.t_args.fields, 52 | &self.t_args.extras, 53 | &self.c_args.files, 54 | &dir, 55 | &exclude_opt, 56 | ); 57 | 58 | let tags_searcher = CtagsSearcher::new(tags_generator); 59 | 60 | if let Some(ref query) = self.query { 61 | let symbols = 62 | tags_searcher.search_symbols(query, QueryType::StartWith, self.force_generate)?; 63 | for symbol in symbols { 64 | println!("{symbol:?}"); 65 | } 66 | } 67 | 68 | Ok(()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/maple_core/src/searcher/grep/mod.rs: -------------------------------------------------------------------------------- 1 | mod stoppable_searcher; 2 | 3 | pub use self::stoppable_searcher::search; 4 | use self::stoppable_searcher::{FileResult, StoppableSearchImpl, UPDATE_INTERVAL}; 5 | use matcher::Matcher; 6 | use std::path::PathBuf; 7 | use std::sync::atomic::{AtomicBool, Ordering}; 8 | use std::sync::Arc; 9 | use std::time::Instant; 10 | use tokio::sync::mpsc::unbounded_channel; 11 | 12 | use super::SearchInfo; 13 | 14 | #[derive(Debug)] 15 | pub struct SearchResult { 16 | pub matches: Vec, 17 | pub total_matched: u64, 18 | pub total_processed: u64, 19 | } 20 | 21 | pub async fn cli_search(paths: Vec, matcher: Matcher) -> SearchResult { 22 | let (sender, mut receiver) = unbounded_channel(); 23 | 24 | let stop_signal = Arc::new(AtomicBool::new(false)); 25 | 26 | let search_info = SearchInfo::new(); 27 | 28 | { 29 | let search_info = search_info.clone(); 30 | std::thread::Builder::new() 31 | .name("searcher-worker".into()) 32 | .spawn(move || { 33 | StoppableSearchImpl::new(paths, matcher, sender, stop_signal, usize::MAX) 34 | .run(search_info) 35 | }) 36 | .expect("Failed to spawn searcher worker thread"); 37 | } 38 | 39 | let mut matches = Vec::new(); 40 | let mut total_matched = 0; 41 | 42 | let mut past = Instant::now(); 43 | 44 | while let Some(file_result) = receiver.recv().await { 45 | matches.push(file_result); 46 | total_matched += 1; 47 | let total_processed = search_info.total_processed.load(Ordering::Relaxed); 48 | 49 | if total_matched % 16 == 0 || total_processed.is_multiple_of(16) { 50 | let now = Instant::now(); 51 | if now > past + UPDATE_INTERVAL { 52 | println!("total_matched: {total_matched:?}, total_processed: {total_processed:?}"); 53 | past = now; 54 | } 55 | } 56 | } 57 | 58 | let total_processed = search_info.total_processed.load(Ordering::SeqCst) as u64; 59 | 60 | SearchResult { 61 | matches, 62 | total_matched, 63 | total_processed, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /autoload/clap/plugin/colorizer.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | 3 | scriptencoding utf-8 4 | 5 | let s:save_cpo = &cpoptions 6 | set cpoptions&vim 7 | 8 | if has('nvim') 9 | let s:colorizer_ns_id = nvim_create_namespace('clap_colorizer') 10 | else 11 | let s:types = [] 12 | endif 13 | 14 | function! s:create_new_group(bufnr, highlight_group) abort 15 | execute printf( 16 | \ 'hi %s guibg=%s ctermbg=%d', 17 | \ a:highlight_group.name, 18 | \ a:highlight_group.guibg, 19 | \ a:highlight_group.ctermbg, 20 | \ ) 21 | 22 | if !has('nvim') && index(s:types, a:highlight_group.name) == -1 23 | call add(s:types, a:highlight_group.name) 24 | call prop_type_add(a:highlight_group.name, {'highlight': a:highlight_group.name}) 25 | endif 26 | endfunction 27 | 28 | " lnum and col is 0-based. 29 | function! s:add_highlight(bufnr, line_number, color_info) abort 30 | if !hlexists(a:color_info.highlight_group.name) 31 | call s:create_new_group(a:bufnr, a:color_info.highlight_group) 32 | endif 33 | 34 | if has('nvim') 35 | call nvim_buf_add_highlight(a:bufnr, s:colorizer_ns_id, 36 | \ a:color_info.highlight_group.name, 37 | \ a:line_number, 38 | \ a:color_info.col, 39 | \ a:color_info.col + a:color_info.length, 40 | \ ) 41 | else 42 | call prop_add(a:line_number + 1, a:color_info.col + 1, { 43 | \ 'type': a:color_info.highlight_group.name, 44 | \ 'length': a:color_info.length, 45 | \ 'bufnr': a:bufnr, 46 | \ }) 47 | endif 48 | endfunction 49 | 50 | function! clap#plugin#colorizer#add_highlights(bufnr, highlights) abort 51 | for [line_number, color_infos] in items(a:highlights) 52 | call map(color_infos, 's:add_highlight(a:bufnr, str2nr(line_number), v:val)') 53 | endfor 54 | endfunction 55 | 56 | function! clap#plugin#colorizer#clear_highlights(bufnr) abort 57 | if has('nvim') 58 | call nvim_buf_clear_namespace(a:bufnr, s:colorizer_ns_id, 0, -1) 59 | else 60 | call prop_remove({ 'types': s:types, 'all': v:true, 'bufnr': a:bufnr } ) 61 | endif 62 | endfunction 63 | 64 | let &cpoptions = s:save_cpo 65 | unlet s:save_cpo 66 | -------------------------------------------------------------------------------- /autoload/clap/provider/tagfiles.vim: -------------------------------------------------------------------------------- 1 | " Author: romgrk 2 | " Description: Project-wide tags 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:provider = {} 8 | 9 | function! s:provider.on_typed() abort 10 | call clap#client#notify_provider('on_typed') 11 | endfunction 12 | 13 | function! s:provider.init() abort 14 | call clap#client#notify_on_init() 15 | endfunction 16 | 17 | function! s:provider.sink(selected) abort 18 | call s:jump_to(s:extract(a:selected)) 19 | try 20 | silent! call vista#util#Blink(2, 200) 21 | catch '*' | endtry 22 | endfunction 23 | 24 | function! s:provider.on_move() abort 25 | let [path, address] = s:extract(g:clap.display.getcurline()) 26 | call clap#preview#file_at(path, address) 27 | endfunction 28 | 29 | let s:provider.on_move_async = function('clap#impl#on_move#async') 30 | let s:provider.enable_rooter = v:true 31 | let s:provider.support_open_action = v:true 32 | let s:provider.syntax = 'clap_tagfiles' 33 | 34 | let g:clap#provider#tagfiles# = s:provider 35 | 36 | " Helpers 37 | 38 | function! s:extract(tag_row) abort 39 | let parts = split(a:tag_row, '::::') 40 | " let line = parts[0] 41 | let file = parts[1] 42 | let address = parts[2] 43 | if address[0] ==# '/' 44 | " Format: `/^function example()$/` 45 | " inside the `/^` and `$/` is like nomagic, but some ctags program 46 | " put the ^ and $ anyway. 47 | let address = address[1:-2] 48 | if address[0] ==# '^' 49 | let address = '\v^\V' . address[1:] 50 | else 51 | let address = '\V' . address 52 | end 53 | if address[-1:] ==# '$' 54 | let address = address[:-2] . '\v$' 55 | end 56 | else 57 | let address = str2nr(matchstr(address, '\v\d+')) 58 | end 59 | return [file, address] 60 | endfunction 61 | 62 | function! s:jump_to(position) abort 63 | let [file, address] = a:position 64 | 65 | execute 'edit' file 66 | 67 | if type(address) == v:t_number 68 | let lnum = address 69 | execute 'normal! ' lnum 'gg' 70 | else 71 | let g:cp = address 72 | let lnum = search(address) 73 | end 74 | 75 | execute 'normal! ^zvzz' 76 | endfunc 77 | 78 | let &cpoptions = s:save_cpo 79 | unlet s:save_cpo 80 | -------------------------------------------------------------------------------- /autoload/clap/popup/action.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Action dialog based on vim popup. 3 | scriptencoding utf-8 4 | 5 | let s:save_cpo = &cpoptions 6 | set cpoptions&vim 7 | 8 | function! s:action_filter(id, key) abort 9 | " Handle shortcut key 10 | if has_key(s:key2choice, toupper(a:key)) 11 | call popup_close(a:id, s:key2choice[toupper(a:key)]) 12 | return 1 13 | endif 14 | 15 | " No shortcut, pass to generic filter 16 | return popup_filter_menu(a:id, a:key) 17 | endfunc 18 | 19 | function! s:action_callback(id, result) abort 20 | if a:result == -1 21 | return 22 | endif 23 | if has_key(s:provider_action, a:result) 24 | call s:provider_action[a:result]() 25 | else 26 | call clap#helper#echo_error('Invalid action choice:'.a:result) 27 | endif 28 | endfunction 29 | 30 | function! s:highlight_shortcut() abort 31 | call map(s:key_indices, 'matchaddpos("Function", [[v:key+1, v:val+1]])') 32 | endfunction 33 | 34 | function! clap#popup#action#invoke() abort 35 | let s:provider_action = g:clap.provider._().action 36 | if has_key(s:provider_action, 'title') 37 | let title = s:provider_action['title']() 38 | else 39 | let title = 'Choose action:' 40 | endif 41 | let choices = filter(keys(s:provider_action), 'v:val !~# "title"') 42 | 43 | let s:key2choice = {} 44 | let s:key_indices = [] 45 | for choice in choices 46 | let key_idx = stridx(choice, '&') 47 | if key_idx == -1 48 | call clap#helper#echo_error('choice does not contain &: '.choice) 49 | continue 50 | endif 51 | call add(s:key_indices, key_idx) 52 | let s:key2choice[choice[key_idx+1]] = choice 53 | endfor 54 | 55 | let display_menus = map(choices, "substitute(v:val, '&', '', '')") 56 | 57 | let dialog_winid = popup_menu(display_menus, { 58 | \ 'filter': function('s:action_filter'), 59 | \ 'callback': function('s:action_callback'), 60 | \ 'title': ' '.title.' ', 61 | \ 'zindex': 100000, 62 | \ 'borderchars': ['─', '│', '─', '│', '╭', '╮', '╯', '╰'], 63 | \ }) 64 | 65 | call win_execute(dialog_winid, 'call s:highlight_shortcut()') 66 | endfunction 67 | 68 | let &cpoptions = s:save_cpo 69 | unlet s:save_cpo 70 | -------------------------------------------------------------------------------- /autoload/clap/legacy/filter/sync/lua.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Lua implementation of fzy filter algorithm. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | if has('nvim-0.5') 8 | 9 | function! clap#legacy#filter#sync#lua#(query, candidates, _winwidth, enable_icon, match_scope) abort 10 | let g:_clap_lua_query = a:query 11 | let g:_clap_lua_candidates = a:candidates 12 | let g:_clap_lua_enable_icon = a:enable_icon 13 | let g:_clap_lua_match_scope = a:match_scope 14 | 15 | lua << EOF 16 | local fzy_filter = require('fzy_filter') 17 | vim.g.__clap_fuzzy_matched_indices, vim.g._clap_lua_filtered = 18 | fzy_filter.do_fuzzy_match(vim.g._clap_lua_query, vim.g._clap_lua_candidates, vim.g._clap_lua_enable_icon, vim.g._clap_lua_match_scope) 19 | EOF 20 | 21 | return g:_clap_lua_filtered 22 | endfunction 23 | 24 | else 25 | 26 | function! s:deconstrcut(joint_indices) abort 27 | return map(split(a:joint_indices, ','), 'str2nr(v:val)') 28 | endfunction 29 | 30 | function! clap#legacy#filter#sync#lua#(query, candidates, _winwidth, enable_icon, match_scope) abort 31 | lua << EOF 32 | local fzy_filter = require('fzy_filter') 33 | 34 | local candidates = vim.eval('a:candidates') 35 | 36 | local lines = {} 37 | for i = #candidates-1, 0, -1 do 38 | table.insert(lines, candidates[i]) 39 | end 40 | 41 | matched_indices, _clap_lua_filtered = 42 | fzy_filter.do_fuzzy_match(vim.eval('a:query'), lines, vim.eval('a:enable_icon'), vim.eval('a:match_scope')) 43 | 44 | __clap_fuzzy_matched_indices = {} 45 | for i, v1 in ipairs(matched_indices) do 46 | local joint_indices = '' 47 | for _, v2 in ipairs(v1) do 48 | joint_indices = joint_indices .. v2 .. ',' 49 | end 50 | table.insert(__clap_fuzzy_matched_indices, joint_indices) 51 | end 52 | EOF 53 | 54 | " TODO: vim.list() can not work with a List of List. 55 | " echom string(luaeval('vim.list(__clap_fuzzy_matched_indices)')) 56 | 57 | let g:__clap_fuzzy_matched_indices = map(luaeval('vim.list(__clap_fuzzy_matched_indices)'), 's:deconstrcut(v:val)') 58 | 59 | return luaeval('vim.list(_clap_lua_filtered)') 60 | endfunction 61 | 62 | endif 63 | 64 | let &cpoptions = s:save_cpo 65 | unlet s:save_cpo 66 | -------------------------------------------------------------------------------- /autoload/clap/impl/on_move.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: CursorMoved handler 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:on_move_timer = -1 8 | let s:on_move_delay = get(g:, 'clap_on_move_delay', 300) 9 | 10 | function! s:sync_run_with_delay() abort 11 | if s:on_move_timer != -1 12 | call timer_stop(s:on_move_timer) 13 | endif 14 | let s:on_move_timer = timer_start(s:on_move_delay, { -> g:clap.provider._().on_move() }) 15 | endfunction 16 | 17 | if clap#maple#is_available() 18 | " Deprecated. 19 | function! clap#impl#on_move#handler(result, error) abort 20 | if a:error isnot v:null 21 | return 22 | endif 23 | call clap#picker#update_preview(has_key(a:result, 'result') ? a:result.result : a:result) 24 | endfunction 25 | 26 | function! s:dispatch_on_move_impl() abort 27 | if has_key(g:clap.provider._(), 'on_move_async') 28 | call g:clap.provider._().on_move_async() 29 | else 30 | call s:sync_run_with_delay() 31 | endif 32 | endfunction 33 | 34 | function! clap#impl#on_move#async() abort 35 | call clap#client#notify_provider('on_move') 36 | endfunction 37 | else 38 | function! s:dispatch_on_move_impl() abort 39 | call s:sync_run_with_delay() 40 | endfunction 41 | 42 | function! clap#impl#on_move#async() abort 43 | endfunction 44 | 45 | function! clap#impl#on_move#handler(_result, _error) abort 46 | endfunction 47 | endif 48 | 49 | function! clap#impl#on_move#invoke() abort 50 | if get(g:, '__clap_has_no_matches', v:false) 51 | return 52 | endif 53 | " Currently the on_move impl is for preview only. 54 | if !clap#preview#is_enabled() 55 | return 56 | endif 57 | 58 | if has_key(g:clap.provider._(), 'on_move_async') 59 | call g:clap.provider._().on_move_async() 60 | elseif has_key(g:clap.provider._(), 'on_move') 61 | call s:dispatch_on_move_impl() 62 | endif 63 | endfunction 64 | 65 | function! clap#impl#on_move#invoke_async() abort 66 | if get(g:, '__clap_has_no_matches', v:false) 67 | return 68 | endif 69 | if has_key(g:clap.provider._(), 'on_move_async') 70 | call g:clap.provider._().on_move_async() 71 | endif 72 | endfunction 73 | 74 | let &cpoptions = s:save_cpo 75 | unlet s:save_cpo 76 | -------------------------------------------------------------------------------- /pythonx/clap/test_fzy_with_rust.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import itertools 5 | import random 6 | import re 7 | import string 8 | 9 | import fuzzymatch_rs 10 | from fzy_impl import fzy_scorer 11 | 12 | 13 | def fuzzy_match_py(query, candidates): 14 | scored = [] 15 | 16 | for c in candidates: 17 | score, indices = fzy_scorer(query, c) 18 | if score != float("-inf"): 19 | scored.append({'score': score, 'indices': indices, 'text': c}) 20 | 21 | ranked = sorted(scored, key=lambda x: x['score'], reverse=True) 22 | 23 | indices = [] 24 | filtered = [] 25 | for r in ranked: 26 | filtered.append(r['text']) 27 | indices.append(r['indices']) 28 | 29 | return (indices, filtered) 30 | 31 | 32 | query = 'sr' 33 | candidates = open('../../test/testdata.txt', 'r').read().split('\n') 34 | 35 | print(fuzzy_match_py(query, candidates)) 36 | print(fuzzymatch_rs.fuzzy_match(query, candidates)) 37 | 38 | 39 | def test_pure_python_10000(benchmark): 40 | print(benchmark(fuzzy_match_py, query, candidates[:10000])) 41 | 42 | 43 | def test_rust_10000(benchmark): 44 | print(benchmark(fuzzymatch_rs.fuzzy_match, query, candidates[:10000])) 45 | 46 | 47 | def test_pure_python_100000(benchmark): 48 | print(benchmark(fuzzy_match_py, query, candidates[:100000])) 49 | 50 | 51 | def test_rust_100000(benchmark): 52 | print(benchmark(fuzzymatch_rs.fuzzy_match, query, candidates[:100000])) 53 | 54 | 55 | def test_pure_python_200000(benchmark): 56 | print(benchmark(fuzzy_match_py, query, candidates[:200000])) 57 | 58 | 59 | def test_rust_200000(benchmark): 60 | print(benchmark(fuzzymatch_rs.fuzzy_match, query, candidates[:200000])) 61 | 62 | 63 | # This would cost more than 30 seconds for Python. 64 | # def test_pure_python_500000(benchmark): 65 | # print(benchmark(fuzzy_match_py, query, candidates[:500000])) 66 | 67 | # def test_rust_500000(benchmark): 68 | # print(benchmark(fuzzymatch_rs.fuzzy_match, query, candidates[:500000])) 69 | 70 | # def test_pure_python_800000(benchmark): 71 | # print(benchmark(fuzzy_match_py, query, candidates[:800000])) 72 | 73 | # def test_rust_800000(benchmark): 74 | # print(benchmark(fuzzymatch_rs.fuzzy_match, query, candidates[:800000])) 75 | -------------------------------------------------------------------------------- /autoload/clap/provider/lines.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the lines of all loaded buffer. 3 | 4 | scriptencoding utf-8 5 | 6 | let s:save_cpo = &cpoptions 7 | set cpoptions&vim 8 | 9 | let s:lines = {} 10 | 11 | function! s:lines.sink(selected) abort 12 | let splitted = split(a:selected) 13 | let bufnr = splitted[0][1:-2] 14 | let lnum = str2nr(splitted[2]) 15 | execute 'b' bufnr 16 | silent call cursor(lnum, 1) 17 | normal! ^zvzz 18 | endfunction 19 | 20 | function! s:buflisted() abort 21 | return filter(range(1, bufnr('$')), 'buflisted(v:val) && getbufvar(v:val, "&filetype") !=# "qf"') 22 | endfunction 23 | 24 | function! s:bufnr_display(bufnr) abort 25 | let bufnr = str2nr(a:bufnr) 26 | if bufnr < 10 27 | return '['.bufnr.']'.' ' 28 | elseif bufnr < 100 29 | return '['.bufnr.']'.' ' 30 | else 31 | return '['.bufnr.']' 32 | endif 33 | endfunction 34 | 35 | function! s:lines.source() abort 36 | let cur = [] 37 | let rest = [] 38 | let buf = bufnr('') 39 | 40 | let buflisted = s:buflisted() 41 | 42 | let longest_name = 0 43 | let bufnames = {} 44 | for b in buflisted 45 | let bp = pathshorten(fnamemodify(bufname(b), ':~:.')) 46 | let longest_name = max([longest_name, len(bp)]) 47 | let bufnames[b] = bp 48 | endfor 49 | 50 | let len_bufnames = min([15, longest_name]) 51 | 52 | for b in buflisted 53 | let lines = getbufline(b, 1, '$') 54 | if empty(lines) 55 | let path = fnamemodify(bufname(b), ':p') 56 | let lines = filereadable(path) ? readfile(path) : [] 57 | endif 58 | 59 | let bufname = bufnames[b] 60 | if len(bufname) > len_bufnames + 1 61 | let bufname = '…' . bufname[-len_bufnames+1:] 62 | endif 63 | let bufname = printf('%'.len_bufnames.'s', bufname) 64 | 65 | let b_display = s:bufnr_display(b) 66 | let linefmt = '%s %s %4d %s' 67 | call extend(b == buf ? cur : rest, 68 | \ filter( 69 | \ map(lines, '(empty(v:val)) ? "" : printf(linefmt, b_display, bufname, v:key + 1, v:val)'), 70 | \ '!empty(v:val)')) 71 | endfor 72 | 73 | return extend(cur, rest) 74 | endfunction 75 | 76 | let s:lines.syntax = 'clap_lines' 77 | let g:clap#provider#lines# = s:lines 78 | 79 | let &cpoptions = s:save_cpo 80 | unlet s:save_cpo 81 | -------------------------------------------------------------------------------- /autoload/clap/provider/windows.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: List the windows. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:windows = {} 8 | 9 | function! s:jump(w, t) abort 10 | execute a:t.'tabnext' 11 | execute a:w.'wincmd w' 12 | endfunction 13 | 14 | function! s:get_clap_winids() abort 15 | let clap_winids = map( 16 | \ filter(['display', 'input', 'spinner', 'preview'], 'exists("g:clap.".v:val.".winid")'), 17 | \ 'g:clap[v:val].winid' 18 | \ ) 19 | if exists('g:__clap_indicator_bufnr') 20 | call extend(clap_winids, win_findbuf(g:__clap_indicator_bufnr)) 21 | endif 22 | return clap_winids 23 | endfunction 24 | 25 | function! s:format_win(winid) abort 26 | let buf = winbufnr(a:winid) 27 | let modified = getbufvar(buf, '&modified') 28 | let name = bufname(buf) 29 | let name = empty(name) ? '[No Name]' : name 30 | let active = a:winid == g:clap.start.winid 31 | return (active? '> ' : ' ') . name . (modified? ' [+]' : '') 32 | endfunction 33 | 34 | function! s:windows.source() abort 35 | let clap_winids = s:get_clap_winids() 36 | let lines = [] 37 | for t in range(1, tabpagenr('$')) 38 | for w in range(1, tabpagewinnr(t, '$')) 39 | " Skip Clap windows 40 | let winid = win_getid(w, t) 41 | if index(clap_winids, winid) != -1 42 | continue 43 | endif 44 | call add(lines, printf('%s %s %s', printf('%3d', t), printf('%3d', w), s:format_win(winid))) 45 | endfor 46 | endfor 47 | return lines 48 | endfunction 49 | 50 | function! s:parse_win(line) abort 51 | let tab_win = matchlist(a:line, '^ *\([0-9]\+\) *\([0-9]\+\)') 52 | return [tab_win[2], tab_win[1]] 53 | endfunction 54 | 55 | function! s:windows.on_move() abort 56 | let [win, tab] = s:parse_win(g:clap.display.getcurline()) 57 | let winid = win_getid(win, tab) 58 | let fpath = bufname(winbufnr(winid)) 59 | let lnum = has('nvim') ? nvim_win_get_cursor(winid)[0] : line('.', winid) 60 | call clap#preview#file_at(fpath, lnum) 61 | endfunction 62 | 63 | function! s:windows.sink(line) abort 64 | let [win, tab] = s:parse_win(a:line) 65 | call s:jump(win, tab) 66 | endfunction 67 | 68 | let g:clap#provider#windows# = s:windows 69 | 70 | let &cpoptions = s:save_cpo 71 | unlet s:save_cpo 72 | -------------------------------------------------------------------------------- /docs/src/guide/install_rust.md: -------------------------------------------------------------------------------- 1 | # Install Rust Dependency 2 | 3 | You can download the prebuilt binary from GitHub or compile the binary locally on your own. 4 | 5 | ### Compile Rust Binary Locally 6 | 7 | Refer to [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) if you haven't installed Rust on your system. 8 | 9 | Assuming Rust has already been installed on your system, specifically, `cargo` executable exists, you can have several ways to compile the binary: 10 | 11 | - Use this helper function `:call clap#installer#build_maple()` within Vim/NeoVim. 12 | 13 | - Run `make` under the clap plugin directory (macOS and Linux). 14 | 15 | - Run the `cargo` command on your own: 16 | 17 | ```bash 18 | cd path/to/vim-clap 19 | 20 | # Compile the release build, you can find the compiled executable at target/release/maple. 21 | cargo build --release 22 | ``` 23 | 24 | ### Compile Rust binary via Docker (Linux Only) 25 | 26 | If you run into the libssl error when using the prebuilt binary from GitHub release, you can try building a static Rust binary: 27 | 28 | ```bash 29 | $ cd path/to/vim-clap 30 | $ docker run --rm -it -v "$(pwd)":/volume clux/muslrust cargo build --profile production --locked 31 | $ cp target/x86_64-unknown-linux-musl/production/maple bin/maple 32 | # See if it really works 33 | $ ./bin/maple version 34 | ``` 35 | 36 | ### Download Prebuilt binary 37 | 38 | The prebuilt binary is available from GitHub release. You can call `:call clap#installer#download_binary()` in Vim/NeoVim, or do it manually as follows. 39 | 40 | #### Quick Downloader 41 | 42 | The scripts to download the prebuilt binary quickly are provided out of the box. The downloaded executable can be found at `bin/maple` on success. 43 | 44 | - Unix: `$ bash install.sh` 45 | - Windows: Run `install.ps1` in the powershell. 46 | 47 | #### Download Prebuilt Binary By Hand 48 | 49 | 1. Download the binary from the latest release [https://github.com/liuchengxu/vim-clap/releases](https://github.com/liuchengxu/vim-clap/releases) according to your system. 50 | 2. Rename the downloaded binary to: 51 | - Unix: `maple` 52 | - Windows: `maple.exe` 53 | 3. Move `maple`/`maple.exe` to `bin` directory. Don't forget to assign execute permission to `maple` via `chmod a+x bin/maple` if you are using the Unix system. 54 | -------------------------------------------------------------------------------- /crates/matcher/src/matchers/bonus_matcher/bonus/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cwd; 2 | pub mod filename; 3 | pub mod language; 4 | pub mod recent_files; 5 | 6 | use self::cwd::Cwd; 7 | use self::filename::calc_bonus_file_name; 8 | use self::language::Language; 9 | use self::recent_files::RecentFiles; 10 | use crate::Score; 11 | use std::sync::Arc; 12 | use types::ClapItem; 13 | 14 | /// Tweak the matching score calculated by the base match algorithm. 15 | #[derive(Debug, Clone, Default)] 16 | pub enum Bonus { 17 | /// Give a bonus if the item is an absolute file path and matches the cwd. 18 | Cwd(Cwd), 19 | 20 | /// Give a bonus if the item contains a language keyword. 21 | Language(Language), 22 | 23 | /// Give a bonus if the item is in the list of recently opened files. 24 | RecentFiles(RecentFiles), 25 | 26 | /// Give a bonus if the item is a file path and the matches are in the file name. 27 | /// 28 | /// Ref https://github.com/liuchengxu/vim-clap/issues/561 29 | FileName, 30 | 31 | /// No additional bonus. 32 | #[default] 33 | None, 34 | } 35 | 36 | impl> From for Bonus { 37 | fn from(s: T) -> Self { 38 | match s.as_ref().to_lowercase().as_str() { 39 | "filename" => Self::FileName, 40 | _ => Self::None, 41 | } 42 | } 43 | } 44 | 45 | impl Bonus { 46 | /// Calculates the bonus score given the match result of base algorithm. 47 | pub fn item_bonus_score( 48 | &self, 49 | item: &Arc, 50 | score: Score, 51 | indices: &[usize], 52 | ) -> Score { 53 | // Ignore the long line. 54 | if item.raw_text().len() > 1024 { 55 | return 0; 56 | } 57 | 58 | self.text_bonus_score(item.bonus_text(), score, indices) 59 | } 60 | 61 | pub fn text_bonus_score(&self, bonus_text: &str, score: Score, indices: &[usize]) -> Score { 62 | match self { 63 | Self::Cwd(cwd) => cwd.calc_bonus(bonus_text, score), 64 | Self::Language(language) => language.calc_bonus(bonus_text, score), 65 | Self::RecentFiles(recent_files) => recent_files.calc_bonus(bonus_text, score), 66 | Self::FileName => calc_bonus_file_name(bonus_text, score, indices), 67 | Self::None => 0, 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/code_tools/src/linting/linters/python.rs: -------------------------------------------------------------------------------- 1 | use crate::linting::{Code, Diagnostic, DiagnosticSpan, Linter, Severity}; 2 | use serde::Deserialize; 3 | use std::path::Path; 4 | 5 | #[derive(Debug, Deserialize)] 6 | struct Location { 7 | column: usize, 8 | row: usize, 9 | } 10 | 11 | // https://github.com/astral-sh/ruff/blob/b3a6f0ce81bfd547d8a01bfe5dee61cb1b8e73b3/crates/ruff_linter/src/message/json.rs#L80 12 | // 13 | // {"cell":null,"code":"E701","end_location":{"column":50,"row":36},"filename":"/Users/xuliucheng/.vim/plugged/vim-clap/pythonx/clap/fzy.py","fix":null,"location":{"column":49,"row":36},"message":"Multiple statements on one line (colon)","noqa_row":36,"url":"https://docs.astral.sh/ruff/rules/multiple-statements-on-one-line-colon"} 14 | #[derive(Debug, Deserialize)] 15 | struct RuffJsonMessage { 16 | code: String, 17 | end_location: Location, 18 | // filename: String, 19 | // fix: Option, 20 | location: Location, 21 | message: String, 22 | // url: String, 23 | } 24 | 25 | impl RuffJsonMessage { 26 | fn into_diagnostic(self) -> Diagnostic { 27 | let severity = if self.code.starts_with('E') { 28 | Severity::Error 29 | } else if self.code.starts_with('W') { 30 | Severity::Warning 31 | } else { 32 | Severity::Unknown 33 | }; 34 | 35 | Diagnostic { 36 | spans: vec![DiagnosticSpan { 37 | line_start: self.location.row, 38 | line_end: self.end_location.row, 39 | column_start: self.location.column, 40 | column_end: self.end_location.column, 41 | }], 42 | code: Code { code: self.code }, 43 | severity, 44 | message: self.message, 45 | } 46 | } 47 | } 48 | 49 | pub struct Ruff; 50 | 51 | impl Linter for Ruff { 52 | const EXE: &'static str = "ruff"; 53 | 54 | fn add_args(cmd: &mut tokio::process::Command, source_file: &Path) { 55 | cmd.arg("check") 56 | .arg("--output-format=json-lines") 57 | .arg(source_file); 58 | } 59 | 60 | fn parse_line(&self, line: &[u8]) -> Option { 61 | serde_json::from_slice::(line) 62 | .map(RuffJsonMessage::into_diagnostic) 63 | .ok() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /autoload/clap/job.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: APIs for working with Asynchronous jobs. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | if has('nvim') 8 | function! clap#job#exists(job_id) abort 9 | return a:job_id > 0 10 | endfunction 11 | 12 | function! clap#job#stop(job_id) abort 13 | silent! call jobstop(a:job_id) 14 | endfunction 15 | 16 | function! clap#job#start_buffered(cmd, OnEvent) abort 17 | let job_id = jobstart(a:cmd, { 18 | \ 'on_exit': a:OnEvent, 19 | \ 'on_stdout': a:OnEvent, 20 | \ 'on_stderr': a:OnEvent, 21 | \ 'stdout_buffered': v:true, 22 | \ }) 23 | return job_id 24 | endfunction 25 | else 26 | let s:job_id_map = {} 27 | 28 | function! clap#job#exists(job_id) abort 29 | return a:job_id > -1 30 | endfunction 31 | 32 | function! clap#job#stop(job_id) abort 33 | " Ignore the invalid job_id 34 | if has_key(s:job_id_map, a:job_id) 35 | " Kill it! 36 | call job_stop(remove(s:job_id_map, a:job_id), 'kill') 37 | endif 38 | endfunction 39 | 40 | function! clap#job#vim8_job_id_of(channel) abort 41 | return ch_info(a:channel)['id'] 42 | endfunction 43 | 44 | function! clap#job#get_vim8_job_id(job) abort 45 | return ch_info(job_getchannel(a:job))['id'] 46 | endfunction 47 | 48 | " wrap_cmd is only necessary when cmd is a String, otherwise vim panics. 49 | if has('win32') 50 | function! clap#job#wrap_cmd(cmd) abort 51 | return &shell . ' ' . &shellcmdflag . ' ' . a:cmd 52 | endfunction 53 | else 54 | function! clap#job#wrap_cmd(cmd) abort 55 | return split(&shell) + split(&shellcmdflag) + [a:cmd] 56 | endfunction 57 | endif 58 | 59 | function! clap#job#start_buffered(cmd_list, CloseCallback) abort 60 | let job = job_start(a:cmd_list, { 61 | \ 'in_io': 'null', 62 | \ 'close_cb': a:CloseCallback, 63 | \ 'noblock': 1, 64 | \ 'mode': 'raw', 65 | \ }) 66 | let job_id = ch_info(job_getchannel(job))['id'] 67 | let s:job_id_map[job_id] = job 68 | return job_id 69 | endfunction 70 | 71 | function! clap#job#track(job_id, job) abort 72 | let s:job_id_map[a:job_id] = a:job 73 | endfunction 74 | endif 75 | 76 | let &cpoptions = s:save_cpo 77 | unlet s:save_cpo 78 | -------------------------------------------------------------------------------- /test/bench/python/test_fuzzy_filter.vim: -------------------------------------------------------------------------------- 1 | " With my machine: 2 | " /usr in macOS has over 300,000 files. 3 | " /usr in Ubuntu18.04 has less than 300,000 files 4 | let g:clap_builtin_fuzzy_filter_threshold = 400000 5 | 6 | if has('macunix') 7 | function! s:RunClap() abort 8 | silent Clap files ~/src/github.com 9 | endfunction 10 | else 11 | function! s:RunClap() abort 12 | silent Clap files /usr 13 | endfunction 14 | endif 15 | 16 | function RunInputOnce() abort 17 | call s:RunClap() 18 | if has('nvim') 19 | call timer_start(5000, { -> feedkeys("sr") } ) 20 | else 21 | " wait for the forerunner job and then input something. 22 | call timer_start(15000, { -> feedkeys("sr", "xt") } ) 23 | endif 24 | call timer_start(18000, { -> writefile(['total items: '.g:clap.display.initial_size], 'stats.log', 'a') }) 25 | call timer_start(20000, { -> execute("qa!") } ) 26 | endfunction 27 | 28 | function RunInputMulti() abort 29 | call s:RunClap() 30 | if has('nvim') 31 | call timer_start(5000, { -> feedkeys("s") } ) 32 | call timer_start(10000, { -> feedkeys("r") } ) 33 | call timer_start(15000, { -> feedkeys("q") } ) 34 | else 35 | call timer_start(5000, { -> feedkeys("s", "xt") } ) 36 | call timer_start(10000, { -> feedkeys("r", "xt") } ) 37 | call timer_start(15000, { -> feedkeys("q", "xt") } ) 38 | endif 39 | call timer_start(18000, { -> writefile(['total items: '.g:clap.display.initial_size], 'stats.log', 'a') }) 40 | call timer_start(20000, { -> execute("qa!") } ) 41 | endfunction 42 | 43 | function! PythonFilter(query, candidates) abort 44 | call clap#legacy#filter#sync#python#(a:query, a:candidates, 60, v:true) 45 | endfunction 46 | 47 | function RunBench100000() abort 48 | let candidates = readfile(expand('testdata.txt'), '', 100000) 49 | call PythonFilter('sr', candidates) 50 | call timer_start(10000, { -> execute("qa!") } ) 51 | endfunction 52 | 53 | function RunBench200000() abort 54 | let candidates = readfile(expand('testdata.txt'), '', 200000) 55 | call PythonFilter('sr', candidates) 56 | call timer_start(15000, { -> execute("qa!") } ) 57 | endfunction 58 | 59 | function RunBench300000() abort 60 | let candidates = readfile(expand('testdata.txt'), '', 300000) 61 | call PythonFilter('sr', candidates) 62 | call timer_start(20000, { -> execute("qa!") } ) 63 | endfunction 64 | -------------------------------------------------------------------------------- /autoload/clap/provider/quickfix.vim: -------------------------------------------------------------------------------- 1 | " Author: KITAGAWA Yasutaka 2 | " Description: List the entries of the quickfix list. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:quickfix = {} 8 | 9 | function! s:quickfix.source() abort 10 | " Ignore quickfix list entries with non-existing buffer number. 11 | let qflist = filter(getqflist(), 'v:val["bufnr"]') 12 | 13 | return map(qflist, 's:qf_fmt_entry(v:val)') 14 | endfunction 15 | 16 | function! clap#provider#quickfix#into_qf_line(qf_entry) abort 17 | return s:qf_fmt_entry(a:qf_entry) 18 | endfunction 19 | 20 | function! clap#provider#quickfix#extract_position(selected) abort 21 | return s:extract_position(a:selected) 22 | endfunction 23 | 24 | function! s:qf_fmt_entry(qf_entry) abort 25 | let path = bufname(a:qf_entry['bufnr']) 26 | let line_col = a:qf_entry['lnum'].' col '.a:qf_entry['col'] 27 | return path.'|'.line_col.'| '.trim(s:qf_fmt_text(a:qf_entry['text'])) 28 | endfunction 29 | 30 | function! s:qf_fmt_text(text) abort 31 | return substitute(a:text, '\n\( \|\t\)*', ' ', 'g') 32 | endfunction 33 | 34 | function! s:extract_position(selected) abort 35 | let [fpath, line_col] = split(a:selected, '|')[:1] 36 | let [lnum, column] = split(line_col, ' col ') 37 | return [fpath, lnum, column] 38 | endfunction 39 | 40 | function! s:quickfix.sink(selected) abort 41 | let [fpath, lnum, column] = s:extract_position(a:selected) 42 | execute 'edit' fpath 43 | noautocmd call cursor(lnum, column) 44 | endfunction 45 | 46 | function! s:quickfix.on_move() abort 47 | let [fpath, lnum, column] = s:extract_position(g:clap.display.getcurline()) 48 | 49 | if lnum == 0 || column == 0 50 | call clap#preview#file(fpath) 51 | else 52 | call clap#preview#file_at(fpath, lnum) 53 | endif 54 | endfunction 55 | 56 | function! s:quickfix.on_move_async() abort 57 | call clap#client#request_async('quickfix', function('clap#impl#on_move#handler'), { 58 | \ 'curline': g:clap.display.getcurline(), 59 | \ 'cwd': clap#rooter#working_dir(), 60 | \ 'winwidth': winwidth(g:clap.display.winid), 61 | \ 'winheight': winheight(g:clap.display.winid), 62 | \ }) 63 | endfunction 64 | 65 | let s:quickfix.syntax = 'qf' 66 | let g:clap#provider#quickfix# = s:quickfix 67 | 68 | let &cpoptions = s:save_cpo 69 | unlet s:save_cpo 70 | -------------------------------------------------------------------------------- /autoload/clap/legacy/filter/sync/python.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Python and Rust implementation of fzy filter algorithm. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | let s:py_exe = has('python3') ? 'python3' : 'python' 8 | let s:pyfile = has('python3') ? 'py3file' : 'pyfile' 9 | let s:plugin_root_dir = fnamemodify(g:clap#autoload_dir, ':h') 10 | 11 | if has('win32') 12 | let s:LIB = '\pythonx\clap\fuzzymatch_rs.pyd' 13 | let s:SETUP_PY = '\setup_python.py' 14 | else 15 | let s:LIB = '/pythonx/clap/fuzzymatch_rs.so' 16 | let s:SETUP_PY = '/setup_python.py' 17 | endif 18 | 19 | " Import pythonx/clap 20 | if !has('nvim') 21 | execute s:pyfile s:plugin_root_dir.s:SETUP_PY 22 | endif 23 | 24 | let s:has_py_dynamic_module = filereadable(s:plugin_root_dir.s:LIB) 25 | let s:using_dynamic_module = v:false 26 | 27 | " For test only 28 | if get(g:, 'clap_use_pure_python', 0) 29 | let s:py_fn = 'clap_fzy_py' 30 | else 31 | if s:has_py_dynamic_module 32 | let s:py_fn = 'clap_fzy_rs' 33 | let s:using_dynamic_module = v:true 34 | else 35 | let s:py_fn = 'clap_fzy_py' 36 | endif 37 | endif 38 | 39 | try 40 | execute s:py_exe 'from clap.fzy import' s:py_fn 41 | catch 42 | let s:using_dynamic_module = v:false 43 | endtry 44 | 45 | function! clap#legacy#filter#sync#python#has_dynamic_module() abort 46 | return s:has_py_dynamic_module 47 | endfunction 48 | 49 | if s:using_dynamic_module 50 | " Rust dynamic module has the feature of truncating the long lines to make fuzzy matched items visible. 51 | function! clap#legacy#filter#sync#python#(query, candidates, recent_files, context) abort 52 | " If the query is empty, neovim and vim's python client might crash. 53 | if a:query ==# '' 54 | return a:candidates 55 | endif 56 | " Possibly a bug of Neovim's Python provider, the result from Python can be null once passed to the vim side. 57 | let [g:__clap_fuzzy_matched_indices, filtered, g:__clap_lines_truncated_map] = pyxeval(s:py_fn.'()') 58 | return filtered 59 | endfunction 60 | else 61 | function! clap#legacy#filter#sync#python#(query, candidates, _recent_files, context) abort 62 | let [g:__clap_fuzzy_matched_indices, filtered] = pyxeval(s:py_fn.'()') 63 | return filtered 64 | endfunction 65 | endif 66 | 67 | let &cpoptions = s:save_cpo 68 | unlet s:save_cpo 69 | -------------------------------------------------------------------------------- /autoload/clap/themes/nord.vim: -------------------------------------------------------------------------------- 1 | " Author: Ihor Kalnytskyi 2 | " Description: Clap theme based on Nord theme. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | " colors from arcticicestudio/nord-vim 8 | let s:nord0_gui = '#2E3440' 9 | let s:nord1_gui = '#3B4252' 10 | let s:nord2_gui = '#434C5E' 11 | let s:nord3_gui = '#4C566A' 12 | let s:nord4_gui = '#D8DEE9' 13 | let s:nord5_gui = '#E5E9F0' 14 | let s:nord6_gui = '#ECEFF4' 15 | let s:nord7_gui = '#8FBCBB' 16 | let s:nord8_gui = '#88C0D0' 17 | let s:nord9_gui = '#81A1C1' 18 | let s:nord10_gui = '#5E81AC' 19 | let s:nord11_gui = '#BF616A' 20 | let s:nord12_gui = '#D08770' 21 | let s:nord13_gui = '#EBCB8B' 22 | let s:nord14_gui = '#A3BE8C' 23 | let s:nord15_gui = '#B48EAD' 24 | 25 | let s:nord1_term = '0' 26 | let s:nord3_term = '8' 27 | let s:nord5_term = '7' 28 | let s:nord6_term = '15' 29 | let s:nord7_term = '14' 30 | let s:nord8_term = '6' 31 | let s:nord9_term = '4' 32 | let s:nord10_term = '12' 33 | let s:nord11_term = '1' 34 | let s:nord12_term = '11' 35 | let s:nord13_term = '3' 36 | let s:nord14_term = '2' 37 | let s:nord15_term = '5' 38 | 39 | let s:palette = {} 40 | let s:palette.display = { 41 | \ 'guibg': s:nord1_gui, 42 | \ 'ctermbg': s:nord1_term, 43 | \ 'guifg': s:nord4_gui, 44 | \ 'ctermfg': 'NONE', 45 | \ } 46 | let s:palette.input = s:palette.display 47 | let s:palette.spinner = extend( 48 | \ { 49 | \ 'guifg': s:nord9_gui, 50 | \ 'ctermfg': s:nord9_term, 51 | \ 'gui': 'bold', 52 | \ 'cterm': 'bold', 53 | \ }, 54 | \ s:palette.input, 55 | \ 'keep' 56 | \ ) 57 | let s:palette.search_text = s:palette.input 58 | let s:palette.selected = { 59 | \ 'guibg': s:nord2_gui, 60 | \ 'guifg': s:nord4_gui, 61 | \ 'ctermbg': s:nord3_term, 62 | \ 'ctermfg': 'NONE', 63 | \ } 64 | let s:palette.selected_sign = s:palette.selected 65 | let s:palette.current_selection = extend( 66 | \ { 67 | \ 'gui': 'bold', 68 | \ 'cterm': 'bold', 69 | \ }, 70 | \ s:palette.selected, 71 | \ 'keep' 72 | \ ) 73 | let s:palette.current_selection_sign = s:palette.current_selection 74 | let s:palette.preview = { 75 | \ 'guibg': s:nord2_gui, 76 | \ 'ctermbg': s:nord3_term 77 | \ } 78 | 79 | let g:clap#themes#nord#palette = s:palette 80 | let g:clap_fuzzy_match_hl_groups = [ 81 | \ [s:nord8_term, s:nord8_gui], 82 | \ ] 83 | 84 | let &cpoptions = s:save_cpo 85 | unlet s:save_cpo 86 | -------------------------------------------------------------------------------- /crates/filter/src/sequential_source.rs: -------------------------------------------------------------------------------- 1 | use crate::MatchedItems; 2 | use matcher::Matcher; 3 | use std::io::BufRead; 4 | use std::path::PathBuf; 5 | use std::sync::Arc; 6 | use subprocess::Exec; 7 | use types::{ClapItem, MatchedItem, SourceItem}; 8 | 9 | /// [`SequentialSource`] provides an iterator of [`ClapItem`] which 10 | /// will be processed sequentially. 11 | #[derive(Debug)] 12 | pub enum SequentialSource>> { 13 | Iterator(I), 14 | Stdin, 15 | File(PathBuf), 16 | Exec(Box), 17 | } 18 | 19 | impl>> From for SequentialSource { 20 | fn from(fpath: PathBuf) -> Self { 21 | Self::File(fpath) 22 | } 23 | } 24 | 25 | impl>> From for SequentialSource { 26 | fn from(exec: Exec) -> Self { 27 | Self::Exec(Box::new(exec)) 28 | } 29 | } 30 | 31 | /// Filters items from a sequential source using the given matcher. 32 | pub fn filter_sequential>>( 33 | source: SequentialSource, 34 | matcher: Matcher, 35 | ) -> crate::Result> { 36 | let clap_item_stream: Box>> = match source { 37 | SequentialSource::Iterator(iter) => Box::new(iter), 38 | SequentialSource::Stdin => Box::new( 39 | std::io::stdin() 40 | .lock() 41 | .lines() 42 | .map_while(Result::ok) 43 | .map(|line| Arc::new(SourceItem::from(line)) as Arc), 44 | ), 45 | SequentialSource::File(path) => Box::new( 46 | std::io::BufReader::new(std::fs::File::open(path)?) 47 | .lines() 48 | .map_while(Result::ok) 49 | .map(|line| Arc::new(SourceItem::from(line)) as Arc), 50 | ), 51 | SequentialSource::Exec(exec) => Box::new( 52 | std::io::BufReader::new(exec.stream_stdout()?) 53 | .lines() 54 | .map_while(Result::ok) 55 | .map(|line| Arc::new(SourceItem::from(line)) as Arc), 56 | ), 57 | }; 58 | 59 | Ok(MatchedItems::from( 60 | clap_item_stream 61 | .filter_map(|item| matcher.match_item(item)) 62 | .collect::>(), 63 | ) 64 | .par_sort() 65 | .inner()) 66 | } 67 | -------------------------------------------------------------------------------- /crates/tree_sitter/benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use rand::Rng; 3 | use tree_sitter::{UncheckedUtf8CharIndices, Utf8CharIndices}; 4 | 5 | fn bench_utf8_char_indices(c: &mut Criterion) { 6 | const CHARSET: &[u8] = 7 | b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789)(*&^%$#@!~\n\ 8 | "; 9 | 10 | let multi_byte_set = String::from_utf8_lossy("你好,世界!".as_bytes()) 11 | .chars() 12 | .collect::>(); 13 | 14 | let mut rng = rand::thread_rng(); 15 | let input = std::iter::repeat_with(|| { 16 | if rand::random::() { 17 | let idx = rng.gen_range(0..CHARSET.len()); 18 | CHARSET[idx] as char 19 | } else { 20 | let idx = rng.gen_range(0..multi_byte_set.len()); 21 | multi_byte_set[idx] 22 | } 23 | }) 24 | .take(10_000) 25 | .collect::(); 26 | let input = input.as_bytes(); 27 | 28 | c.bench_function("Utf8CharIndices", |b| { 29 | b.iter(|| { 30 | for (index, ch) in Utf8CharIndices::new(black_box(input)) { 31 | black_box(index); 32 | black_box(ch); 33 | } 34 | }) 35 | }); 36 | 37 | c.bench_function("UncheckedUtf8CharIndices", |b| { 38 | b.iter(|| { 39 | for (index, ch) in UncheckedUtf8CharIndices::new(black_box(input)) { 40 | black_box(index); 41 | black_box(ch); 42 | } 43 | }) 44 | }); 45 | 46 | c.bench_function("String::from_utf8_lossy().char_indices()", |b| { 47 | b.iter(|| { 48 | let utf8_string = String::from_utf8_lossy(black_box(input)); 49 | for (index, ch) in utf8_string.char_indices() { 50 | black_box(index); 51 | black_box(ch); 52 | } 53 | }) 54 | }); 55 | 56 | assert_eq!( 57 | String::from_utf8_lossy(input) 58 | .char_indices() 59 | .collect::>(), 60 | Utf8CharIndices::new(input).collect::>() 61 | ); 62 | 63 | assert_eq!( 64 | String::from_utf8_lossy(input) 65 | .char_indices() 66 | .collect::>(), 67 | UncheckedUtf8CharIndices::new(input).collect::>() 68 | ); 69 | } 70 | 71 | criterion_group!(benches, bench_utf8_char_indices); 72 | 73 | criterion_main!(benches); 74 | -------------------------------------------------------------------------------- /scripts/dumb_jump/generate_pattern.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import re 5 | import os.path 6 | import json 7 | 8 | if os.path.isfile("dumb-jump.el"): 9 | lines = open('dumb-jump.el').readlines() 10 | lines = [line[:-1] for line in lines] 11 | else: 12 | import urllib.request 13 | url = ('https://raw.githubusercontent.com/' 14 | 'jacktasia/dumb-jump/master/dumb-jump.el') 15 | response = urllib.request.urlopen(url) 16 | lines = response.read().decode('utf-8').split("\n") 17 | 18 | start_line = '(defcustom dumb-jump-find-rules' 19 | stop_line = '(defcustom dumb-jump-language-contexts' 20 | type_pattern = r':type\s+(.*):supports\s+(.*):language\s+(.*)' 21 | regex_pattern = r':regex\s+"(.*)"' 22 | 23 | start_idx = lines.index(start_line) 24 | 25 | rules = {} 26 | 27 | for idx in range(start_idx, len(lines)): 28 | if lines[idx + 1].strip().startswith(';;'): 29 | continue 30 | t = re.search(type_pattern, lines[idx]) 31 | if t: 32 | regex = lines[idx + 1].split()[1][1:-1] 33 | 34 | regex = regex.replace('\\\\', "\\") 35 | 36 | ty = t.group(1).replace('"', '').strip() 37 | supports = t.group(2) 38 | language = t.group(3).replace('"', '').split()[0].strip() 39 | 40 | if language in rules: 41 | language_rule = rules[language] 42 | if ty in language_rule: 43 | language_rule[ty].append(regex) 44 | else: 45 | language_rule[ty] = [regex] 46 | else: 47 | rules[language] = {ty: [regex]} 48 | 49 | if lines[idx] == stop_line: 50 | break 51 | 52 | special_lang_map = {'c++': 'cpp'} 53 | 54 | rules['cpp'] = rules['c++'] 55 | rules.pop('c++', None) 56 | 57 | with open('rg_pcre2_regex.json', 'w') as f: 58 | json.dump(rules, f, indent=4) 59 | 60 | print(rules.keys()) 61 | 62 | comments_map = { 63 | '*': ['//'], 64 | 'lua': ['--'], 65 | 'erl': ['%'], 66 | 'hrl': ['%'], 67 | 'tex': ['%'], 68 | 'r': ['//'], 69 | 'go': ['//'], 70 | 'rs': ['//', '//!', '///'], 71 | 'cpp': ['//'], 72 | 'javascript': ['//'], 73 | 'typescript': ['//'], 74 | 'php': ['//', '#'], 75 | 'el': [';'], 76 | 'clj': [';'], 77 | 'exs': ['#'], 78 | 'perl': ['#'], 79 | 'py': ['#'], 80 | 'nim': ['#'], 81 | 'rb': ['#'], 82 | } 83 | 84 | with open('comments_map.json', 'w') as f: 85 | json.dump(comments_map, f, indent=4) 86 | -------------------------------------------------------------------------------- /autoload/clap/legacy/state.vim: -------------------------------------------------------------------------------- 1 | " Author: liuchengxu 2 | " Description: Change state of current filtering, e.g., matches count. 3 | 4 | let s:save_cpo = &cpoptions 5 | set cpoptions&vim 6 | 7 | " NOTE: some local variable without explicit l:, e.g., count, 8 | " may run into some erratic read-only error. 9 | function! clap#legacy#state#refresh_matches_count(cnt) abort 10 | call clap#indicator#update_matched(a:cnt) 11 | call clap#sign#reset_to_first_line() 12 | endfunction 13 | 14 | function! clap#legacy#state#process_filter_message(decoded_msg, ensure_sign_exists) abort 15 | if !g:clap.display.win_is_valid() 16 | return 17 | endif 18 | 19 | let decoded = a:decoded_msg 20 | 21 | if has_key(decoded, 'total') 22 | if decoded.total == 0 && exists('g:__clap_lines_truncated_map') 23 | unlet g:__clap_lines_truncated_map 24 | endif 25 | call clap#indicator#update_matched(decoded.total) 26 | endif 27 | 28 | if has_key(decoded, 'matched') 29 | call clap#indicator#update(decoded.matched, decoded.processed) 30 | elseif has_key(decoded, 'total_matched') 31 | if has_key(decoded, 'total_processed') 32 | call clap#indicator#update(decoded.total_matched, decoded.total_processed) 33 | else 34 | call clap#indicator#update_matched(decoded.total_matched) 35 | endif 36 | endif 37 | 38 | if has_key(decoded, 'lines') 39 | call g:clap.display.set_lines(decoded.lines) 40 | if empty(decoded.lines) 41 | call g:clap.preview.clear() 42 | return 43 | endif 44 | endif 45 | 46 | if exists('g:__clap_lines_truncated_map') 47 | unlet g:__clap_lines_truncated_map 48 | endif 49 | 50 | if has_key(decoded, 'truncated_map') 51 | let g:__clap_lines_truncated_map = decoded.truncated_map 52 | elseif exists('g:__clap_lines_truncated_map') 53 | unlet g:__clap_lines_truncated_map 54 | endif 55 | 56 | if has_key(decoded, 'icon_added') 57 | let g:__clap_icon_added_by_maple = decoded.icon_added 58 | endif 59 | 60 | if has_key(decoded, 'display_syntax') 61 | call setbufvar(g:clap.display.bufnr, '&syntax', decoded.display_syntax) 62 | endif 63 | 64 | if a:ensure_sign_exists 65 | call clap#sign#ensure_exists() 66 | endif 67 | 68 | if has_key(decoded, 'indices') 69 | try 70 | call clap#highlighter#add_highlights(decoded.indices) 71 | catch 72 | return 73 | endtry 74 | endif 75 | endfunction 76 | 77 | let &cpoptions = s:save_cpo 78 | unlet s:save_cpo 79 | -------------------------------------------------------------------------------- /crates/cli/src/command/exec.rs: -------------------------------------------------------------------------------- 1 | use crate::app::Args; 2 | use crate::CacheableCommand; 3 | use anyhow::Result; 4 | use clap::Parser; 5 | use maple_core::process::{shell_command, ShellCommand}; 6 | use std::path::PathBuf; 7 | use std::process::Command; 8 | 9 | /// Execute the shell command 10 | #[derive(Parser, Debug, Clone)] 11 | pub struct Exec { 12 | /// Specify the system command to run. 13 | #[clap(index = 1)] 14 | shell_cmd: String, 15 | 16 | /// Specify the working directory of CMD 17 | #[clap(long, value_parser)] 18 | cmd_dir: Option, 19 | 20 | /// Specify the threshold for writing the output of command to a tempfile. 21 | #[clap(long, default_value = "100000")] 22 | output_threshold: usize, 23 | } 24 | 25 | impl Exec { 26 | // This can work with the piped command, e.g., git ls-files | uniq. 27 | fn prepare_exec_cmd(&self) -> Command { 28 | let mut cmd = shell_command(self.shell_cmd.as_str()); 29 | 30 | if let Some(ref cmd_dir) = self.cmd_dir { 31 | cmd.current_dir(cmd_dir); 32 | } 33 | 34 | cmd 35 | } 36 | 37 | pub fn run( 38 | &self, 39 | Args { 40 | number, 41 | icon, 42 | no_cache, 43 | .. 44 | }: Args, 45 | ) -> Result<()> { 46 | let mut exec_cmd = self.prepare_exec_cmd(); 47 | 48 | // TODO: fix this properly 49 | // 50 | // `let g:clap_builtin_fuzzy_filter_threshold == 0` is used to configure clap always use 51 | // the async on_typed impl, but some commands also makes this variable to control 52 | // `--output-threshold`, which can be problamatic. I imagine not many people actually are 53 | // aware of the option `--output-threshold`, I'll use this ugly fix for now. 54 | let output_threshold = if self.output_threshold == 0 { 55 | 100_000 56 | } else { 57 | self.output_threshold 58 | }; 59 | 60 | let cwd = match &self.cmd_dir { 61 | Some(dir) => dir.clone(), 62 | None => std::env::current_dir()?, 63 | }; 64 | 65 | let shell_cmd = ShellCommand::new(self.shell_cmd.clone(), cwd); 66 | 67 | CacheableCommand::new( 68 | &mut exec_cmd, 69 | shell_cmd, 70 | number, 71 | icon, 72 | Some(output_threshold), 73 | ) 74 | .try_cache_or_execute(no_cache)? 75 | .print(); 76 | 77 | Ok(()) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/icon/build.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::env; 3 | use std::ffi::OsStr; 4 | use std::fs::{read_to_string, File}; 5 | use std::io::{LineWriter, Write}; 6 | use std::path::Path; 7 | 8 | use itertools::Itertools; 9 | 10 | fn build_raw_line + ?Sized>(p: &S, const_name: &str) -> String { 11 | let json_file_path = Path::new(p); 12 | let json_file_str = read_to_string(json_file_path).expect("file not found"); 13 | let icon_map: HashMap = serde_json::from_str(&json_file_str) 14 | .unwrap_or_else(|err| panic!("error while reading {}: {err:?})", json_file_path.display())); 15 | 16 | let sorted_icon_tuples = icon_map 17 | .keys() 18 | .sorted() 19 | .map(|k| format!("(\"{}\", '{}')", k, icon_map[k])) 20 | .join(","); 21 | 22 | format!("pub const {const_name}: &[(&str, char)] = &[{sorted_icon_tuples}];",) 23 | } 24 | 25 | fn main() { 26 | let current_dir = std::env::current_dir().unwrap(); 27 | 28 | let file_under_current_dir = |filename: &str| { 29 | let mut icon_path = current_dir.clone(); 30 | icon_path.push(filename); 31 | icon_path 32 | }; 33 | 34 | let out_dir = env::var_os("OUT_DIR").unwrap(); 35 | let dest_path = Path::new(&out_dir).join("constants.rs"); 36 | let file = File::create(dest_path).expect("can not create file"); 37 | let mut file = LineWriter::new(file); 38 | 39 | let build_line = |filename: &str, const_name: &str| { 40 | build_raw_line(&file_under_current_dir(filename), const_name) 41 | }; 42 | 43 | let line = build_line("exactmatch_map.json", "EXACTMATCH_ICON_TABLE"); 44 | file.write_all(format!("{line}\n").as_bytes()).unwrap(); 45 | 46 | let line = build_line("extension_map.json", "EXTENSION_ICON_TABLE"); 47 | file.write_all(format!("\n{line}\n").as_bytes()).unwrap(); 48 | 49 | let line = build_line("tagkind_map.json", "TAGKIND_ICON_TABLE"); 50 | file.write_all(format!("\n{line}\n").as_bytes()).unwrap(); 51 | 52 | file.write_all( 53 | " 54 | pub fn bsearch_icon_table(c: &str, table: &[(&str, char)]) ->Option { 55 | table.binary_search_by(|&(key, _)| key.cmp(c)).ok() 56 | } 57 | \n" 58 | .as_bytes(), 59 | ) 60 | .unwrap(); 61 | 62 | println!("cargo:rerun-if-changed=build.rs"); 63 | println!("cargo:rerun-if-changed=exactmatch_map.json"); 64 | println!("cargo:rerun-if-changed=extension_map.json"); 65 | println!("cargo:rerun-if-changed=tagkind_map.json"); 66 | } 67 | -------------------------------------------------------------------------------- /crates/utils/src/bytelines.rs: -------------------------------------------------------------------------------- 1 | //! A custom implementation of `lines()` method, display the non-utf8 line as well. 2 | 3 | use memchr::{memchr, memrchr}; 4 | use std::borrow::Cow; 5 | use std::iter::{DoubleEndedIterator, FusedIterator, Iterator}; 6 | use std::str; 7 | 8 | /// Parses raw untrusted bytes into the strings. 9 | #[derive(Clone)] 10 | pub struct ByteLines<'a> { 11 | text: &'a [u8], 12 | } 13 | impl<'a> ByteLines<'a> { 14 | #[inline] 15 | pub fn new(text: &'a [u8]) -> Self { 16 | Self { text } 17 | } 18 | } 19 | 20 | /// Newline char. 21 | const NL: u8 = b'\n'; 22 | 23 | impl<'a> Iterator for ByteLines<'a> { 24 | type Item = Cow<'a, str>; 25 | 26 | #[inline] 27 | fn size_hint(&self) -> (usize, Option) { 28 | // The maximum of items takes every char to be a newline. 29 | let high = self.text.len(); 30 | (0, Some(high)) 31 | } 32 | 33 | #[inline] 34 | fn next(&mut self) -> Option { 35 | let text = self.text; 36 | 37 | if text.is_empty() { 38 | return None; 39 | } 40 | 41 | let line = match memchr(NL, text) { 42 | Some(newline_idx) => { 43 | self.text = &text[newline_idx + 1..]; 44 | &text[..newline_idx] 45 | } 46 | 47 | None => { 48 | // This line is the last one 49 | self.text = &[]; 50 | text 51 | } 52 | }; 53 | 54 | Some(match simdutf8::basic::from_utf8(line) { 55 | Ok(s) => s.into(), 56 | Err(_) => String::from_utf8_lossy(line), 57 | }) 58 | } 59 | } 60 | 61 | impl DoubleEndedIterator for ByteLines<'_> { 62 | #[inline] 63 | fn next_back(&mut self) -> Option { 64 | let text = self.text; 65 | 66 | if text.is_empty() { 67 | return None; 68 | } 69 | 70 | let line = match memrchr(NL, text) { 71 | Some(newline_idx) => { 72 | self.text = &text[newline_idx + 1..]; 73 | &text[..newline_idx] 74 | } 75 | 76 | None => { 77 | // This line is the last one 78 | self.text = &[]; 79 | text 80 | } 81 | }; 82 | 83 | Some(match simdutf8::basic::from_utf8(line) { 84 | Ok(s) => s.into(), 85 | Err(_) => String::from_utf8_lossy(line), 86 | }) 87 | } 88 | } 89 | 90 | impl FusedIterator for ByteLines<'_> {} 91 | --------------------------------------------------------------------------------