{"id":19895264,"url":"https://github.com/zjp-cn/nvim-cmp-lsp-rs","last_synced_at":"2025-05-02T20:30:51.834Z","repository":{"id":230538248,"uuid":"779590726","full_name":"zjp-CN/nvim-cmp-lsp-rs","owner":"zjp-CN","description":"Refine nvim-cmp completion behavior by applying useful filtering and sorting for candidates from Rust Analyzer. (Won't cause side effects on other cmp sources)","archived":false,"fork":false,"pushed_at":"2024-11-14T11:41:23.000Z","size":75,"stargazers_count":50,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-07T05:11:36.543Z","etag":null,"topics":["lsp-completion","neovim","neovim-plugin","nvim-cmp","rust"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zjp-CN.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-03-30T08:35:42.000Z","updated_at":"2025-02-11T12:21:17.000Z","dependencies_parsed_at":"2024-03-30T09:25:59.628Z","dependency_job_id":"a2a16484-43d7-413b-add4-0f98a574b091","html_url":"https://github.com/zjp-CN/nvim-cmp-lsp-rs","commit_stats":null,"previous_names":["zjp-cn/nvim-cmp-lsp-rs"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zjp-CN%2Fnvim-cmp-lsp-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zjp-CN%2Fnvim-cmp-lsp-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zjp-CN%2Fnvim-cmp-lsp-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zjp-CN%2Fnvim-cmp-lsp-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zjp-CN","download_url":"https://codeload.github.com/zjp-CN/nvim-cmp-lsp-rs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252104011,"owners_count":21695400,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lsp-completion","neovim","neovim-plugin","nvim-cmp","rust"],"created_at":"2024-11-12T18:36:06.014Z","updated_at":"2025-05-02T20:30:51.509Z","avatar_url":"https://github.com/zjp-CN.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"## nvim-cmp-lsp-rs\n\nRefine completion behavior by applying useful filtering and sorting for candidates,\nbut only specific to Rust filetype (or rather Rust-Analyzer).\n\n\nBefore (click the picture and jump to #1 to see details)\n[![](https://github.com/zjp-CN/nvim-cmp-lsp-rs/assets/25300418/e3b00e5e-7aa2-4a46-8704-7351f24d7ded)][#1]\n\n[#1]: https://github.com/zjp-CN/nvim-cmp-lsp-rs/issues/1\n\nand after (both are improved, which is better depends on your usecase!)\n\n![](https://github.com/zjp-CN/nvim-cmp-lsp-rs/assets/25300418/f69d8ec9-8611-4a0a-a4ef-b0e8f38ac8c1)\n\n![](https://github.com/zjp-CN/nvim-cmp-lsp-rs/assets/25300418/83bda386-a5e8-4040-ae7f-56cd9501da4f)\n\nOne of the improvements is alphabetic sorting separately on inherent methods,\nin-scope trait methods and to-be-imported trait methods.\n\nFor more usage, jump to [Usage](#usage) section by skipping mutters in Background.\n\n\n\u003cdetails\u003e\n\n\u003csummary\u003eBackground\u003c/summary\u003e\n\nHave you been aware of the great [comparators][cmp-comparators] in [nvim-cmp]?\n\n[nvim-cmp]: https://github.com/hrsh7th/nvim-cmp/tree/main\n[cmp-comparators]: https://github.com/hrsh7th/nvim-cmp/blob/97dc716fc914c46577a4f254035ebef1aa72558a/lua/cmp/config/compare.lua\n\nThe default sorting is defined as below, which means if you use [`LazyVim`], you'll see the\nweird completion item list exactly as the first picture shows.\n\n[`LazyVim`]: https://www.lazyvim.org/\n\n```lua\nsorting = {\n  priority_weight = 2,\n  comparators = {\n    compare.offset,\n    compare.exact,\n    -- compare.scopes,\n    compare.score,\n    compare.recently_used,\n    compare.locality,\n    compare.kind,\n    -- compare.sort_text,\n    compare.length,\n    compare.order,\n  },\n}\n```\n\nThe problem is not about each sorting, but about the combination of sortings.\n\n`compare.kind` is very close to the tail, meaning it'll be used only if all the sortings \nbefore it return nil.\n\nA comparator is a sorting function [used] in `table.sort` to compare two arguments passed in.\n\nA comparator in the form of `fn(a, b)` returns\n* true to indicate a is prior to b \n* false to indicate b is prior to a \n* nil to indicate comparison result is the same or uncertain: like for the same lsp.CompletionItemKind\n\n[used]: https://github.com/hrsh7th/nvim-cmp/blob/97dc716fc914c46577a4f254035ebef1aa72558a/lua/cmp/view.lua#L64\n\nSo if you want a simplist and general solution, putting `require(\"cmp\").config.compare.kind` first might be good.\nIt sort the completion items in [completionItemKind] order, but with Text kind always lowest priority and Snippet\nkind a bit higher in some cases. \n\n[completionItemKind]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemKind\n\nYou may notice sometimes the ordering is not good for Rust codebases!\n* You don't want Snippets have higher priorities: search in RA's [manual] with `snippet` keyword, and there are \n  many cool features to let you config/add these Snippets.\n  * But if you've already used [LuaSnip] (or snippets edit User UI [nvim-scissors]), too much for Snippets kind!\n* You want some kinds to be higher priorities: [CompletionItemKind] treats Variables and Fields lower then Methods, \n  then you can do nothing but typing more to wait the desired one pops up.\n  * Typing more is not a big problem: when you have large candidates, no matter for what sorting, you must type more \n    characters or arrow keys :)\n  * The bigger problem is kinds like Variables/Fields are closer to use for you compared to other kinds.\n* You want features sepecific to Rust. Like\n  * Items in scope are prior to that needing to import.\n    * You may rarely want a non-imported method appears as the first candidate when other in-scope methods exist.\n    * You may want local modules prior to external modules.\n  * Inherent methods are prior to trait methods.\n\n[manual]: https://rust-analyzer.github.io/manual.html\n[LuaSnip]: https://github.com/L3MON4D3/LuaSnip\n[nvim-scissors]: https://github.com/chrisgrieser/nvim-scissors\n\nWhy are you telling me this story or details?\n* Share what I found lately. I didn't realize nvim-cmp could change the sorting behavior so much easily\n  even though I've been using it for two years in (almost) daily coding.\n* Encourage you to check out the comparators, tweak it a bit so that feel comfortable when seeing completion popup.\n* Knowing more details helps you use or write related code to enjoy the wonderful completion experience\n  neovim and nvim-cmp power us.\n* I don't want to extend the sorting functions to other LSP/languanges. So the background hopefully can\n  inspire people starting out.\n\n\u003c/details\u003e\n\n### Usage\n\nThis plugin should be a plugin of `nvim-cmp`, which means the completion \nbehavior is affacted by specifying `sorting.comparators` and `entry_filter`\nin nvim-cmp.\n\nHere's how to do in lazy.nvim (NOT default setting)\n```lua\n  {\n    \"hrsh7th/nvim-cmp\",\n    keys = {\n        -- See opts.combo from nvim-cmp-lsp-rs below\n        {\n          \"\u003cleader\u003ebc\",\n          \"\u003ccmd\u003elua require'cmp_lsp_rs'.combo()\u003ccr\u003e\",\n          desc = \"(nvim-cmp) switch comparators\"\n        },\n    },\n    dependencies = {\n      {\n        \"zjp-CN/nvim-cmp-lsp-rs\",\n        ---@type cmp_lsp_rs.Opts\n        opts = {\n          -- Filter out import items starting with one of these prefixes.\n          -- A prefix can be crate name, module name or anything an import \n          -- path starts with, no matter it's complete or incomplete.\n          -- Only literals are recognized: no regex matching.\n          unwanted_prefix = { \"color\", \"ratatui::style::Styled\" },\n          -- make these kinds prior to others\n          -- e.g. make Module kind first, and then Function second,\n          --      the rest ordering is merged from a default kind list\n          kind = function(k) \n            -- The argument in callback is type-aware with opts annotated,\n            -- so you can type the CompletionKind easily.\n            return { k.Module, k.Function }\n          end,\n          -- Override the default comparator list provided by this plugin.\n          -- Mainly used with key binding to switch between these Comparators.\n          combo = {\n            -- The key is the name for combination of comparators and used \n            -- in notification in swiching.\n            -- The value is a list of comparators functions or a function \n            -- to generate the list.\n            alphabetic_label_but_underscore_last = function()\n              local comparators = require(\"cmp_lsp_rs\").comparators\n              return { comparators.sort_by_label_but_underscore_last }\n            end,\n            recentlyUsed_sortText = function()\n              local compare = require(\"cmp\").config.compare\n              local comparators = require(\"cmp_lsp_rs\").comparators\n              -- Mix cmp sorting function with cmp_lsp_rs.\n              return {\n                compare.recently_used,\n                compare.sort_text,\n                comparators.sort_by_label_but_underscore_last\n              }\n            end,\n          },\n        },\n      },\n    },\n    --@param opts cmp.ConfigSchema\n    opts = function(_, opts)\n      local cmp_lsp_rs = require(\"cmp_lsp_rs\")\n      local comparators = cmp_lsp_rs.comparators\n      local compare = require(\"cmp\").config.compare\n\n      opts.sorting.comparators = {\n        compare.exact,\n        compare.score,\n        -- comparators.inherent_import_inscope,\n        comparators.inscope_inherent_import,\n        comparators.sort_by_label_but_underscore_last,\n      }\n\n      for _, source in ipairs(opts.sources) do\n        cmp_lsp_rs.filter_out.entry_filter(source)\n      end\n\n      return opts\n    end,\n  }\n```\n`unwanted_prefix` only applies to import items, with items in scope unaffacted.\n\nWhen specifying the kind list, you can directly pass in a list of integer that \n`lsp.CompletionItemKind` represents. So `kind = { 9, 3 }` behaves the same way.\n\nIt's totally fine to omit opts on nvim-cmp-lsp-rs, and dynamically \nchange them in runtime when you already open a rust file and RA starts.\n\nThe way to inject into nvim-cmp's config is by overriding comparators list \nand entry_filter for nvim_lsp source.\n\nNOTE: we use a callback to modify opts on nvim-cmp, because opts table form \ncan't make this plugin work. Maybe this is a nuance from lazy.nvim. Therefore,\nyou should tweak your original opts to this way.\n\nThe order in comparators list matters. `inscope_inherent_import` or \n`inherent_import_inscope` is used with `kind`. They will sort Rust entries\nby kind, and then group for inherent vs trait methods and in-scope vs import \nitems. They will also affact non-Rust entries, but only sort them by kind. \n\n`sort_by_label_but_underscore_last` will sort the entries the first comparator \nemits nil on. The sort is alphabetic, but `_` will be put to the last. This \nis most desired because it means low priority in most cases. If you don't want \n`_` to be last, use `sort_by_label` instead.\n\nYou may notice there are two comparators built in nvim-cmp as the first and\nsecond. It provides better typed characters matching across entry kinds.\nSee [here] for demonstration of lacking them.\n\n[here]: https://github.com/zjp-CN/nvim-cmp-lsp-rs/issues/4\n\nThe entry_filter will only apply to nvim_lsp source and rust filetype.\nCurrently, it filters out import methods with unwanted_prefix.\n\n### cmp_lsp_rs.comparators\n\nTwo sorting functions are provided.\n\n#### inscope_inherent_import\n\n```lua\n local cmp_rs = require(\"cmp_lsp_rs\")\n local comparators = cmp_rs.comparators\n \n opts.sorting.comparators = {\n   comparators.inscope_inherent_import,\n   comparators.sort_by_label_but_underscore_last,\n }\n```\nSorting Behaviors:\n\n* in-scope items\n  * kind order: Field -\u003e Method -\u003e rest\n    * alphabetic sort on item names separately in the same kind\n  * method order: inherent -\u003e trait\n    * alphabetic sort on method names in inherent\n    * alphabetic sort on trait names in trait methods\n    * alphabetic sort on method names in the same trait\n* import items\n  * kind order\n    * alphabetic sort on item names separately in the same kind\n  * trait method order\n    * alphabetic sort on trait names in trait methods\n    * alphabetic sort on method names in the same trait\n```rust\n [entry 1] s (this is a Field)\n [entry 2] render(…)\n [entry 3] zzzz()\n [entry 4] f() (as AAA)\n [entry 5] z() (as AAA)\n [entry 6] into() (as Into)\n [entry 7] try_into() (as TryInto)\n ... other kinds\n [entry 24] bg() (use color_eyre::owo_colors::OwoColorize)\n ... methods from color_eyre::owo_colors::OwoColorize trait\n [entry 79] yellow() (use color_eyre::owo_colors::OwoColorize)\n [entry 80] type_id() (use std::any::Any)\n [entry 81] borrow() (use std::borrow::Borrow)\n [entry 82] borrow_mut() (use std::borrow::BorrowMut)\n ... other kinds\n```\n\n#### inherent_import_inscope\n\n```lua\n  local cmp_rs = require(\"cmp_lsp_rs\")\n  local comparators = cmp_rs.comparators\n  \n  opts.sorting.comparators = {\n    comparators.inherent_import_inscope,\n    comparators.sort_by_label_but_underscore_last,\n  }\n```\nSorting Behaviors:\n\n* kind order: Field -\u003e Method -\u003e rest\n  * alphabetic sort on item names separately in the same kind\n  * method order\n    * inherent methods\n      * alphabetic sort on method names\n    * trait methods\n      * in-scope methods\n        * alphabetic sort on trait names in trait methods\n        * alphabetic sort on method names in the same trait\n      * import methods\n          * alphabetic sort on trait names in trait methods\n          * alphabetic sort on method names in the same trait\n```rust\n  [entry 1] s (this is a Field)\n  [entry 2] render(…)\n  [entry 3] zzzz()\n  [entry 4] f() (as AAA)\n  [entry 5] z() (as AAA)\n  [entry 6] into() (as Into)\n  [entry 7] try_into() (as TryInto)\n  [entry 8] bg() (use color_eyre::owo_colors::OwoColorize)\n  ... (use color_eyre::owo_colors::OwoColorize)\n  [entry 63] yellow() (use color_eyre::owo_colors::OwoColorize)\n  [entry 64] type_id() (use std::any::Any)\n  [entry 65] borrow() (use std::borrow::Borrow)\n  [entry 66] borrow_mut() (use std::borrow::BorrowMut)\n  ... \n```\n\n### cmp_lsp_rs.combo\n\nSee the configuration example in [usage](#usage) above.\n\nYou can bind the function to a key to switch between defined combinations.\n\nThe default is like\n```lua\n  {\n    [\"inherent_import_inscope + sort_by_label_but_underscore_last\"] = {\n      M.comparators.inherent_import_inscope, \n      M.comparators.sort_by_label_but_underscore_last\n    },\n    [\"inscope_inherent_import + sort_by_label_but_underscore_last\"] = {\n      M.comparators.inscope_inherent_import,\n      M.comparators.sort_by_label_but_underscore_last\n    },\n  }\n```\n\n![toggle-combo](https://github.com/zjp-CN/nvim-cmp-lsp-rs/assets/25300418/ec77123b-0cc4-422b-9557-774d544ed57a)\n\n### cmp_lsp_rs.log\n\nYou can call `require(\"cmp_lsp_rs\").log.register()` to listen on `menu_opened`\nevent emitted by nvim-cmp to obtain the last and sorted completion result \ndisplayed to you.\n\nThe log file is `entries.log` right under the current folder.\n\nThis is mainly used in debuging. \n\n\u003cdetails\u003e\n\n\u003csummary\u003eThe format shouldn't be relied on. e.g.\u003c/summary\u003e\n\n```rust\n  [entry 1] s\n    filter_text: s\n    kind: Field\n  [entry 2] render(…)\n    filter_text: render\n    kind: Method\n  [entry 3] zzzz()\n    filter_text: zzzz\n    kind: Method\n  [entry 4] f() (as AAA)\n    filter_text: f\n    kind: Method\n    [entry 5] z() (as AAA)\n    filter_text: z\n    kind: Method\n  [entry 6] into() (as Into)\n    filter_text: into\n    kind: Method\n  [entry 8] box\n    filter_text: box\n    kind: Snippet\n  [entry 80] type_id() (use std::any::Any)\n    filter_text: type_id\n    kind: Method\n    data: {\n    full_import_path = \"std::any::Any\",\n    imported_name = \"Any\"\n  }\n  [entry 84] arc (use std::sync::Arc)\n    filter_text: arc\n    kind: Snippet\n    data: {\n    full_import_path = \"std::sync::Arc\",\n    imported_name = \"Arc\"\n  }\n```\n\u003c/details\u003e\n\n### Dynamic Setting in Runtime\n\nThe filtering and sorting in nvim-cmp are pretty dynamic and straightforward.\n\nEach entry from various sources will be passed into `entry_filter`, a \nfunction that accepts an entry and returns the entry if the function returns \ntrue. Then a table of entries will be sorted by a list of comparator.\n\n#### Dynamic Unwanted Prefix\n```vim\n  :lua rs = require'cmp_lsp_rs'\n  :lua rs.unwanted_prefix.get()    -- query\n  :lua rs.unwanted_prefix.set(...) -- override\n  :lua rs.unwanted_prefix.add(...) -- append\n  :lua rs.unwanted_prefix.remove(...) -- delete\n```\nThe argument `...` for them can be a `string` or `string[]`.\n`unwanted_prefix` is default to empty.\n\n#### Dynamic Kind Sorting\n\n```vim\n  :lua rs = require'cmp_lsp_rs'\n  :lua rs.kind.get()    -- query\n  :lua rs.kind.set(...) -- set kind ordering with most priorities\n```\nThe argument `...` for `set` can be one of these \n* `string` name for kind\n* `string[]` names for kind\n* `integer` integer for kind\n* `integer[]` integers for kind\n* `function(k) -\u003e integer[]` where k is of kind type and you can easily write \n  the kinds like `k.Module` etc with lsp help. \n\ne.g. for the last case, you can write \n```lua\n  rs.kind.set(function(k) return { k.Module, k.Function })\n```\n\nThe current default ordering is as follows:\n```\n  Variable Value Field EnumMember Property TypeParameter Method Module\n  Function Constructor Interface Class Struct Enum Constant Unit Keyword\n  Snippet Color File Folder Event Operator Reference Text \n```\n\n#### Dynamic Comparators\n\nIf you want to override comparators nvim-cmp calls when experimenting them,\nyou can run these commands.\n```vim\n  :lua rs = require'cmp_lsp_rs'\n  :lua cmp = require'cmp'\n  :lua cmp.get_config().sorting.comparators = { rs.comparators.sort_by_label }\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzjp-cn%2Fnvim-cmp-lsp-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzjp-cn%2Fnvim-cmp-lsp-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzjp-cn%2Fnvim-cmp-lsp-rs/lists"}