From b77a8ca015e39fa70089ef9244204702483a1097 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 30 May 2024 20:31:53 +0200 Subject: [PATCH] feat: initial commit --- .github/CODEOWNERS | 2 + .github/ISSUE_TEMPLATE/bug_report.yml | 105 ++++ .github/ISSUE_TEMPLATE/config.yml | 4 + .github/ISSUE_TEMPLATE/feature_request.yml | 33 + .github/funding.yml | 2 + .github/workflows/.luarc.json | 14 + .github/workflows/auto_release.yml | 16 + .github/workflows/formatting.yml | 48 ++ .github/workflows/lint.yml | 15 + .github/workflows/typecheck.yml | 44 ++ .gitignore | 1 + LICENSE | 674 +++++++++++++++++++++ docs/completion_item.norg | 48 ++ docs/config.norg | 172 ++++++ docs/context.norg | 77 +++ docs/core.norg | 74 +++ docs/design.norg | 163 +++++ docs/entry.norg | 29 + docs/menu.norg | 154 +++++ docs/misc.norg | 33 + docs/source.norg | 79 +++ lua/neocomplete/config.lua | 78 +++ lua/neocomplete/context.lua | 36 ++ lua/neocomplete/core.lua | 52 ++ lua/neocomplete/highlights.lua | 19 + lua/neocomplete/init.lua | 26 + lua/neocomplete/lsp_source.lua | 95 +++ lua/neocomplete/menu.lua | 280 +++++++++ lua/neocomplete/sources.lua | 35 ++ lua/neocomplete/types/completion_item.lua | 4 + lua/neocomplete/types/config.lua | 34 ++ lua/neocomplete/types/context.lua | 16 + lua/neocomplete/types/core.lua | 14 + lua/neocomplete/types/entry.lua | 2 + lua/neocomplete/types/menu.lua | 36 ++ lua/neocomplete/types/misc.lua | 4 + lua/neocomplete/types/source.lua | 8 + lua/neocomplete/utils/async.lua | 3 + lua/neocomplete/utils/format.lua | 39 ++ lua/neocomplete/utils/init.lua | 16 + lua/neocomplete/utils/lsp.lua | 36 ++ plugin/neocomplete.lua | 5 + readme.md | 10 + stylua.toml | 6 + test.lua | 24 + 45 files changed, 2665 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/funding.yml create mode 100644 .github/workflows/.luarc.json create mode 100644 .github/workflows/auto_release.yml create mode 100644 .github/workflows/formatting.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/typecheck.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 docs/completion_item.norg create mode 100644 docs/config.norg create mode 100644 docs/context.norg create mode 100644 docs/core.norg create mode 100644 docs/design.norg create mode 100644 docs/entry.norg create mode 100644 docs/menu.norg create mode 100644 docs/misc.norg create mode 100644 docs/source.norg create mode 100644 lua/neocomplete/config.lua create mode 100644 lua/neocomplete/context.lua create mode 100644 lua/neocomplete/core.lua create mode 100644 lua/neocomplete/highlights.lua create mode 100644 lua/neocomplete/init.lua create mode 100644 lua/neocomplete/lsp_source.lua create mode 100644 lua/neocomplete/menu.lua create mode 100644 lua/neocomplete/sources.lua create mode 100644 lua/neocomplete/types/completion_item.lua create mode 100644 lua/neocomplete/types/config.lua create mode 100644 lua/neocomplete/types/context.lua create mode 100644 lua/neocomplete/types/core.lua create mode 100644 lua/neocomplete/types/entry.lua create mode 100644 lua/neocomplete/types/menu.lua create mode 100644 lua/neocomplete/types/misc.lua create mode 100644 lua/neocomplete/types/source.lua create mode 100644 lua/neocomplete/utils/async.lua create mode 100644 lua/neocomplete/utils/format.lua create mode 100644 lua/neocomplete/utils/init.lua create mode 100644 lua/neocomplete/utils/lsp.lua create mode 100644 plugin/neocomplete.lua create mode 100644 readme.md create mode 100644 stylua.toml create mode 100644 test.lua diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..bcac959 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# global rule +* @max397574 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..9c20604 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,105 @@ +name: Bug Report +description: Report a problem with neocomplete.nvim +labels: [bug] +body: + - type: checkboxes + id: faq-prerequisite + attributes: + label: Prerequisites + options: + - label: I am using the latest stable neovim version + required: true + - label: I am using the latest version of the plugin + required: true + - type: input + attributes: + label: "Neovim Version" + description: "`nvim --version`:" + validations: + required: true + - type: textarea + attributes: + label: "neocomplete.nvim setup" + description: | + - Copy your entire `require("neocomplete").setup` function + validations: + required: true + - type: textarea + attributes: + label: "Actual behavior" + description: "A description of actual behavior. May optionally include images or videos." + validations: + required: true + - type: textarea + attributes: + label: "Expected behavior" + description: "A description of the behavior you expected." + validations: + required: true + - type: textarea + attributes: + label: "Healthcheck" + description: "Output of `:checkhealth neocomplete`" + render: markdown + placeholder: | + neocomplete: require("neocomplete.health").check() + ======================================================================== + ## neocomplete.nvim + required: true + - type: textarea + attributes: + label: "Steps to reproduce" + description: "Please describe how we can reproduce the issue." + placeholder: | + 1. `nvim -nu minimal.lua` + 2. ... + validations: + required: true + - type: textarea + attributes: + label: "Other information" + description: "Other information that could be helpful with debugging." + - type: textarea + attributes: + label: "Minimal config" + description: "Minimal config with which the issue is reproducible. Save this as `minimal_init.lua. Add plugins and configs which are necessary at indicated location." + render: Lua + value: | + local root = vim.fn.fnamemodify("./.repro", ":p") + + -- set stdpaths to use .repro + for _, name in ipairs({ "config", "data", "state", "cache" }) do + vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name + end + + -- bootstrap lazy + local lazypath = root .. "/plugins/lazy.nvim" + if not vim.loop.fs_stat(lazypath) then + vim.fn.system({ + "git", + "clone", + "--filter=blob:none", + "--single-branch", + "https://github.com/folke/lazy.nvim.git", + lazypath, + }) + end + vim.opt.runtimepath:prepend(lazypath) + + -- install plugins + local plugins = { + "folke/tokyonight.nvim", + { + "max397574/neocomplete.nvim", + }, + } + require("lazy").setup(plugins, { + root = root .. "/plugins", + }) + + -- add anything else here + vim.opt.termguicolors = true + vim.cmd([[colorscheme tokyonight]]) + require"neocomplete".setup() + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..75f6d67 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: Ask a question + url: https://github.com/max397574/neocomplete.nvim/discussions + about: If you need help with configuration or something else diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..46b137a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,33 @@ +name: Feature request +description: Request a feature for neocomplete.nvim +labels: [feature] +body: + - type: checkboxes + id: issue-prerequisite + attributes: + label: Issues + options: + - label: I have checked [existing issues](https://github.com/max397574/neocomplete.nvim/issues) and there are no existing ones with the same request. + required: true + - type: textarea + attributes: + label: "Feature description" + validations: + required: true + - type: dropdown + id: help + attributes: + label: "Help" + description: "Would you be able to implement this by submitting a pull request?" + options: + - "Yes" + - "Yes, but I don't know how to start. I would need guidance" + - "No" + validations: + required: true + - type: textarea + attributes: + label: "Implementation help" + description: "If you selected yes in the last question please specify in detail what you would need help with in order to implement this." + validations: + required: false diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000..4661b63 --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,2 @@ +github: 'max397574' +custom: ['https://buymeacoffee.com/max397574'] diff --git a/.github/workflows/.luarc.json b/.github/workflows/.luarc.json new file mode 100644 index 0000000..0c47c78 --- /dev/null +++ b/.github/workflows/.luarc.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", + "runtime.version": "LuaJIT", + "runtime.path": [ + "lua/?.lua", + "lua/?/init.lua" + ], + "workspace.library": [ + "/home/runner/work/neocomplete.nvim/neocomplete.nvim/deps/neovim/runtime/lua", + "/home/runner/work/neocomplete.nvim/neocomplete.nvim/deps/luacats/luv/library" + ], + "diagnostics.libraryFiles": "Disable", + "workspace.checkThirdParty": "Disable" +} diff --git a/.github/workflows/auto_release.yml b/.github/workflows/auto_release.yml new file mode 100644 index 0000000..86b85bf --- /dev/null +++ b/.github/workflows/auto_release.yml @@ -0,0 +1,16 @@ +name: Release Please + +on: + push: + branches: + - main + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - uses: google-github-actions/release-please-action@v3 + with: + release-type: simple + package-name: neocomplete.nvim diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml new file mode 100644 index 0000000..ca2439d --- /dev/null +++ b/.github/workflows/formatting.yml @@ -0,0 +1,48 @@ +name: Formatting + +on: + push: + branches: [ "main" ] + paths-ignore: + - ".github/**" + - "**.md" + - "**.norg" + +jobs: + format-with-stylua: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Cache cargo modules + id: cache-cargo + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.cargo + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.toml') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Install cargo + run: curl https://sh.rustup.rs -sSf | sh -s -- -y + + - name: Install stylua + run: cargo install stylua --features lua52 + + - name: Run formatting + run: stylua -v --verify . + + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: "chore: autoformat with stylua" + branch: ${{ github.ref }} + + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..33a1f23 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,15 @@ +name: Linter +on: + pull_request: ~ + push: + branches: + - '*' +jobs: + luacheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: nebularg/actions-luacheck@v1 + with: + files: 'lua' + args: --no-unused diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 0000000..2ea7ba5 --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -0,0 +1,44 @@ +name: lua-ls Typecheck +on: + pull_request: ~ + push: + branches: + - '*' +jobs: + build: + name: lua-ls Typecheck + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + - name: Get latest neovim release tag + id: get_latest_neovim_tag + run: | + latest_tag=$(curl --silent "https://api.github.com/repos/neovim/neovim/tags" | jq -r '.[0].name') + echo "Latest tag: $latest_tag" + echo "::set-output name=latest_tag::$latest_tag" + - name: Checkout neovim for type annotations + uses: actions/checkout@v3 + with: + repository: "neovim/neovim" + path: "deps/neovim" + ref: ${{ steps.get_latest_neovim_tag.outputs.latest_tag }} + - name: Install luv luaCATS + uses: actions/checkout@v3 + with: + repository: "LuaCATS/luv" + path: "deps/luacats/luv" + - uses: leafo/gh-actions-lua@v9 # get luarocks dependencies for their types (eg `PathlibPath`) + with: + luaVersion: "5.1" + - uses: leafo/gh-actions-luarocks@v4 + - name: install dependencies + run: | + luarocks init + luarocks install --only-deps ./*.rockspec + - name: Type Check Code Base + uses: mrcjkb/lua-typecheck-action@v0.2.1 + with: + configpath: .github/workflows/.luarc.json + directories: | + lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fb57e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +todo.norg diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/docs/completion_item.norg b/docs/completion_item.norg new file mode 100644 index 0000000..909dc9d --- /dev/null +++ b/docs/completion_item.norg @@ -0,0 +1,48 @@ +@document.meta +title: Source +description: Type description of neocomplete.nvim completion item +authors: [ + max397574 +] +categories: [ + docs, + types +] +tangle: { + languages: { + lua: ../lua/neocomplete/types/completion_item.lua + } + scope: tagged + delimiter: none +} +created: 2023-11-17T16:29:17+0100 +updated: 2023-11-17T16:29:50+0100 +version: 1.1.1 +@end + +* General + This is a superset of the {https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItem}[lsp-completion-item]. + Completion items are the items which are returned by a completion source. *Important:* They are + not to be confused with an {:./entry:}. + + #tangle + @code lua + --- An item returned by a completion source + ---@class neocomplete.completion_item: lsp.CompletionItem + @end + +* Label + Every item needs to have a label. This is also the default text that will be inserted. There can + also be additional `labelDetails`. Both of these properties are inherited from the lsp-completion- + item. + +* Kind + The optional `kind` property is used to determine the kind of the completion item. This is often + used to display an icon representing it. In neocomplete this can either be a number like in the + lsp-specification of a string. + + #tangle + @code lua + --- The kind of the completion item + ---@field kind? string|lsp.CompletionItemKind + @end diff --git a/docs/config.norg b/docs/config.norg new file mode 100644 index 0000000..9553184 --- /dev/null +++ b/docs/config.norg @@ -0,0 +1,172 @@ +@document.meta +title: Source +description: Type description of neocomplete.nvim config +authors: [ + max397574 +] +categories: [ + docs, + types +] +tangle: { + languages: { + lua: ../lua/neocomplete/types/config.lua + } + scope: tagged + delimiter: none +} +created: 2023-11-17T16:29:17+0100 +updated: 2023-11-17T16:32:27+0100 +version: 1.1.1 +@end + +* General + The config of neocomplete is used to configure the ui and neocomplete itself. + #tangle + @code lua + --- Configuration for neocomplete.nvim + ---@class neocomplete.config + @end + + There are two main parts to the config. The first one is the `ui` field and the second on is the + rest of the configuration which is for configuring neocomplete itself. + +** UI + In the ui field the completion-menu, the docs-view and the format of the entries are configured. + There is also a field for configuring type icons. + #tangle + @code lua + --- Configuration for the ui of neocomplete + ---@field ui neocomplete.config.ui + @end + +** Snippet expansion + Here a function for expanding snippets is defined. By default this is the builtin + `vim.snippet.expand()`. You can also use a plugin like luasnip for this like this: + @code lua + snippet_expansion = function(body) + require("luasnip").lsp_expand(body) + end + @end + + #tangle + @code lua + --- Function used to expand snippets + ---@field snippet_expansion fun(string): nil + @end + +** Enabled + This function can be used to disable neocomplete in certain contexts. By default this disables + neocomplete in prompts. + + #tangle + @code lua + --- Configuration for the ui of neocomplete + ---@field enabled fun(): boolean + @end + +* UI + The ui configuration is used to configure the whole ui of neocomplete. One of the main goals of + this is to be as extensible as possible. This is especially important for the completion entries. + Read more about that under {:./design:**** Configuraton of item display}. + #tangle + @code lua + --- The main class for the ui configuration of neocomplete.nvim + ---@class neocomplete.config.ui + @end + The most important part for many users will be the `menu` field. It's used to configure the completion menu. + #tangle + @code lua + --- Configuration of the completion menu of neocomplete.nvim + ---@field menu neocomplete.config.ui.menu + @end + + You can also configure the documentation view just like the main menu. + #tangle + @code lua + --- Configuration of the documentation view of neocomplete.nvim + ---@field docs_view neocomplete.config.ui.docs + @end + + Lastly the users can also configure the icons which will be used for the different items. + #tangle + @code lua + --- The icons for the different compltion item kinds + ---@field type_icons neocomplete.config.ui.type_icons + @end + +** Menu + This configuration should allow you to completely adapt the completion menu to your likings. + #tangle + @code lua + --- Configuration of the completion menu of neocomplete.nvim + ---@class neocomplete.config.ui.menu + @end + It includes some basic window properties like the border and the maximum height of the window. + #tangle + @code lua + --- Maximum height of the menu + ---@field max_height integer + --- The border of the completion menu + ---@field border string|string[]|string[][] + @end + + Another field is `format_entry`. This is a function which recieves an entry of the completion + menu and determines how it's formatted. For that a table with text-highlight chunks like + `:h nvim_buf_set_extmarks()` is used. You can create sections which are represented by tables + and can have a different alignment each. This is specified with another field which takes a table + with the alignment of each section. + + For example you want to have the label of an entry in a red highlight and an icon in a entry-kind + specific color left aligned first and then the source of the entry right aligned in blue. + You could do that like this: + @code lua + format_entry = function(entry) + return { + -- The first section with the two chunks for the label and the icon + { { entry.label .. " ", "MyRedHlGroup" }, { entry.kind, "HighlightKind" .. entry.kind } } + -- The second section for the source + { { entry.source, "MyBlueHlGroup" } } + } + end, + alignment = { "left", "right" } + @end + + Notice that there are multiple differences between having one table containing the chunks for the + label and kind and having them separately. The latter would require another entry in the `alignment` + table. It would also change the style of the menu because the left sides of the icons would be + aligned at the same column and not be next to the labels. In the example there also was some + spacing added in between the two. + + #tangle + @code lua + --- How an entry should be formatted + ---@field format_entry fun(neocomplete.entry): { [1]: string, [2]: string }[][] + --- How the sections in the menu should be aligned + ---@field alignment ("left"|"center"|"right")[] + @end + +** Documentation view + This configuration allows you to configure the documentation view. + #tangle + @code lua + --- Configuration of the completion menu of neocomplete.nvim + ---@class neocomplete.config.ui.docs + @end + It consists of window properties like the border and the maximum height of the window. + #tangle + @code lua + --- Maximum height of the documentation view + ---@field max_height integer + --- The border of the documentation view + ---@field border string|string[]|string[][] + @end + +** Type Icons + This is a table which defines the different icons. + + #tangle + @code lua + --- The icons used for the different completion item types + ---@alias neocomplete.config.ui.type_icons table + @end diff --git a/docs/context.norg b/docs/context.norg new file mode 100644 index 0000000..ab78932 --- /dev/null +++ b/docs/context.norg @@ -0,0 +1,77 @@ +@document.meta +title: Index +description: Type description of context +authors: [ + max397574 +] +categories: [ + docs, + types +] +created: 2023-11-15T17:42:46+0100 +updated: 2024-05-28T20:54:48+0100 +tangle: { + languages: { + lua: ../lua/neocomplete/types/context.lua + } + scope: tagged + delimiter: none +} +version: 1.1.1 +@end + +* Completion Context + #tangle + @code lua + --- Context provided to completion sources + ---@class neocomplete.context + @end + +** Changed + Whether the context changed in comparison to the previous one + #tangle + @code lua + --- Context changed since previous + ---@field changed fun(neocomplete.context): boolean + @end + +** New + Create a new context + #tangle + @code lua + --- Create a new context + ---@field new fun(neocomplete.context?): neocomplete.context + @end + +** Previous + The previous context which is used to determine whether the context changed or not + #tangle + @code lua + --- The previous context + ---@field previous neocomplete.context? + @end + +** Cursor + #tangle + @code lua + --- The cursor position + ---@field cursor neocomplete.context.cursor + @end + +** Bufnr + Number of the buffer + #tangle + @code lua + --- The number of the buffer + ---@field bufnr integer + @end + + + #tangle + @code lua + --- A cursor position + ---@class neocomplete.context.cursor + ---@field row integer + ---@field col integer + @end + diff --git a/docs/core.norg b/docs/core.norg new file mode 100644 index 0000000..af49479 --- /dev/null +++ b/docs/core.norg @@ -0,0 +1,74 @@ +@document.meta +title: Core +description: Type description of neocomplete.nvim source +authors: [ + max397574 +] +categories: [ + docs, + types +] +created: 2023-11-15T17:42:46+0100 +updated: 2024-05-29T16:24:09+0100 +tangle: { + languages: { + lua: ../lua/neocomplete/types/core.lua + } + scope: tagged + delimiter: none +} +version: 1.1.1 +@end + +* General + #tangle + @code lua + --- The core of neocomplete where the main api functions are defined + ---@class neocomplete.core + @end + +** New + Creates a new core instance + #tangle + @code lua + --- Create a new instance of the core + ---@field new fun(): neocomplete.core + @end + +** Context + #tangle + @code lua + --- Context instance of the core + ---@field context neocomplete.context + @end + +** Menu + #tangle + @code lua + --- Menu instance of the core + ---@field menu neocomplete.menu + @end + +** Complete + The main function which starts the completion + #tangle + @code lua + --- Complete + ---@field complete fun(neocomplete.core): nil + @end + +** On Change + #tangle + @code lua + --- The function that gets invoked when the text changes + ---@field on_change fun(neocomplete.core): nil + @end + +** Setup + The setup function is used to setup neocomplete so it will actually provide autocompletion when + typing. + #tangle + @code lua + --- Setup core (for now autocommands) + ---@field setup fun(self: neocomplete.core): nil + @end diff --git a/docs/design.norg b/docs/design.norg new file mode 100644 index 0000000..c6ebec0 --- /dev/null +++ b/docs/design.norg @@ -0,0 +1,163 @@ +@document.meta +title: Design +description: Design of neocomplete.nvim +authors: [max397574] +categories: [docs] +created: 2023-11-15T17:42:49+0100 +updated: 2023-11-15T17:42:49+0100 +version: 1.1.1 +@end + +* General + There is the {https://github.com/max397574/neocomplete.nvim}[neocomplete.nvim] plugin. This is + the main module of the whole completion architecture. Then there are also sources, which is where + the core gets it's completions from. + +** Neocomplete.nvim + This is the core of the autocompletion. From here the sources are used to get completions which + are then displayed in the {*** Completion Menu} + +*** Completion Menu + The completion menu displays the current completions. + Features that it should have: + - Completely customizable display for every entry (with *text-highlight chunks* like extmark-api) + - Customizable scrollbar + - Customizable window properties + -- Border + -- Max height + - Docview + -- Customizable + -- Try to get nicely concealed like in core or noice.nvim + -- Allow to send to e.g. quickfix or copy + +** Source + +** Types + - {:./types/source:} + + +* Architecture + TODO: fancy ascii diagramms + + autocompletion: + ~ `TextChangedI` or `CursorMovedI` + ~ Check if character was a trigger character or you're in specific context e.g. at beginning of line or word + ~ Depending on ^^ decide what to do /for every source/: + -- Get new completions + ~~~ get the context (line before, cursor position etc) + ~~~ get completions from source based on context + -- sort completions + ~~~ use character typed to fuzzy match and sort completions + ~ display things + ~~ figure out top matching completions + ~~ if there is a selected one: + --- highlight selected one + --- show preview of selected completion + --- show docs + + When pressing e.g. ``: + ~ check if menu is open + ~ check if anything is selected + ~ complete + ~~ insert text + ~~ additional text edits (check core functions) + ~~ snippet expansion (core or luasnip) + ~ close menu + +* Motivation +** Nvim-cmp + These days nvim-cmp is the most popular completion-engine for neovim. There are some mayor issues + me and also other people in the community have with cmp. + +*** Bad code/Documentation + The code of nvim-cmp is often quite unreadable. Sometimes this might be due to optimizations + and surely some of it has just grown historically. Also there are nearly no docs on how the + whole completion engine works. The api for new sources is quite unclear and far from optimal. + While this doesn't really matter to a user it definitely does to a potential contributor and + developers of sources. + +*** Legacy code/features + There are a lot of things which grew just historically. The author of nvim-cmp is + (understandable to a certain degree) afraid of making breaking changes and fixing them or just + doesn't think changes are necessary. + +**** Configuraton of item display + One such example is the configuration of how items are displayed in the menu. This works with + a function `formatting` which takes a completion item and is allowed to return an item where + the three fields `menu`, `kind` and `abbr` and three more fields for highlights for those can + be set. So appart from the background and border color of the menu you're limited to have three + different fields and colors in your menu E.g. source name, kind name, kind icon and text isn't + possible. It's also not possible to have round or half blocks around icons because you don't + have enough colors. An example of an issue can be found {https://github.com/hrsh7th/nvim-cmp/issues/1599}[here]. + You also can't add padding wherever you want and you can't align the fields as you want. + +**** Legacy code + There is e.g. the whole "native menu" thing laying around in the codebase. Nowadays this isn't + really needed anymore. Everything of it can be accomplished with the "custom menu". There is a + lot of duplicate code because of that. + +*** Mapping system + The mapping system is quite confusing. It's a table in the config with keys to be mapped as keys + and a call to `cmp.mapping()` with a callback to the actual functionality as value. + +*** Why not contribute? + The maintainer is in general quite conservative. There were pull-requests for many features open + which were liked by the community (seen by reactions and comments). But they were abandoned + because the maintainer saw no reason to add it. There was for example a pull-request to fix the + issue with the limited fields in the configuration {https://github.com/hrsh7th/nvim-cmp/pull/1238}[here]. + This pull request was closed because *No specific use cases have emerged at this stage.* + according to the author. Even though there was clearly a problem described and what the pr would + allow (this pr allowed custom fields which still isn't nice but fixed the obvious problems). + There were also some features (in particular the custom scrollbars) removed because there were + some issues with it which apperently weren't worth fixing for the feature. + So it's not really motivating to try to contribute new things. It's also quite hard because of + the messy code with lots of legacy code. + +* Goals +** Use nvim-cmp sources + We should be able to use nvim-cmp sources. This should be possible by adding a `package.loaders` + where we can redirect calls to `cmp.register_source` (which happens in most sources auto- + matically) to our own plugin. We *don't want to adapt to cmp's apis* for this though. We won't + extend our own formats e.g. for entries or sources to match cmps. Even when it's complicated we + will just convert between the different formats. + +* Non-Goals +** Different views + Nvim-cmp has different views. At the moment wild-menu, native menu and custom menu. There is a + lot of code duplication because of this. We'd like to avoid having multiple views. The native + one isn't needed anyway (it likely is just in cmp for historical reasons). + In the future we'd like to allow injecting custom views via config where you just get the + entries and do things with them yourself. This is mostly to avoid code duplication in core. + +* Types + TODO: tangle those into files? + TODO: perhaps move to different file? + The types should minimally be the lsp things (for the context passed to source, for response and + for entries). Everything additionally just optional. + @code lua + ---A source for neocomplete + ---@class neocomplete.source + ---@field + @end + +* Code style +** Object-Orientation + TOOD: decide + avoid for utils? + menu, view, entry, source oop? +** Functions + You should always write functions in the form of `[local] function ()` as + opposed to `[local = function()`. The first notation provides the advantage + that you can directly jump to it's definition and you won't get multiple results (the name and + the anonymous function). +** Comments and annotations + Add annotations to every public function of a module (e.g. with neogen) and add comments + explaining what the code does. We'd like to have code which would be understandable for outsiders. + +*** Format + TODO: decide whether we want spaces after `---` or not + +** Types + We have files for types which are tangled from a norg file (this one?) using lua-ls annotations. + They are prefixed with `neocomplete`. + As often as possible we should try to use the `lsp.*` types which are in neovim core (TODO: perhaps only on 0.10? but we don't care). diff --git a/docs/entry.norg b/docs/entry.norg new file mode 100644 index 0000000..fa788e2 --- /dev/null +++ b/docs/entry.norg @@ -0,0 +1,29 @@ +@document.meta +title: Entry +description: Type description of neocomplete.nvim entry +authors: [ + max397574 +] +categories: [ + docs, + types +] +created: 2023-11-15T17:42:46+0100 +updated: 2024-05-29T15:28:34+0100 +tangle: { + languages: { + lua: ../lua/neocomplete/types/entry.lua + } + scope: tagged + delimiter: none +} +version: 1.1.1 +@end + +* General + Entries are the items in the completion menu. + #tangle + @code lua + --- An entry for the neocomplete completion menu + ---@class neocomplete.entry: lsp.CompletionItem + @end diff --git a/docs/menu.norg b/docs/menu.norg new file mode 100644 index 0000000..c63d324 --- /dev/null +++ b/docs/menu.norg @@ -0,0 +1,154 @@ +@document.meta +title: Menu +description: Type description of neocomplete.nvim menu +authors: [ + max397574 +] +categories: [ + docs, + types +] +created: 2024-05-29T11:30:25+0100 +updated: 2024-05-30T17:41:03+0100 +tangle: { + languages: { + lua: ../lua/neocomplete/types/menu.lua + } + scope: tagged + delimiter: none +} +version: 1.1.1 +@end + +* General + #tangle + @code lua + --- The completion menu of neocomplete + ---@class neocomplete.menu + @end + +* Methods +** New + Creates a new instance + #tangle + @code lua + --- Create a new instance of the menu + ---@field new fun(): neocomplete.menu + @end + +** Draw + #tangle + @code lua + --- Draws the menu + ---@field draw fun(self: neocomplete.menu): nil + @end + +** Open win + #tangle + @code lua + --- Opens window for the menu + ---@field open_win fun(self: neocomplete.menu): nil + @end + + +** Select next + #tangle + @code lua + --- Select next entry in menu + ---@field select_next fun(self: neocomplete.menu, count: integer): nil + @end + +** Select prev + #tangle + @code lua + --- Select previous entry in menu + ---@field select_prev fun(self: neocomplete.menu, count: integer): nil + @end + +** Open + #tangle + @code lua + --- Opens the menu with specified entries + ---@field open fun(self: neocomplete.menu, entries: neocomplete.entry[]): nil + @end + +** Close + #tangle + @code lua + --- Closes the window of the menu + ---@field close fun(self: neocomplete.menu): nil + @end + +** Get active entry + #tangle + @code lua + --- Gets the currently selected entry + ---@field get_active_entry fun(self: neocomplete.menu): neocomplete.entry? + @end + +** Confirm + #tangle + @code lua + --- Selects the current entry and inserts it's text + ---@field confirm fun(self: neocomplete.menu): nil + @end + +** confirm + #tangle + @code lua + --- Selects the current entry and inserts it's text + ---@field complete fun(self: neocomplete.menu, entry: neocomplete.entry): nil + @end + +* Fields +** Entries + #tangle + @code lua + --- Entries of the menu + ---@field entries neocomplete.entry[] + @end + +** Namespace + #tangle + @code lua + --- Namespace used for the menu + ---@field ns integer + @end + +** Config + #tangle + @code lua + --- Neocomplete config to be used in the menu + ---@field config neocomplete.config + @end + +** Input + Currently just used to test highlighting match characters + #tangle + @code lua + --- Input of the user which is fuzzy matched + ---@field input string + @end + +** Buffer + Buffer used for the menu + #tangle + @code lua + --- Buffer of the menu + ---@field buf integer + @end + +** Window + #tangle + @code lua + --- Window of menu + ---@field winnr integer? + @end + +** Index + used to get and track the currently selected item + #tangle + @code lua + --- Index of selected item + ---@field index integer + @end diff --git a/docs/misc.norg b/docs/misc.norg new file mode 100644 index 0000000..52c3917 --- /dev/null +++ b/docs/misc.norg @@ -0,0 +1,33 @@ +@document.meta +title: Misc +description: Misc types +authors: [ + max397574 +] +categories: [ + docs, + types +] +created: 2024-05-29T11:30:25+0100 +updated: 2024-05-29T11:32:14+0100 +tangle: { + languages: { + lua: ../lua/neocomplete/types/misc.lua + } + scope: tagged + delimiter: none +} +version: 1.1.1 +@end + + +* Completion context + a combination of context and lsp completion context + #tangle + @code lua + --- Context provided to completion sources + ---@class neocomplete.completion_context + ---@field completion_context lsp.CompletionContext + ---@field context neocomplete.context + @end + diff --git a/docs/source.norg b/docs/source.norg new file mode 100644 index 0000000..0313fe1 --- /dev/null +++ b/docs/source.norg @@ -0,0 +1,79 @@ +@document.meta +title: Source +description: Type description of neocomplete.nvim source +authors: [ + max397574 +] +categories: [ + docs, + types +] +created: 2023-11-15T17:42:46+0100 +updated: 2024-05-29T11:33:36+0100 +tangle: { + languages: { + lua: ../lua/neocomplete/types/source.lua + } + scope: tagged + delimiter: none +} +version: 1.1.1 +@end + +TODO: decide about object-orientation of sources + +* General + The sources are used to get get completions for neocomplete.nvim. + + #tangle + @code lua + --- A completion source for neocomplete.nvim + ---@class neocomplete.source + @end + +* Methods +** `is_available()` + Each source can have a function to show whether it's available or not. If your source should + for example be enabled for a certain filetype you can just do it like this: + @code lua + function my_source.is_available() + return vim.bo.ft == "lua" + end + @end + + This function will be called quite often so developers should try to keep it more or less + performant. This won't be an issue in the vast majority of cases though. + + #tangle + @code lua + --- Whether the source will provide completions in the current context or not + ---@field is_available? fun(): boolean + @end + +** `get_trigger_characters()` + This function should return characters which trigger completion for the source. If one of those + characters is types the completion will be retriggered. Otherwise newly entered characters are + used for sorting and filtering. + An example for this could be `.`, `\\` and `/` when working with paths. + @code lua + function my_source.get_trigger_characters() + return { ".", "\\", "/" } + end + @end + + #tangle + @code lua + --- Characters which should trigger new completions of the source + ---@field get_trigger_characters? fun(): string[] + @end + +** `complete()` + This is arguably the most important function of each source. This function returns completions. + The function takes in a {:./index:* Completion Context}[completion context] and should return a + list of {:./completion_item:}[completion items]. + + #tangle + @code lua + --- Returns completion in the provided context + ---@field complete fun(completion_context: neocomplete.completion_context, callback: fun(items: neocomplete.completion_item[])): neocomplete.completion_item[] + @end diff --git a/lua/neocomplete/config.lua b/lua/neocomplete/config.lua new file mode 100644 index 0000000..76d644d --- /dev/null +++ b/lua/neocomplete/config.lua @@ -0,0 +1,78 @@ +local config = {} + +---@type neocomplete.config +---@diagnostic disable-next-line: missing-fields +config.options = {} + +---@type neocomplete.config +config.defaults = { + ui = { + menu = { + max_height = 10, + border = "rounded", + format_entry = function(entry) + local type_icons = config.options.ui.type_icons + local entry_kind = type(entry.kind) == "string" and entry.kind + or require("neocomplete.utils.lsp").get_kind_name(entry.kind) + return { + { { entry.label .. " ", "@neocomplete.entry" } }, + { { type_icons[entry_kind] or "", ("@neocomplete.type.%s"):format(entry_kind) } }, + } + end, + alignment = {}, + }, + docs_view = { + max_height = 7, + border = "rounded", + }, + type_icons = { + Class = "  ", + Color = "  ", + Constant = "  ", + Constructor = "  ", + Enum = " 了", + EnumMember = "  ", + Event = "  ", + Field = " 󰜢 ", + File = " ", + Folder = "  ", + Function = "  ", + Interface = "  ", + Keyword = "  ", + Method = " ƒ ", + Module = "  ", + Operator = " 󰆕 ", + Property = "  ", + Reference = "  ", + Snippet = "  ", + Struct = " ", + Text = "  ", + TypeParameter = "", + Unit = " 󰑭 ", + Value = " 󰎠 ", + Variable = "  ", + }, + }, + snippet_expansion = function(snippet_body) + vim.snippet.expand(snippet_body) + end, + enabled = function() + local enabled = true + if vim.api.nvim_get_option_value("buftype", { buf = 0 }) == "prompt" then + enabled = false + end + return enabled + end, +} + +function config.setup(opts) + if vim.tbl_isempty(config.options) then + config.options = vim.tbl_deep_extend("force", config.defaults, opts or {}) + else + config.options = vim.tbl_deep_extend("force", config.options, opts or {}) + end +end + +config.setup({}) + +return config diff --git a/lua/neocomplete/context.lua b/lua/neocomplete/context.lua new file mode 100644 index 0000000..25ae99d --- /dev/null +++ b/lua/neocomplete/context.lua @@ -0,0 +1,36 @@ +---@type neocomplete.context +---@diagnostic disable-next-line: missing-fields +local context = {} + +---@type lsp.CompletionContext + +---@param previous neocomplete.context? +---@return neocomplete.context +function context.new(previous) + local self = setmetatable({}, { __index = context }) + previous = previous or {} + previous.previous = nil + self.previous = previous and vim.deepcopy(previous) + self.bufnr = vim.api.nvim_get_current_buf() + local cursor = vim.api.nvim_win_get_cursor(0) + self.cursor = { row = cursor[1], col = cursor[2] } + return self +end + +function context.changed(self) + if not self.previous then + return true + end + if self.bufnr ~= self.previous.bufnr then + return true + end + if self.cursor.col ~= self.previous.cursor.col then + return true + end + if self.cursor.row ~= self.previous.cursor.row then + return true + end + return false +end + +return context diff --git a/lua/neocomplete/core.lua b/lua/neocomplete/core.lua new file mode 100644 index 0000000..8691728 --- /dev/null +++ b/lua/neocomplete/core.lua @@ -0,0 +1,52 @@ +---@type neocomplete.core +---@diagnostic disable-next-line: missing-fields +local core = {} + +function core.new() + ---@type neocomplete.core + local self = setmetatable({}, { __index = core }) + self.context = require("neocomplete.context").new() + self.menu = require("neocomplete.menu").new() + return self +end + +function core:complete() + local sources = require("neocomplete.sources").get_sources() + local entries = {} + local remaining = #sources + for _, source in ipairs(sources) do + if source.is_available() then + require("neocomplete.sources").complete(self.context, source, function(items) + remaining = remaining - 1 + if items and not vim.tbl_isempty(items) then + vim.list_extend(entries, items) + vim.schedule(function() + if remaining == 0 then + self.menu:open(entries) + end + end) + end + end) + else + remaining = remaining - 1 + end + end +end + +function core.setup(self) + vim.api.nvim_create_autocmd("TextChangedI", { + callback = function() + self:on_change() + end, + group = "neocomplete", + }) +end + +function core.on_change(self) + if self.context and (not self.context:changed()) then + return + end + self:complete() +end + +return core diff --git a/lua/neocomplete/highlights.lua b/lua/neocomplete/highlights.lua new file mode 100644 index 0000000..4480434 --- /dev/null +++ b/lua/neocomplete/highlights.lua @@ -0,0 +1,19 @@ +--[[ +@neocomplete: Fallback for everything +@neocomplete.type: Fallback for the type highlights (only one defined) +@neocomplete.selected: Selected entry +@neocomplete.match: Matched part of entries +--]] + +-- TODO: move into function? + +local hl = function(...) + vim.api.nvim_set_hl(0, ...) +end + +hl("@neocomplete", { link = "Normal", default = true }) +hl("@neocomplete.type", { link = "Normal", default = true }) +hl("@neocomplete.selected", { link = "Visual", default = true }) +hl("@neocomplete.match", { link = "Special", default = true }) +hl("@neocomplete.menu", { link = "NormalFloat", default = true }) +hl("@neocomplete.entry", { italic = true }) diff --git a/lua/neocomplete/init.lua b/lua/neocomplete/init.lua new file mode 100644 index 0000000..823f733 --- /dev/null +++ b/lua/neocomplete/init.lua @@ -0,0 +1,26 @@ +local neocomplete = {} + +---@type neocomplete.core +neocomplete.core = nil + +local function on_insert_enter() + neocomplete.core = require("neocomplete.core").new() + neocomplete.core:setup() +end + +--- Sets up neocomplete +function neocomplete.setup() + require("neocomplete.config").setup() + require("neocomplete.highlights") + + local augroup = vim.api.nvim_create_augroup("neocomplete", {}) + vim.api.nvim_create_autocmd("InsertEnter", { + callback = function() + on_insert_enter() + end, + once = true, + group = augroup, + }) +end + +return neocomplete diff --git a/lua/neocomplete/lsp_source.lua b/lua/neocomplete/lsp_source.lua new file mode 100644 index 0000000..bbefa0b --- /dev/null +++ b/lua/neocomplete/lsp_source.lua @@ -0,0 +1,95 @@ +local lsp_source = {} + +--- @alias vim.lsp.CompletionResult lsp.CompletionList | lsp.CompletionItem[] + +lsp_source.clients = {} + +-- function lsp_source.setup() +-- ---@type neocomplete.source +-- ---@diagnostic disable-next-line: missing-fields +-- local source = {} +-- source.name = "nvim-lsp" +-- require("neocomplete.sources").register_source(source) +-- end + +---@param item lsp.CompletionItem +---@param defaults lsp.ItemDefaults +local function apply_defaults(item, defaults) + if not defaults then + return + end + ---@diagnostic disable-next-line: undefined-field + item.commitCharacters = item.commitCharacters or defaults.commitCharacters + if defaults.editRange then + item.textEdit = item.textEdit or {} + item.textEdit.newText = item.textEdit.newText or item.textEditText or item.insertText + if defaults.editRange.insert then + item.textEdit.insert = defaults.editRange.insert + item.textEdit.replace = defaults.editRange.replace + else + item.textEdit.range = item.textEdit.range or defaults.editRange + end + end + item.insertTextFormat = item.insertTextFormat or defaults.insertTextFormat + item.insertTextMode = item.insertTextMode or defaults.insertTextMode + item.data = item.data or defaults.data +end + +--- @param result vim.lsp.CompletionResult +--- @return lsp.CompletionItem[] +local function get_items(result) + if result.items then + for _, item in ipairs(result.items) do + ---@diagnostic disable-next-line: param-type-mismatch + apply_defaults(item, result.itemDefaults) + end + return result.items + else + return result + end +end + +---@param client vim.lsp.Client +---@return neocomplete.source +function lsp_source.new(client) + ---@type neocomplete.source + local source = { + name = "nvim-lsp " .. client.name, + ---@param context neocomplete.completion_context + complete = function(context, callback) + local params = vim.lsp.util.make_position_params(0, client.offset_encoding) + params.context = context.completion_context + ---@type lsp.CompletionItem + local items + client.request(vim.lsp.protocol.Methods.textDocument_completion, params, function(err, result) + if err then + vim.print(err) + end + if result then + items = get_items(result) + end + callback(items) + end) + end, + get_trigger_characters = function() + return client.server_capabilities.completionProvider.triggerCharacters + end, + is_available = function() + return not client.is_stopped() + end, + } + return source +end + +vim.api.nvim_create_autocmd("InsertEnter", { + callback = function() + -- TODO: always check if clients are from buf and not `is_stopped()` + for _, client in ipairs(vim.lsp.get_clients()) do + if not lsp_source.clients[client.id] then + local source = lsp_source.new(client) + lsp_source.clients[client.id] = source + require("neocomplete.sources").register_source(source) + end + end + end, +}) diff --git a/lua/neocomplete/menu.lua b/lua/neocomplete/menu.lua new file mode 100644 index 0000000..bccadfd --- /dev/null +++ b/lua/neocomplete/menu.lua @@ -0,0 +1,280 @@ +---@type neocomplete.menu +---@diagnostic disable-next-line: missing-fields +local Menu = {} + +local format_utils = require("neocomplete.utils.format") +local utils = require("neocomplete.utils") + +function Menu.new() + ---@type neocomplete.menu + local self = setmetatable({}, { __index = Menu }) + self.entries = nil + self.ns = vim.api.nvim_create_namespace("neocomplete") + self.config = require("neocomplete.config").options + self.input = "e" + self.buf = vim.api.nvim_create_buf(false, true) + self.winnr = nil + self.index = 0 + return self +end + +local function get_texts(aligned_sec) + local texts = {} + for _, aligned_chunks in ipairs(aligned_sec) do + local line_text = {} + for _, chunk in ipairs(aligned_chunks) do + table.insert(line_text, chunk[1]) + end + table.insert(texts, table.concat(line_text, "")) + end + return texts +end + +--- Realigns chunks and adds extmarks +---@param aligned_sec table +---@param realign function(chunk: {[1]: string, [2]: number}): {[1]: string, [2]: number} +---@param buf integer +---@param ns integer +---@param column integer +local function add_extmarks(aligned_sec, realign, buf, ns, column) + for line, aligned_chunks in ipairs(aligned_sec) do + local realigned_chunks = {} + for _, chunk in ipairs(aligned_chunks) do + table.insert(realigned_chunks, realign(chunk)) + end + vim.api.nvim_buf_set_extmark(buf, ns, line - 1, column, { + virt_text = realigned_chunks, + virt_text_pos = "overlay", + hl_mode = "combine", + }) + end +end + +function Menu:draw() + local alignment = self.config.ui.menu.alignment + local width, entry_texts = format_utils.get_width(self.entries) + local aligned_table = format_utils.get_align_tables(self.entries) + local column = 0 + vim.api.nvim_buf_clear_namespace(self.buf, self.ns, 0, -1) + local spaces = {} + for _ = 1, #self.entries do + table.insert(spaces, (" "):rep(width)) + end + vim.api.nvim_buf_set_lines(self.buf, 0, -1, false, spaces) + if self.index and self.index > 0 then + for i = 0, #self.entries do + if i == self.index then + vim.api.nvim_buf_set_extmark(self.buf, self.ns, i - 1, 0, { + virt_text = { { string.rep(" ", width), "@neocomplete.selected" } }, + virt_text_pos = "overlay", + }) + end + end + end + for i, aligned_sec in ipairs(aligned_table) do + if not alignment[i] or alignment[i] == "left" then + local texts = {} + for line, aligned_chunks in ipairs(aligned_sec) do + local line_text = {} + for _, chunk in ipairs(aligned_chunks) do + table.insert(line_text, chunk[1]) + end + local cur_line_text = table.concat(line_text, "") + table.insert(texts, cur_line_text) + vim.api.nvim_buf_set_extmark(self.buf, self.ns, line - 1, column, { + virt_text = aligned_chunks, + virt_text_pos = "overlay", + hl_mode = "combine", + }) + end + column = column + utils.longest(texts) + elseif alignment[i] == "right" then + local texts = get_texts(aligned_sec) + local length = utils.longest(texts) + add_extmarks(aligned_sec, function(chunk) + return { string.rep(" ", length - #chunk[1]) .. chunk[1], chunk[2] } + end, self.buf, self.ns, column) + column = column + length + elseif alignment[i] == "center" then + local texts = get_texts(aligned_sec) + local length = utils.longest(texts) + add_extmarks(aligned_sec, function(chunk) + return { string.rep(" ", math.floor((length - #chunk[1]) / 2)) .. chunk[1], chunk[2] } + end, self.buf, self.ns, column) + column = column + length + end + end + -- TODO: do properly once filtering is implemented + -- for line, entry_text in ipairs(entry_texts) do + -- for char_idx = 1, #entry_text do + -- local char = entry_text:sub(char_idx, char_idx) + -- if self.input:find(char, 1, true) then + -- vim.api.nvim_buf_add_highlight( + -- self.buf, + -- self.ns, + -- "@neocomplete.match", + -- line - 1, + -- char_idx - 1, + -- char_idx + -- ) + -- end + -- end + -- end +end + +function Menu:open_win() + local width, _ = format_utils.get_width(self.entries) + Menu.winnr = vim.api.nvim_open_win(self.buf, false, { + relative = "cursor", + height = math.min(#self.entries, self.config.ui.menu.max_height), + width = width, + style = "minimal", + border = self.config.ui.menu.border, + row = 1, + col = 1, + }) + vim.wo[self.winnr][self.buf].scrolloff = 0 +end + +function Menu:close() + pcall(vim.api.nvim_win_close, self.winnr, true) + Menu.winnr = nil +end + +function Menu:select_next(count) + self.index = self.index + count + if self.index > #self.entries then + self.index = self.index - #self.entries - 1 + end + if self.index > (vim.fn.line("w$", self.winnr) - vim.fn.line("w0", self.winnr)) then + vim.api.nvim_win_call(self.winnr, function() + vim.cmd("normal! " .. self.index - (vim.fn.line("w$", self.winnr) - vim.fn.line("w0", self.winnr)) .. "zt") + end) + end + self:draw() +end + +function Menu:select_prev(count) + self.index = self.index - count + if self.index < 0 then + self.index = #self.entries + self.index + 1 + end + if self.index < (vim.fn.line("w0", self.winnr)) then + vim.api.nvim_win_call(self.winnr, function() + vim.cmd("normal! " .. self.index .. "zt") + end) + end + self:draw() +end + +function Menu:open(entries) + self.entries = entries + if not entries or #entries < 1 then + return + end + if self.winnr then + self:close() + end + self.index = 0 + if not self.winnr then + self:open_win() + self:draw() + end +end + +function Menu:get_active_entry() + if not self.entries then + return nil + end + -- TODO: make configurable (cmpts "autoselect") + if self.index == 0 then + return self.entries[1] + end + return self.entries[self.index] +end + +---@param entry neocomplete.entry +---@return neocomplete.entry +local function normalize_entry(entry) + entry.insertTextFormat = entry.insertTextFormat or 1 + -- TODO: make this earlier because the sorting won't happen here + -- TODO: perhaps remove? are these fields even relevant to complete? + entry.filterText = entry.filterText or entry.label + entry.sortText = entry.sortText or entry.label + entry.insertText = entry.insertText or entry.label + return entry +end + +---@param entry neocomplete.entry +function Menu:complete(entry) + entry = normalize_entry(entry) + local current_buf = vim.api.nvim_get_current_buf() + local cursor_row, cursor_col = unpack(vim.api.nvim_win_get_cursor(0)) --- @type integer, integer + -- get cursor uses 1 based lines, rest of api 0 based + cursor_row = cursor_row - 1 + local line = vim.api.nvim_get_current_line() + local line_to_cursor = line:sub(1, cursor_col) + -- TODO: will this work for all sources? + local word_boundary = vim.fn.match(line_to_cursor, "\\k*$") + + local prefix = line:sub(word_boundary + 1, cursor_col) + + -- TODO: entry.insertTextMode + local is_snippet = entry.insertTextFormat == 2 + + if entry.textEdit and not is_snippet then + -- An edit which is applied to a document when selecting this completion. + -- When an edit is provided the value of `insertText` is ignored. + + if entry.textEdit.range then + vim.lsp.util.apply_text_edits({ entry.textEdit }, current_buf, "utf-8") + else + -- TODO: config option to determine whether to pick insert or replace + local textEdit = { range = entry.textEdit.insert, newText = entry.textEdit.newText } + vim.lsp.util.apply_text_edits({ textEdit }, current_buf, "utf-8") + end + elseif entry.textEdit and is_snippet then + local textEdit + if entry.textEdit.range then + textEdit = { range = entry.textEdit.range, newText = "" } + else + -- TODO: config option to determine whether to pick insert or replace + textEdit = { range = entry.textEdit.insert, newText = "" } + end + vim.lsp.util.apply_text_edits({ textEdit }, current_buf, "utf-8") + vim.api.nvim_win_set_cursor(0, { textEdit.range.start.line + 1, textEdit.range.start.character }) + self.config.snippet_expansion(entry.textEdit.newText) + else + -- TODO: confirm this is correct + -- remove prefix which was used for sorting, text edit should remove it + ---@see lsp.CompletionItem.insertText + local start_char = cursor_col - #prefix + vim.api.nvim_buf_set_text(0, cursor_row, start_char, cursor_row, start_char + #prefix, { "" }) + if is_snippet then + self.config.snippet_expansion(entry.insertText) + else + -- TODO: check if should be `start_char - 1` + vim.api.nvim_buf_set_text(0, cursor_row, start_char, cursor_row, start_char, { entry.insertText }) + end + end + + if entry.additionalTextEdits and #entry.additionalTextEdits > 0 then + vim.lsp.util.apply_text_edits(entry.additionalTextEdits, current_buf, "utf-8") + end + + if entry.command then + ---@diagnostic disable-next-line: param-type-mismatch + vim.lsp.buf.execute_command(entry.command) + end +end + +function Menu:confirm() + local entry = self:get_active_entry() + if not entry then + return + end + self:complete(entry) + self:close() +end + +return Menu diff --git a/lua/neocomplete/sources.lua b/lua/neocomplete/sources.lua new file mode 100644 index 0000000..465da32 --- /dev/null +++ b/lua/neocomplete/sources.lua @@ -0,0 +1,35 @@ +local neocomplete_sources = {} +neocomplete_sources.sources = {} + +---@param source neocomplete.source +function neocomplete_sources.register_source(source) + table.insert(neocomplete_sources.sources, source) +end + +---@return neocomplete.source[] +function neocomplete_sources.get_sources() + return vim.deepcopy(neocomplete_sources.sources) +end + +---@param context neocomplete.context +---@param source neocomplete.source +---@param callback fun(items: neocomplete.entry[]) +function neocomplete_sources.complete(context, source, callback) + local cursor = vim.api.nvim_win_get_cursor(0) + local last_char = vim.api.nvim_get_current_line():sub(cursor[2], cursor[2]) + ---@type lsp.CompletionContext + local completion_context + if vim.tbl_contains(source.get_trigger_characters(), last_char) then + completion_context = { + triggerKind = 2, + triggerCharacter = last_char, + } + else + completion_context = { + triggerKind = 1, + } + end + source.complete({ completion_context = completion_context, context = context }, callback) +end + +return neocomplete_sources diff --git a/lua/neocomplete/types/completion_item.lua b/lua/neocomplete/types/completion_item.lua new file mode 100644 index 0000000..9bc9ea1 --- /dev/null +++ b/lua/neocomplete/types/completion_item.lua @@ -0,0 +1,4 @@ +--- An item returned by a completion source +---@class neocomplete.completion_item: lsp.CompletionItem +--- The kind of the completion item +---@field kind? string|lsp.CompletionItemKind diff --git a/lua/neocomplete/types/config.lua b/lua/neocomplete/types/config.lua new file mode 100644 index 0000000..7ec23e9 --- /dev/null +++ b/lua/neocomplete/types/config.lua @@ -0,0 +1,34 @@ +--- Configuration for neocomplete.nvim +---@class neocomplete.config +--- Configuration for the ui of neocomplete +---@field ui neocomplete.config.ui +--- Function used to expand snippets +---@field snippet_expansion fun(string): nil +--- Configuration for the ui of neocomplete +---@field enabled fun(): boolean +--- The main class for the ui configuration of neocomplete.nvim +---@class neocomplete.config.ui +--- Configuration of the completion menu of neocomplete.nvim +---@field menu neocomplete.config.ui.menu +--- Configuration of the documentation view of neocomplete.nvim +---@field docs_view neocomplete.config.ui.docs +--- The icons for the different compltion item kinds +---@field type_icons neocomplete.config.ui.type_icons +--- Configuration of the completion menu of neocomplete.nvim +---@class neocomplete.config.ui.menu +--- Maximum height of the menu +---@field max_height integer +--- The border of the completion menu +---@field border string|string[]|string[][] +--- How an entry should be formatted +---@field format_entry fun(neocomplete.entry): { [1]: string, [2]: string }[][] +--- How the sections in the menu should be aligned +---@field alignment ("left"|"center"|"right")[] +--- Configuration of the completion menu of neocomplete.nvim +---@class neocomplete.config.ui.docs +--- Maximum height of the documentation view +---@field max_height integer +--- The border of the documentation view +---@field border string|string[]|string[][] +--- The icons used for the different completion item types +---@alias neocomplete.config.ui.type_icons table diff --git a/lua/neocomplete/types/context.lua b/lua/neocomplete/types/context.lua new file mode 100644 index 0000000..b9df536 --- /dev/null +++ b/lua/neocomplete/types/context.lua @@ -0,0 +1,16 @@ +--- Context provided to completion sources +---@class neocomplete.context +--- Context changed since previous +---@field changed fun(neocomplete.context): boolean +--- Create a new context +---@field new fun(neocomplete.context?): neocomplete.context +--- The previous context +---@field previous neocomplete.context? +--- The cursor position +---@field cursor neocomplete.context.cursor +--- The number of the buffer +---@field bufnr integer +--- A cursor position +---@class neocomplete.context.cursor +---@field row integer +---@field col integer diff --git a/lua/neocomplete/types/core.lua b/lua/neocomplete/types/core.lua new file mode 100644 index 0000000..39c10e5 --- /dev/null +++ b/lua/neocomplete/types/core.lua @@ -0,0 +1,14 @@ +--- The core of neocomplete where the main api functions are defined +---@class neocomplete.core +--- Create a new instance of the core +---@field new fun(): neocomplete.core +--- Context instance of the core +---@field context neocomplete.context +--- Menu instance of the core +---@field menu neocomplete.menu +--- Complete +---@field complete fun(neocomplete.core): nil +--- The function that gets invoked when the text changes +---@field on_change fun(neocomplete.core): nil +--- Setup core (for now autocommands) +---@field setup fun(self: neocomplete.core): nil diff --git a/lua/neocomplete/types/entry.lua b/lua/neocomplete/types/entry.lua new file mode 100644 index 0000000..e1b12cd --- /dev/null +++ b/lua/neocomplete/types/entry.lua @@ -0,0 +1,2 @@ +--- An entry for the neocomplete completion menu +---@class neocomplete.entry: lsp.CompletionItem diff --git a/lua/neocomplete/types/menu.lua b/lua/neocomplete/types/menu.lua new file mode 100644 index 0000000..a2ccf3c --- /dev/null +++ b/lua/neocomplete/types/menu.lua @@ -0,0 +1,36 @@ +--- The completion menu of neocomplete +---@class neocomplete.menu +--- Create a new instance of the menu +---@field new fun(): neocomplete.menu +--- Draws the menu +---@field draw fun(self: neocomplete.menu): nil +--- Opens window for the menu +---@field open_win fun(self: neocomplete.menu): nil +--- Select next entry in menu +---@field select_next fun(self: neocomplete.menu, count: integer): nil +--- Select previous entry in menu +---@field select_prev fun(self: neocomplete.menu, count: integer): nil +--- Opens the menu with specified entries +---@field open fun(self: neocomplete.menu, entries: neocomplete.entry[]): nil +--- Closes the window of the menu +---@field close fun(self: neocomplete.menu): nil +--- Gets the currently selected entry +---@field get_active_entry fun(self: neocomplete.menu): neocomplete.entry? +--- Selects the current entry and inserts it's text +---@field confirm fun(self: neocomplete.menu): nil +--- Selects the current entry and inserts it's text +---@field complete fun(self: neocomplete.menu, entry: neocomplete.entry): nil +--- Entries of the menu +---@field entries neocomplete.entry[] +--- Namespace used for the menu +---@field ns integer +--- Neocomplete config to be used in the menu +---@field config neocomplete.config +--- Input of the user which is fuzzy matched +---@field input string +--- Buffer of the menu +---@field buf integer +--- Window of menu +---@field winnr integer? +--- Index of selected item +---@field index integer \ No newline at end of file diff --git a/lua/neocomplete/types/misc.lua b/lua/neocomplete/types/misc.lua new file mode 100644 index 0000000..3d58e4f --- /dev/null +++ b/lua/neocomplete/types/misc.lua @@ -0,0 +1,4 @@ +--- Context provided to completion sources +---@class neocomplete.completion_context +---@field completion_context lsp.CompletionContext +---@field context neocomplete.context diff --git a/lua/neocomplete/types/source.lua b/lua/neocomplete/types/source.lua new file mode 100644 index 0000000..433f0b3 --- /dev/null +++ b/lua/neocomplete/types/source.lua @@ -0,0 +1,8 @@ +--- A completion source for neocomplete.nvim +---@class neocomplete.source +--- Whether the source will provide completions in the current context or not +---@field is_available? fun(): boolean +--- Characters which should trigger new completions of the source +---@field get_trigger_characters? fun(): string[] +--- Returns completion in the provided context +---@field complete fun(completion_context: neocomplete.completion_context, callback: fun(items: neocomplete.completion_item[])): neocomplete.completion_item[] diff --git a/lua/neocomplete/utils/async.lua b/lua/neocomplete/utils/async.lua new file mode 100644 index 0000000..0ef6407 --- /dev/null +++ b/lua/neocomplete/utils/async.lua @@ -0,0 +1,3 @@ +local async = {} + +return async diff --git a/lua/neocomplete/utils/format.lua b/lua/neocomplete/utils/format.lua new file mode 100644 index 0000000..81bc78b --- /dev/null +++ b/lua/neocomplete/utils/format.lua @@ -0,0 +1,39 @@ +local format_utils = {} + +local config = require("neocomplete.config").options +local utils = require("neocomplete.utils") + +--- Gets the width a window for displaying entries must have +---@return number, string[] +function format_utils.get_width(entries) + local formatted_concat = {} + for _, entry in ipairs(entries) do + local formatted = config.ui.menu.format_entry(entry) + local chunk_texts = {} + for _, aligned in ipairs(formatted) do + for _, chunk in ipairs(aligned) do + table.insert(chunk_texts, chunk[1]) + end + end + table.insert(formatted_concat, table.concat(chunk_texts, "")) + end + return utils.longest(formatted_concat), formatted_concat +end + +--- Gets a table with one table for each aligned chunk inside +---@return table +function format_utils.get_align_tables(entries) + local aligned_table = {} + for _, entry in ipairs(entries) do + local formatted = config.ui.menu.format_entry(entry) + for aligned_index, aligned_chunks in ipairs(formatted) do + if not aligned_table[aligned_index] then + aligned_table[aligned_index] = {} + end + table.insert(aligned_table[aligned_index], aligned_chunks) + end + end + return aligned_table +end + +return format_utils diff --git a/lua/neocomplete/utils/init.lua b/lua/neocomplete/utils/init.lua new file mode 100644 index 0000000..b925f88 --- /dev/null +++ b/lua/neocomplete/utils/init.lua @@ -0,0 +1,16 @@ +local utils = {} + +--- Gets lenght of longest string of an array +---@param lines table +---@return number +function utils.longest(lines) + local longest = #(lines[1] or "") + for _, line in ipairs(lines) do + if #line > longest then + longest = #line + end + end + return longest +end + +return utils diff --git a/lua/neocomplete/utils/lsp.lua b/lua/neocomplete/utils/lsp.lua new file mode 100644 index 0000000..f45d17a --- /dev/null +++ b/lua/neocomplete/utils/lsp.lua @@ -0,0 +1,36 @@ +local lsp_utils = {} + +--- Gets the name for an lsp kind +---@param kind_number lsp.CompletionItemKind +function lsp_utils.get_kind_name(kind_number) + local lsp_kinds = { + "Text", + "Method", + "Function", + "Constructor", + "Field", + "Variable", + "Class", + "Interface", + "Module", + "Property", + "Unit", + "Value", + "Enum", + "Keyword", + "Snippet", + "Color", + "File", + "Reference", + "Folder", + "EnumMember", + "Constant", + "Struct", + "Event", + "Operator", + "TypeParameter", + } + return lsp_kinds[kind_number] and lsp_kinds[kind_number] or "" +end + +return lsp_utils diff --git a/plugin/neocomplete.lua b/plugin/neocomplete.lua new file mode 100644 index 0000000..1ececbc --- /dev/null +++ b/plugin/neocomplete.lua @@ -0,0 +1,5 @@ +local loaded_neocomplete = false +if not loaded_neocomplete then + require("neocomplete").setup() + loaded_neocomplete = true +end diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..6e9a19d --- /dev/null +++ b/readme.md @@ -0,0 +1,10 @@ +# neocomplete.nvim + +initialized automatically + +use require"neocomplete.config".setup({...}) to configure + +credits +mariasolos work in core and core in general +nvim-cmp +mfussenegger for helping me out diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..bab5533 --- /dev/null +++ b/stylua.toml @@ -0,0 +1,6 @@ +column_width = 120 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 4 +quote_style = "AutoPreferDouble" +call_parentheses = "Always" diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..9c94d28 --- /dev/null +++ b/test.lua @@ -0,0 +1,24 @@ +require("neocomplete.lsp_source") +vim.keymap.set("i", "", function() + vim.snippet.jump(1) +end) +vim.keymap.set("i", "", function() + vim.snippet.jump(-1) +end) +vim.keymap.set("i", "", function() + require("neocomplete").core.menu:select_next(1) +end) +vim.keymap.set("i", "", function() + require("neocomplete").core.menu:close() +end) +vim.keymap.set("i", "", function() + require("neocomplete").core.menu:select_prev(1) +end) +vim.keymap.set("i", "", function() + require("neocomplete").core.menu:confirm() +end) +vim.api.nvim_create_autocmd("InsertLeave", { + callback = function() + require("neocomplete").core.menu:close() + end, +})