mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-07 21:15:03 +00:00
feat: add a neovim plugin as the canonical lsp client implementation (#1842)
Some checks failed
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / build-vsc-assets (push) Has been cancelled
tinymist::ci / build-vscode (push) Has been cancelled
tinymist::ci / build-vscode-others (push) Has been cancelled
tinymist::ci / publish-vscode (push) Has been cancelled
tinymist::ci / build-binary (push) Has been cancelled
tinymist::ci / E2E Tests (darwin-arm64 on macos-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-22.04) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-2019) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-latest) (push) Has been cancelled
Some checks failed
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / build-vsc-assets (push) Has been cancelled
tinymist::ci / build-vscode (push) Has been cancelled
tinymist::ci / build-vscode-others (push) Has been cancelled
tinymist::ci / publish-vscode (push) Has been cancelled
tinymist::ci / build-binary (push) Has been cancelled
tinymist::ci / E2E Tests (darwin-arm64 on macos-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-22.04) (push) Has been cancelled
tinymist::ci / E2E Tests (linux-x64 on ubuntu-latest) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-2019) (push) Has been cancelled
tinymist::ci / E2E Tests (win32-x64 on windows-latest) (push) Has been cancelled
* fix: bad link * feat(neovim): init lsp * feat(neovim): add bootstrap script * build: add notice
This commit is contained in:
parent
0191e55978
commit
c03898cd3d
36 changed files with 1631 additions and 20 deletions
10
.dockerignore
Normal file
10
.dockerignore
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Ignored for `Dockerfile` build tinymist cli
|
||||||
|
target
|
||||||
|
node_modules
|
||||||
|
editors
|
||||||
|
tools
|
||||||
|
refs
|
||||||
|
local
|
||||||
|
syntaxes
|
||||||
|
docs
|
||||||
|
contrib
|
48
Dockerfile
Normal file
48
Dockerfile
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# ```shell
|
||||||
|
# docker build -t myriaddreamin/tinymist:latest .
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# ## References
|
||||||
|
#
|
||||||
|
# https://stackoverflow.com/questions/58473606/cache-rust-dependencies-with-docker-build
|
||||||
|
# https://stackoverflow.com/a/64528456
|
||||||
|
# https://depot.dev/blog/rust-dockerfile-best-practices
|
||||||
|
|
||||||
|
ARG RUST_VERSION=1.85.1
|
||||||
|
|
||||||
|
FROM rust:${RUST_VERSION}-bookworm AS base
|
||||||
|
RUN apt-get install -y git
|
||||||
|
RUN cargo install sccache --version ^0.7
|
||||||
|
RUN cargo install cargo-chef --version ^0.1
|
||||||
|
ENV RUSTC_WRAPPER=sccache SCCACHE_DIR=/sccache
|
||||||
|
# to download the toolchain
|
||||||
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
|
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
|
||||||
|
cargo --version
|
||||||
|
|
||||||
|
FROM base as planner
|
||||||
|
WORKDIR app
|
||||||
|
# We only pay the installation cost once,
|
||||||
|
# it will be cached from the second build onwards
|
||||||
|
RUN cargo install cargo-chef
|
||||||
|
COPY . .
|
||||||
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
|
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
|
||||||
|
cargo chef prepare --recipe-path recipe.json
|
||||||
|
|
||||||
|
FROM base as builder
|
||||||
|
WORKDIR app
|
||||||
|
RUN cargo install cargo-chef
|
||||||
|
COPY --from=planner /app/recipe.json recipe.json
|
||||||
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
|
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
|
||||||
|
cargo chef cook --release --recipe-path recipe.json
|
||||||
|
COPY . .
|
||||||
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
|
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
|
||||||
|
cargo build -p tinymist --release
|
||||||
|
|
||||||
|
FROM debian:12
|
||||||
|
WORKDIR /app/
|
||||||
|
COPY --from=builder /app/target/release/tinymist /usr/local/bin
|
||||||
|
ENTRYPOINT ["/usr/local/bin/tinymist"]
|
|
@ -193,4 +193,5 @@ autocmd BufNewFile,BufRead *.typ setfiletype typst
|
||||||
|
|
||||||
= Contributing
|
= Contributing
|
||||||
<contributing>
|
<contributing>
|
||||||
You can submit issues or make PRs to #link("https://github.com/Myriad-Dreamin/tinymist")[GitHub];.
|
|
||||||
|
Please check the #github-link("/editors/neovim/CONTRIBUTING.md")[contributing guide] for more information on how to contribute to the project.
|
||||||
|
|
1
editors/neovim/.gitignore
vendored
Normal file
1
editors/neovim/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
target
|
16
editors/neovim/CONTRIBUTING.md
Normal file
16
editors/neovim/CONTRIBUTING.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
This guide extends the root [CONTRIBUTING.md](/CONTRIBUTING.md) file with editor-specific information for Neovim integrations.
|
||||||
|
|
||||||
|
# Running Spec Editor Setup
|
||||||
|
|
||||||
|
```
|
||||||
|
./bootstrap.sh editor
|
||||||
|
```
|
||||||
|
|
||||||
|
# Running Tests
|
||||||
|
|
||||||
|
```
|
||||||
|
./bootstrap.sh test
|
||||||
|
```
|
43
editors/neovim/Dockerfile
Normal file
43
editors/neovim/Dockerfile
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Not for end users! This is a Dockerfile to develop the Neovim plugin it self.
|
||||||
|
# https://github.com/Julian/lean.nvim/tree/1b2752069d700a3e6c7953f5c117d49c134ec711/.devcontainer/lazyvim
|
||||||
|
|
||||||
|
FROM debian:12 AS builder
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
file \
|
||||||
|
ninja-build gettext cmake unzip curl build-essential
|
||||||
|
|
||||||
|
RUN git clone --filter=blob:none --branch stable https://github.com/neovim/neovim && cd neovim && make CMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
|
USER root
|
||||||
|
RUN cd neovim/build && cpack -G DEB && dpkg -i nvim-linux-x86_64.deb
|
||||||
|
|
||||||
|
FROM myriaddreamin/tinymist:0.13.14
|
||||||
|
|
||||||
|
COPY --from=builder /neovim/build/nvim-linux-x86_64.deb /tmp/nvim-linux-x86_64.deb
|
||||||
|
RUN apt-get update && apt-get install -y curl git ripgrep build-essential unzip
|
||||||
|
RUN apt-get update && apt-get install -y python3
|
||||||
|
RUN apt-get install -y /tmp/nvim-linux-x86_64.deb \
|
||||||
|
&& rm /tmp/nvim-linux-x86_64.deb
|
||||||
|
|
||||||
|
RUN useradd --create-home --shell /bin/bash runner
|
||||||
|
USER runner
|
||||||
|
WORKDIR /home/runner
|
||||||
|
|
||||||
|
RUN for dependency in AndrewRadev/switch.vim andymass/vim-matchup neovim/nvim-lspconfig nvim-lua/plenary.nvim tomtom/tcomment_vim lewis6991/satellite.nvim; do git clone --quiet --filter=blob:none "https://github.com/$dependency" "packpath/$(basename $dependency)"; done
|
||||||
|
RUN for dependency in Julian/inanis.nvim; do git clone --quiet --filter=blob:none "https://github.com/$dependency" "packpath/$(basename $dependency)"; done
|
||||||
|
|
||||||
|
ENV XDG_CONFIG_HOME=/home/runner/.config
|
||||||
|
ENV XDG_DATA_HOME=/home/runner/.local/share
|
||||||
|
ENV XDG_STATE_HOME=/home/runner/.local/state
|
||||||
|
ENV XDG_CACHE_HOME=/home/runner/.cache
|
||||||
|
|
||||||
|
COPY init.lua $XDG_CONFIG_HOME/nvim/init.lua
|
||||||
|
COPY plugins/dev.lua $XDG_CONFIG_HOME/nvim/lua/plugins/dev.lua
|
||||||
|
COPY plugins/lsp-folding.lua $XDG_CONFIG_HOME/nvim/lua/plugins/lsp-folding.lua
|
||||||
|
COPY plugins/mason-workaround.lua $XDG_CONFIG_HOME/nvim/lua/plugins/mason-workaround.lua
|
||||||
|
COPY plugins/tinymist.lua $XDG_CONFIG_HOME/nvim/lua/others/tinymist.lua
|
||||||
|
|
||||||
|
# SHELL isn't supported by OCI images
|
||||||
|
ENTRYPOINT []
|
||||||
|
|
187
editors/neovim/LICENSES-LEAN-NVIM
Normal file
187
editors/neovim/LICENSES-LEAN-NVIM
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
===============================================================================
|
||||||
|
The Neovim logo, which has been modified in lean.nvim's logo, is authored by
|
||||||
|
Jason Long & licensed under the Creative Commons Attribution 3.0 Unported
|
||||||
|
license available at https://creativecommons.org/licenses/by/3.0/deed.en
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
The abbreviations JSON file is maintained alongside the Lean VSCode extension,
|
||||||
|
and the Lean logo is maintained alongside the Lean 4 codebase, both of which
|
||||||
|
are licensed under the below Apache License.
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
3
editors/neovim/NOTICE
Normal file
3
editors/neovim/NOTICE
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## Files in `lua` and `spec` directories
|
||||||
|
|
||||||
|
- files in `lua/std` and `spec` are mostly copied from lean.nvim. They are licensed under [LICENSES-LEAN-NVIM](./LICENSES-LEAN-NVIM).
|
|
@ -195,5 +195,3 @@ autocmd BufNewFile,BufRead *.typ setfiletype typst
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
You can submit issues or make PRs to [GitHub](https://github.com/Myriad-Dreamin/tinymist).
|
|
||||||
|
|
19
editors/neovim/bootstrap.sh
Executable file
19
editors/neovim/bootstrap.sh
Executable file
|
@ -0,0 +1,19 @@
|
||||||
|
# todo: it is in very early stage, so we are doing dirty things.
|
||||||
|
|
||||||
|
# python3 ./spec/main.py
|
||||||
|
|
||||||
|
DOCKER_ARGS=
|
||||||
|
if [ "$1" = "test" ]; then
|
||||||
|
DOCKER_ARGS="python3 ./spec/main.py"
|
||||||
|
elif [ "$1" = "bash" ]; then
|
||||||
|
DOCKER_ARGS="bash"
|
||||||
|
elif [ "$1" = "editor" ]; then
|
||||||
|
DOCKER_ARGS="nvim ."
|
||||||
|
else
|
||||||
|
echo "Usage: $0 [test|bash|editor]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ../.. && docker build -t myriaddreamin/tinymist:0.13.14 .
|
||||||
|
docker build -t myriaddreamin/tinymist-nvim:0.13.14 .
|
||||||
|
docker run --rm -it -v $PWD/../../tests/workspaces:/home/runner/dev/workspaces -v $PWD:/home/runner/dev -v $PWD/target/.local:/home/runner/.local -v $PWD/target/.cache:/home/runner/.cache -w /home/runner/dev myriaddreamin/tinymist-nvim:0.13.14 $DOCKER_ARGS
|
96
editors/neovim/init.lua
Normal file
96
editors/neovim/init.lua
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
-- This is a single file that bootstraps neovim editor for development.
|
||||||
|
-- Plus plugins in the `plugins` directory, it should be ready total edit typst files.
|
||||||
|
|
||||||
|
-- -- bootstrap lazy.nvim and your plugins
|
||||||
|
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||||
|
if not (vim.uv or vim.loop).fs_stat(lazypath) then
|
||||||
|
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
|
||||||
|
local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
|
||||||
|
if vim.v.shell_error ~= 0 then
|
||||||
|
vim.api.nvim_echo({
|
||||||
|
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
|
||||||
|
{ out, "WarningMsg" },
|
||||||
|
{ "\nPress any key to exit..." },
|
||||||
|
}, true, {})
|
||||||
|
vim.fn.getchar()
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
vim.opt.rtp:prepend(lazypath)
|
||||||
|
|
||||||
|
|
||||||
|
-- Sync clipboard between OS and Neovim.
|
||||||
|
-- Remove this option if you want your OS clipboard to remain independent.
|
||||||
|
-- See `:help 'clipboard'`
|
||||||
|
vim.opt.clipboard:append 'unnamedplus'
|
||||||
|
|
||||||
|
-- Fix "waiting for osc52 response from terminal" message
|
||||||
|
-- https://github.com/neovim/neovim/issues/28611
|
||||||
|
|
||||||
|
if vim.env.SSH_TTY ~= nil then
|
||||||
|
-- Set up clipboard for ssh
|
||||||
|
|
||||||
|
local function my_paste(_)
|
||||||
|
return function(_)
|
||||||
|
local content = vim.fn.getreg '"'
|
||||||
|
return vim.split(content, '\n')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.g.clipboard = {
|
||||||
|
name = 'OSC 52',
|
||||||
|
copy = {
|
||||||
|
['+'] = require('vim.ui.clipboard.osc52').copy '+',
|
||||||
|
['*'] = require('vim.ui.clipboard.osc52').copy '*',
|
||||||
|
},
|
||||||
|
paste = {
|
||||||
|
-- No OSC52 paste action since wezterm doesn't support it
|
||||||
|
-- Should still paste from nvim
|
||||||
|
['+'] = my_paste '+',
|
||||||
|
['*'] = my_paste '*',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- important for running LSP servers
|
||||||
|
-- vim.lsp.enable('tinymist')
|
||||||
|
|
||||||
|
vim.api.nvim_set_keymap("n", "gd", "<cmd>lua vim.lsp.buf.definition()<CR>", { noremap = true, silent = true })
|
||||||
|
|
||||||
|
require("lazy").setup({
|
||||||
|
spec = {
|
||||||
|
-- add LazyVim and import its plugins
|
||||||
|
{ "LazyVim/LazyVim", import = "lazyvim.plugins" },
|
||||||
|
-- import/override with your plugins
|
||||||
|
{ import = "plugins" },
|
||||||
|
},
|
||||||
|
defaults = {
|
||||||
|
-- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup.
|
||||||
|
-- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default.
|
||||||
|
lazy = false,
|
||||||
|
-- It's recommended to leave version=false for now, since a lot the plugin that support versioning,
|
||||||
|
-- have outdated releases, which may break your Neovim install.
|
||||||
|
version = false, -- always use the latest git commit
|
||||||
|
-- version = "*", -- try installing the latest stable version for plugins that support semver
|
||||||
|
},
|
||||||
|
install = { colorscheme = { "tokyonight" } },
|
||||||
|
checker = {
|
||||||
|
enabled = true, -- check for plugin updates periodically
|
||||||
|
notify = false, -- notify on update
|
||||||
|
}, -- automatically check for plugin updates
|
||||||
|
performance = {
|
||||||
|
rtp = {
|
||||||
|
-- disable some rtp plugins
|
||||||
|
disabled_plugins = {
|
||||||
|
"gzip",
|
||||||
|
-- "matchit",
|
||||||
|
-- "matchparen",
|
||||||
|
-- "netrwPlugin",
|
||||||
|
"tarPlugin",
|
||||||
|
"tohtml",
|
||||||
|
"tutor",
|
||||||
|
"zipPlugin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
72
editors/neovim/lua/std/inductive.lua
Normal file
72
editors/neovim/lua/std/inductive.lua
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
---@generic S
|
||||||
|
---@alias Constructor fun(self: any, ...: any): S
|
||||||
|
---@alias InductiveMethod fun(self: S, ...: any): any
|
||||||
|
|
||||||
|
---@alias ConstructorDefs table<string, Constructor> | table<string, table<string, InductiveMethod>>
|
||||||
|
|
||||||
|
---@class Inductive<S> : { [string]: InductiveMethod }
|
||||||
|
---@operator call(table): `S`
|
||||||
|
|
||||||
|
---Create a new inductive type.
|
||||||
|
---@param name string The name of the new type, used only for errors
|
||||||
|
---@param defs ConstructorDefs A table of constructor definitions
|
||||||
|
---@return Inductive
|
||||||
|
return function(name, defs)
|
||||||
|
local Type = {}
|
||||||
|
|
||||||
|
local to_obj
|
||||||
|
|
||||||
|
local _, first = next(defs)
|
||||||
|
if type(first) ~= 'table' then
|
||||||
|
to_obj = function(_, t)
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local methods = vim.tbl_keys(first)
|
||||||
|
|
||||||
|
to_obj = function(constructor_name, impl)
|
||||||
|
local obj = setmetatable({
|
||||||
|
serialize = function(self)
|
||||||
|
return { [constructor_name] = self[1] }
|
||||||
|
end,
|
||||||
|
}, { __index = Type })
|
||||||
|
|
||||||
|
for _, method_name in ipairs(methods) do
|
||||||
|
local method = impl[method_name]
|
||||||
|
|
||||||
|
if not method then
|
||||||
|
error(('%s method is missing for %s.%s'):format(method_name, name, constructor_name))
|
||||||
|
end
|
||||||
|
obj[method_name] = method
|
||||||
|
impl[method_name] = nil -- so we can tell if there are any extras...
|
||||||
|
end
|
||||||
|
|
||||||
|
local extra = next(impl)
|
||||||
|
if extra then
|
||||||
|
error(('%s method is unexpected for %s.%s'):format(extra, name, constructor_name))
|
||||||
|
end
|
||||||
|
return function(_, ...)
|
||||||
|
return setmetatable({ ... }, {
|
||||||
|
__index = obj,
|
||||||
|
__call = function(_, ...)
|
||||||
|
return Type[constructor_name](Type, ...)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for constructor_name, impl in pairs(defs) do
|
||||||
|
Type[constructor_name] = to_obj(constructor_name, impl)
|
||||||
|
end
|
||||||
|
return setmetatable(Type, {
|
||||||
|
__call = function(self, data, ...)
|
||||||
|
local constructor_name, value = next(data)
|
||||||
|
local constructor = self[constructor_name]
|
||||||
|
if not constructor then
|
||||||
|
error(('Invalid %s constructor: %s'):format(name, constructor_name))
|
||||||
|
end
|
||||||
|
return constructor(self, value, ...)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
119
editors/neovim/lua/std/lsp.lua
Normal file
119
editors/neovim/lua/std/lsp.lua
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
local lsp = {}
|
||||||
|
|
||||||
|
---Convert an LSP position to a (0, 0)-indexed tuple.
|
||||||
|
---
|
||||||
|
---These are used by extmarks.
|
||||||
|
---See `:h api-indexing` for details.
|
||||||
|
---@param position lsp.Position
|
||||||
|
---@param bufnr integer the buffer whose position is referred to
|
||||||
|
---@return { [1]: integer, [2]: integer } position
|
||||||
|
function lsp.position_to_byte0(position, bufnr)
|
||||||
|
local line = vim.api.nvim_buf_get_lines(bufnr, position.line, position.line + 1, false)[1] or ''
|
||||||
|
local ok, col = pcall(vim.str_byteindex, line, position.character, true)
|
||||||
|
return { position.line, ok and col or position.character }
|
||||||
|
end
|
||||||
|
|
||||||
|
---Polyfill vim.fs.relpath-ish for Neovim < 0.11.
|
||||||
|
---
|
||||||
|
---Don't use this for real filesystem operations (as opposed to display),
|
||||||
|
---its implementation is naive!
|
||||||
|
local relpath = vim.fs.relpath
|
||||||
|
or function(base, target)
|
||||||
|
if vim.startswith(target, base .. '/') then
|
||||||
|
return target:sub(#base + 2)
|
||||||
|
end
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
|
||||||
|
---Convert LSP document params inside the current buffer to a human-readable (1, 1)-indexed string.
|
||||||
|
---
|
||||||
|
---Takes the workspace into account in order to return a relative path.
|
||||||
|
---@param params UIParams
|
||||||
|
function lsp.text_document_position_to_string(params)
|
||||||
|
local workspace = vim.lsp.buf.list_workspace_folders()[1] or vim.uv.cwd()
|
||||||
|
local filename = vim.uri_to_fname(params.textDocument.uri)
|
||||||
|
|
||||||
|
return ('%s at %d:%d'):format(
|
||||||
|
relpath(workspace, filename) or filename,
|
||||||
|
params.position.line + 1,
|
||||||
|
params.position.character + 1
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Convert an LSP range to a human-readable (1, 1)-indexed string.
|
||||||
|
---
|
||||||
|
---We use 1-based indexing here as the `gg` and `|` motions are 1-indexed,
|
||||||
|
---which is the most likely way a human (you?) will interact with this
|
||||||
|
---information.
|
||||||
|
---@param range lsp.Range
|
||||||
|
function lsp.range_to_string(range)
|
||||||
|
return ('%d:%d-%d:%d'):format(
|
||||||
|
range.start.line + 1,
|
||||||
|
range.start.character + 1,
|
||||||
|
range['end'].line + 1,
|
||||||
|
range['end'].character + 1
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ~*~ vim.lsp._private_functions we still need... ~*~
|
||||||
|
|
||||||
|
local format_line_ending = {
|
||||||
|
['unix'] = '\n',
|
||||||
|
['dos'] = '\r\n',
|
||||||
|
['mac'] = '\r',
|
||||||
|
}
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param bufnr (number)
|
||||||
|
---@return string
|
||||||
|
local function buf_get_line_ending(bufnr)
|
||||||
|
return format_line_ending[vim.bo[bufnr].fileformat] or '\n'
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---Returns full text of buffer {bufnr} as a string.
|
||||||
|
---
|
||||||
|
---@param bufnr (number) Buffer handle, or 0 for current.
|
||||||
|
---@return string # Buffer text as string.
|
||||||
|
function lsp.buf_get_full_text(bufnr)
|
||||||
|
local line_ending = buf_get_line_ending(bufnr)
|
||||||
|
local text = table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, true), line_ending)
|
||||||
|
if vim.bo[bufnr].eol then
|
||||||
|
text = text .. line_ending
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
-- vim.lsp.diagnostic has a *private* `diagnostic_lsp_to_vim` :/ ...
|
||||||
|
--
|
||||||
|
-- the below comes from there / is required for assembling vim.Diagnostic
|
||||||
|
-- objects out of LSP responses
|
||||||
|
|
||||||
|
---@param severity lsp.DiagnosticSeverity
|
||||||
|
function lsp.severity_lsp_to_vim(severity)
|
||||||
|
if type(severity) == 'string' then
|
||||||
|
severity = vim.lsp.protocol.DiagnosticSeverity[severity] ---@type integer
|
||||||
|
end
|
||||||
|
return severity
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param diagnostic lsp.Diagnostic
|
||||||
|
---@param client_id integer
|
||||||
|
---@return table?
|
||||||
|
function lsp.tags_lsp_to_vim(diagnostic, client_id)
|
||||||
|
local tags ---@type table?
|
||||||
|
for _, tag in ipairs(diagnostic.tags or {}) do
|
||||||
|
if tag == vim.lsp.protocol.DiagnosticTag.Unnecessary then
|
||||||
|
tags = tags or {}
|
||||||
|
tags.unnecessary = true
|
||||||
|
elseif tag == vim.lsp.protocol.DiagnosticTag.Deprecated then
|
||||||
|
tags = tags or {}
|
||||||
|
tags.deprecated = true
|
||||||
|
else
|
||||||
|
vim.lsp.log.info(string.format('Unknown DiagnosticTag %d from LSP client %d', tag, client_id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return tags
|
||||||
|
end
|
||||||
|
|
||||||
|
return lsp
|
25
editors/neovim/lua/std/nvim/buffer.lua
Normal file
25
editors/neovim/lua/std/nvim/buffer.lua
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
---A Neovim buffer.
|
||||||
|
---@class Buffer
|
||||||
|
---@field bufnr integer The buffer number
|
||||||
|
local Buffer = {}
|
||||||
|
Buffer.__index = Buffer
|
||||||
|
|
||||||
|
---Bind to a Neovim buffer.
|
||||||
|
---@param bufnr? integer buffer number, defaulting to the current one
|
||||||
|
---@return Buffer
|
||||||
|
function Buffer:from_bufnr(bufnr)
|
||||||
|
return setmetatable({ bufnr = bufnr or vim.api.nvim_get_current_buf() }, self)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Bind to the current buffer.
|
||||||
|
function Buffer:current()
|
||||||
|
return self:from_bufnr(vim.api.nvim_get_current_buf())
|
||||||
|
end
|
||||||
|
|
||||||
|
---The buffer's name.
|
||||||
|
---@return string name
|
||||||
|
function Buffer:name()
|
||||||
|
return vim.api.nvim_buf_get_name(self.bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Buffer
|
55
editors/neovim/lua/std/nvim/tab.lua
Normal file
55
editors/neovim/lua/std/nvim/tab.lua
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
local Window = require 'std.nvim.window'
|
||||||
|
|
||||||
|
---A Neovim tab.
|
||||||
|
---@class Tab
|
||||||
|
---@field id integer The tab number
|
||||||
|
local Tab = {}
|
||||||
|
Tab.__index = Tab
|
||||||
|
|
||||||
|
---Bind to a Neovim tab.
|
||||||
|
---@param id? integer tab ID, defaulting to the current one
|
||||||
|
---@return Tab
|
||||||
|
function Tab:from_id(id)
|
||||||
|
return setmetatable({ id = id or vim.api.nvim_get_current_tabpage() }, self)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Bind to the current tab.
|
||||||
|
function Tab:current()
|
||||||
|
return self:from_id(vim.api.nvim_get_current_tabpage())
|
||||||
|
end
|
||||||
|
|
||||||
|
---All current tabs.
|
||||||
|
---@return Tab[] tabs
|
||||||
|
function Tab:all()
|
||||||
|
return vim
|
||||||
|
.iter(vim.api.nvim_list_tabpages())
|
||||||
|
:map(function(tab_id)
|
||||||
|
return self:from_id(tab_id)
|
||||||
|
end)
|
||||||
|
:totable()
|
||||||
|
end
|
||||||
|
|
||||||
|
---Open a new tab page.
|
||||||
|
function Tab:new()
|
||||||
|
-- See https://github.com/neovim/neovim/pull/27223
|
||||||
|
vim.cmd.tabnew()
|
||||||
|
return self:current()
|
||||||
|
end
|
||||||
|
|
||||||
|
---Close the tab page.
|
||||||
|
function Tab:close()
|
||||||
|
vim.cmd.tabclose(vim.api.nvim_tabpage_get_number(self.id))
|
||||||
|
end
|
||||||
|
|
||||||
|
---Return the windows present in the tab.
|
||||||
|
---@return Window[] windows
|
||||||
|
function Tab:windows()
|
||||||
|
return vim
|
||||||
|
.iter(vim.api.nvim_tabpage_list_wins(self.id))
|
||||||
|
:map(function(win_id)
|
||||||
|
return Window:from_id(win_id)
|
||||||
|
end)
|
||||||
|
:totable()
|
||||||
|
end
|
||||||
|
|
||||||
|
return Tab
|
153
editors/neovim/lua/std/nvim/window.lua
Normal file
153
editors/neovim/lua/std/nvim/window.lua
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
local Buffer = require 'std.nvim.buffer'
|
||||||
|
|
||||||
|
---A Neovim window.
|
||||||
|
---@class Window
|
||||||
|
---@field id integer The window ID
|
||||||
|
local Window = {}
|
||||||
|
Window.__index = Window
|
||||||
|
|
||||||
|
---Bind to a Neovim window.
|
||||||
|
---@param id? integer Window ID, defaulting to the current window
|
||||||
|
---@return Window
|
||||||
|
function Window:from_id(id)
|
||||||
|
return setmetatable({ id = id or vim.api.nvim_get_current_win() }, self)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Bind to the current window.
|
||||||
|
function Window:current()
|
||||||
|
return self:from_id(vim.api.nvim_get_current_win())
|
||||||
|
end
|
||||||
|
|
||||||
|
---Return the buffer shown in the window.
|
||||||
|
---@return Buffer buffer
|
||||||
|
function Window:buffer()
|
||||||
|
return Buffer:from_bufnr(self:bufnr())
|
||||||
|
end
|
||||||
|
|
||||||
|
---Return the buffer number of the window.
|
||||||
|
---@return integer bufnr
|
||||||
|
function Window:bufnr()
|
||||||
|
return vim.api.nvim_win_get_buf(self.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Return the tab the window is on.
|
||||||
|
---@return Tab tab
|
||||||
|
function Window:tab()
|
||||||
|
return require('std.nvim.tab'):from_id(vim.api.nvim_win_get_tabpage(self.id))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class SplitOpts
|
||||||
|
---@field buffer? Buffer the buffer to open in the new window (default current)
|
||||||
|
---@field enter? boolean whether to enter the window (default false)
|
||||||
|
---@field direction? 'left'|'right'|'above'|'below' the direction to split
|
||||||
|
|
||||||
|
---Split a new window relative to this window.
|
||||||
|
---@param opts? SplitOpts
|
||||||
|
---@return Window
|
||||||
|
function Window:split(opts)
|
||||||
|
opts = vim.tbl_extend('keep', opts or {}, { enter = false })
|
||||||
|
local direction = opts.direction or vim.o.splitright and 'right' or 'left'
|
||||||
|
|
||||||
|
local config = { win = self.id, split = direction }
|
||||||
|
local bufnr = opts.buffer and opts.buffer.bufnr or 0
|
||||||
|
local id = vim.api.nvim_open_win(bufnr, opts.enter, config)
|
||||||
|
return Window:from_id(id)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Return the window's current cursor position.
|
||||||
|
---
|
||||||
|
---(1, 0)-indexed, like `nvim_win_get_cursor()`.
|
||||||
|
---@return { [1]: integer, [2]: integer } pos
|
||||||
|
function Window:cursor()
|
||||||
|
return vim.api.nvim_win_get_cursor(self.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Set the window's current cursor position.
|
||||||
|
---
|
||||||
|
---(1, 0)-indexed, like `nvim_win_set_cursor()`.
|
||||||
|
---@param pos { [1]: integer, [2]: integer } the new cursor position
|
||||||
|
function Window:set_cursor(pos)
|
||||||
|
vim.api.nvim_win_set_cursor(self.id, pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Is this the current window?
|
||||||
|
function Window:is_current()
|
||||||
|
return vim.api.nvim_get_current_win() == self.id
|
||||||
|
end
|
||||||
|
|
||||||
|
---Make this window be the current one.
|
||||||
|
function Window:make_current()
|
||||||
|
vim.api.nvim_set_current_win(self.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Return the window's height.
|
||||||
|
---@return integer height
|
||||||
|
function Window:height()
|
||||||
|
return vim.api.nvim_win_get_height(self.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Set the window's height.
|
||||||
|
---@param height integer
|
||||||
|
function Window:set_height(height)
|
||||||
|
vim.api.nvim_win_set_height(self.id, height)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Return the window's width.
|
||||||
|
---@return integer width
|
||||||
|
function Window:width()
|
||||||
|
return vim.api.nvim_win_get_width(self.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Set the window's width.
|
||||||
|
---@param width integer
|
||||||
|
function Window:set_width(width)
|
||||||
|
vim.api.nvim_win_set_width(self.id, width)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Run a function with the window as temporary current window.
|
||||||
|
function Window:call(fn)
|
||||||
|
vim.api.nvim_win_call(self.id, fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Check if the window is valid.
|
||||||
|
---
|
||||||
|
---Do you wonder exactly what that corresponds to?
|
||||||
|
---Well keep wondering because the Neovim docstring doesn't elaborate.
|
||||||
|
---
|
||||||
|
---But for one, closed windows return `false`.
|
||||||
|
---@return boolean valid
|
||||||
|
function Window:is_valid()
|
||||||
|
return vim.api.nvim_win_is_valid(self.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Close the window.
|
||||||
|
function Window:close()
|
||||||
|
vim.api.nvim_win_close(self.id, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Beyond the Neovim API...
|
||||||
|
|
||||||
|
---Move the cursor to a given position.
|
||||||
|
---
|
||||||
|
---(1, 0)-indexed, like `nvim_win_set_cursor()`.
|
||||||
|
---
|
||||||
|
---Fires `CursorMoved` if (and only if) the cursor is now at a new position.
|
||||||
|
---@param pos { [1]: integer, [2]: integer } the new cursor position
|
||||||
|
function Window:move_cursor(pos)
|
||||||
|
local start = self:cursor()
|
||||||
|
self:set_cursor(pos)
|
||||||
|
if vim.deep_equal(self:cursor(), start) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.api.nvim_exec_autocmds('CursorMoved', { buffer = self:bufnr() })
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get the contents of the remainder of the line with the window's cursor.
|
||||||
|
---@return string contents text from cursor position to the end of line
|
||||||
|
function Window:rest_of_cursor_line()
|
||||||
|
local row, col = unpack(self:cursor())
|
||||||
|
local line = vim.api.nvim_buf_get_lines(self:bufnr(), row - 1, row, true)[1]
|
||||||
|
return line:sub(col + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Window
|
22
editors/neovim/lua/std/subprocess.lua
Normal file
22
editors/neovim/lua/std/subprocess.lua
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
local subprocess = {}
|
||||||
|
|
||||||
|
---Run a subprocess, blocking on exit, and returning its stdout.
|
||||||
|
---@return string: the lines of stdout of the exited process
|
||||||
|
function subprocess.check_output(...)
|
||||||
|
local process = vim.system(...)
|
||||||
|
local result = process:wait()
|
||||||
|
if result.code == 0 then
|
||||||
|
return result.stdout
|
||||||
|
end
|
||||||
|
|
||||||
|
error(
|
||||||
|
string.format(
|
||||||
|
'%s exited with non-zero exit status %d.\nstderr contained:\n%s',
|
||||||
|
vim.inspect(process.cmd),
|
||||||
|
result.code,
|
||||||
|
result.stderr
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return subprocess
|
29
editors/neovim/lua/std/text.lua
Normal file
29
editors/neovim/lua/std/text.lua
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
local text = {}
|
||||||
|
|
||||||
|
local function max_common_indent(str)
|
||||||
|
local _, _, common_indent, rest = str:find '^(%s*)(.*)'
|
||||||
|
local common_indent_len = #common_indent
|
||||||
|
local len
|
||||||
|
for indent in rest:gmatch '\n( +)' do
|
||||||
|
len = #indent
|
||||||
|
if len < common_indent_len then
|
||||||
|
common_indent, common_indent_len = indent, len
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return common_indent
|
||||||
|
end
|
||||||
|
|
||||||
|
---Dedent a multi-line string.
|
||||||
|
---@param str string
|
||||||
|
function text.dedent(str)
|
||||||
|
str = str:gsub('\n *$', '\n') -- trim leading/trailing space
|
||||||
|
local prefix = max_common_indent(str)
|
||||||
|
return str:gsub('^' .. prefix, ''):gsub('\n' .. prefix, '\n')
|
||||||
|
end
|
||||||
|
|
||||||
|
---Build a single-line string out a multiline one, replacing \n with spaces.
|
||||||
|
function text.s(str)
|
||||||
|
return text.dedent(str):gsub('\n', ' ')
|
||||||
|
end
|
||||||
|
|
||||||
|
return text
|
16
editors/neovim/lua/tinymist/init.lua
Normal file
16
editors/neovim/lua/tinymist/init.lua
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
local tinymist = {}
|
||||||
|
|
||||||
|
---Setup function to be run in your init.lua.
|
||||||
|
---@param opts lean.Config Configuration options
|
||||||
|
function tinymist.setup(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
|
opts.lsp = opts.lsp or {}
|
||||||
|
if opts.lsp.enable ~= false then
|
||||||
|
require('tinymist.lsp').enable(opts.lsp)
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.g.tinymist_config = opts
|
||||||
|
end
|
||||||
|
|
||||||
|
return tinymist
|
17
editors/neovim/lua/tinymist/lsp.lua
Normal file
17
editors/neovim/lua/tinymist/lsp.lua
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
local lsp = {}
|
||||||
|
|
||||||
|
---@param opts LeanClientConfig
|
||||||
|
function lsp.enable(opts)
|
||||||
|
opts.capabilities = opts.capabilities or vim.lsp.protocol.make_client_capabilities()
|
||||||
|
opts = vim.tbl_deep_extend('keep', opts, {
|
||||||
|
init_options = {
|
||||||
|
-- editDelay = 10, -- see lean#289
|
||||||
|
hasWidgets = true,
|
||||||
|
},
|
||||||
|
on_init = function(_, response)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
require('lspconfig').tinymist.setup(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return lsp
|
61
editors/neovim/plugins/dev.lua
Normal file
61
editors/neovim/plugins/dev.lua
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
local tinymist = require("others.tinymist")[2]
|
||||||
|
|
||||||
|
-- set binary path
|
||||||
|
tinymist.opts.servers.tinymist.cmd = { "tinymist" }
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
{ "mason-org/mason.nvim", version = "^1.0.0" },
|
||||||
|
{ "mason-org/mason-lspconfig.nvim", version = "^1.0.0" },
|
||||||
|
|
||||||
|
-- spread tinymist
|
||||||
|
tinymist,
|
||||||
|
|
||||||
|
-- change some telescope options and a keymap to browse plugin files
|
||||||
|
{
|
||||||
|
"nvim-telescope/telescope.nvim",
|
||||||
|
keys = {
|
||||||
|
-- add a keymap to browse plugin files
|
||||||
|
-- stylua: ignore
|
||||||
|
{
|
||||||
|
"<leader>fp",
|
||||||
|
function() require("telescope.builtin").find_files({ cwd = require("lazy.core.config").options.root }) end,
|
||||||
|
desc = "Find Plugin File",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
-- change some options
|
||||||
|
opts = {
|
||||||
|
defaults = {
|
||||||
|
layout_strategy = "horizontal",
|
||||||
|
layout_config = { prompt_position = "top" },
|
||||||
|
sorting_strategy = "ascending",
|
||||||
|
winblend = 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- add more treesitter parsers
|
||||||
|
{
|
||||||
|
"nvim-treesitter/nvim-treesitter",
|
||||||
|
opts = {
|
||||||
|
ensure_installed = {
|
||||||
|
"bash",
|
||||||
|
"json",
|
||||||
|
"lua",
|
||||||
|
"markdown",
|
||||||
|
"markdown_inline",
|
||||||
|
"vim",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
-- add any tools you want to have installed below
|
||||||
|
{
|
||||||
|
"williamboman/mason.nvim",
|
||||||
|
opts = {
|
||||||
|
ensure_installed = {
|
||||||
|
"stylua",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
4
editors/neovim/plugins/mason-workaround.lua
Normal file
4
editors/neovim/plugins/mason-workaround.lua
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
return {
|
||||||
|
{ "mason-org/mason.nvim", version = "^1.0.0" },
|
||||||
|
{ "mason-org/mason-lspconfig.nvim", version = "^1.0.0" },
|
||||||
|
}
|
|
@ -1,7 +1,39 @@
|
||||||
|
-- https://github.com/neovim/neovim/blob/381806729db1016106b02d866c4f4f64f76a351f/src/nvim/highlight_group.c
|
||||||
|
local links = {
|
||||||
|
-- Unable to pick a suitable highlight group for the following:
|
||||||
|
-- ['@lsp.type.raw.typst'] = '@markup.link',
|
||||||
|
-- ['@lsp.type.punct.typst'] = '@punctuation',
|
||||||
|
-- ['@lsp.mod.math.typst'] = '@',
|
||||||
|
-- "*.strong.emph": [
|
||||||
|
-- "markup.bold.typst markup.italic.typst"
|
||||||
|
-- ],
|
||||||
|
|
||||||
|
['@lsp.mod.strong.typst'] = '@markup.strong',
|
||||||
|
['@lsp.mod.emph.typst'] = '@markup.italic',
|
||||||
|
|
||||||
|
['@lsp.type.bool.typst'] = '@boolean',
|
||||||
|
['@lsp.type.escape.typst'] = '@string.escape',
|
||||||
|
['@lsp.type.link.typst'] = '@markup.link',
|
||||||
|
['@lsp.typemod.delim.math.typst'] = '@punctuation',
|
||||||
|
['@lsp.typemod.operator.math.typst'] = '@operator',
|
||||||
|
['@lsp.type.heading.typst'] = '@markup.heading',
|
||||||
|
['@lsp.type.pol.typst'] = '@variable',
|
||||||
|
['@lsp.type.error.typst'] = 'DiagnosticError',
|
||||||
|
['@lsp.type.term.typst'] = '@markup.bold',
|
||||||
|
['@lsp.type.marker.typst'] = '@punctuation',
|
||||||
|
['@lsp.type.ref.typst'] = '@label',
|
||||||
|
['@lsp.type.label.typst'] = '@label',
|
||||||
|
}
|
||||||
|
|
||||||
|
for newgroup, oldgroup in pairs(links) do
|
||||||
|
vim.api.nvim_set_hl(0, newgroup, { link = oldgroup, default = true })
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
-- requires tinymist
|
-- requires tinymist
|
||||||
{
|
{
|
||||||
"williamboman/mason.nvim",
|
"williamboman/mason.nvim",
|
||||||
|
version = "^1.0.0",
|
||||||
opts = {
|
opts = {
|
||||||
ensure_installed = {
|
ensure_installed = {
|
||||||
"tinymist",
|
"tinymist",
|
||||||
|
@ -23,31 +55,32 @@ return {
|
||||||
root_dir = function()
|
root_dir = function()
|
||||||
return vim.fn.getcwd()
|
return vim.fn.getcwd()
|
||||||
end,
|
end,
|
||||||
--- See [Tinymist Server Configuration](https://github.com/Myriad-Dreamin/tinymist/blob/main/Configuration.md) for references.
|
single_file_support = true, -- Fixes LSP attachment in non-Git directories
|
||||||
|
--- See [Tinymist Server Configuration](https://github.com/Myriad-Dreamin/tinymist/blob/main/editors/neovim/Configuration.md) for references.
|
||||||
settings = {
|
settings = {
|
||||||
--- You could set the formatter mode to use lsp-enhanced formatters.
|
--- You could set the formatter mode to use lsp-enhanced formatters.
|
||||||
-- formatterMode = "typstyle",
|
-- formatterMode = "typstyle",
|
||||||
|
|
||||||
--- If you would love to preview exported PDF files at the same time,
|
--- If you would love to preview exported PDF files at the same time,
|
||||||
--- you could set this to `onType` and open the file with your favorite PDF viewer.
|
--- you could set this to `onType` and open the file with your favorite PDF viewer.
|
||||||
-- exportPdf = "onType",
|
-- exportPdf = "onType",
|
||||||
},
|
},
|
||||||
on_attach = function(client, bufnr)
|
on_attach = function(client, bufnr)
|
||||||
vim.keymap.set("n", "<leader>tp", function()
|
vim.keymap.set("n", "<leader>tp", function()
|
||||||
client:exec_cmd({
|
client:exec_cmd({
|
||||||
title = "pin",
|
title = "pin",
|
||||||
command = "tinymist.pinMain",
|
command = "tinymist.pinMain",
|
||||||
arguments = { vim.api.nvim_buf_get_name(0) },
|
arguments = { vim.api.nvim_buf_get_name(0) },
|
||||||
}, { bufnr = bufnr })
|
}, { bufnr = bufnr })
|
||||||
end, { desc = "[T]inymist [P]in", noremap = true })
|
end, { desc = "[T]inymist [P]in", noremap = true })
|
||||||
|
|
||||||
vim.keymap.set("n", "<leader>tu", function()
|
vim.keymap.set("n", "<leader>tu", function()
|
||||||
client:exec_cmd({
|
client:exec_cmd({
|
||||||
title = "unpin",
|
title = "unpin",
|
||||||
command = "tinymist.pinMain",
|
command = "tinymist.pinMain",
|
||||||
arguments = { vim.v.null },
|
arguments = { vim.v.null },
|
||||||
}, { bufnr = bufnr })
|
}, { bufnr = bufnr })
|
||||||
end, { desc = "[T]inymist [U]npin", noremap = true })
|
end, { desc = "[T]inymist [U]npin", noremap = true })
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
45
editors/neovim/scripts/minimal_init.lua
Normal file
45
editors/neovim/scripts/minimal_init.lua
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
vim.o.display = 'lastline' -- Avoid neovim/neovim#11362
|
||||||
|
vim.o.directory = ''
|
||||||
|
vim.o.shada = ''
|
||||||
|
|
||||||
|
local this_dir = vim.fs.dirname(debug.getinfo(1, 'S').source:sub(2))
|
||||||
|
local lean_nvim_dir = vim.fs.dirname(this_dir)
|
||||||
|
local packpath = vim.fs.joinpath('/home/runner/packpath/*')
|
||||||
|
vim.opt.runtimepath:append(packpath)
|
||||||
|
|
||||||
|
-- Doing this unconditionally seems to fail a random indent test?!?!
|
||||||
|
-- Inanis/Plenary will automatically set rtp+. (which seems wrong, but OK)
|
||||||
|
-- so really we need this just for `just nvim`...
|
||||||
|
if #vim.api.nvim_list_uis() ~= 0 then
|
||||||
|
vim.opt.runtimepath:append(lean_nvim_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.cmd [[
|
||||||
|
runtime! plugin/lspconfig.vim
|
||||||
|
runtime! plugin/matchit.vim
|
||||||
|
runtime! plugin/plenary.vim
|
||||||
|
runtime! plugin/switch.vim
|
||||||
|
runtime! plugin/tcomment.vim
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- plenary forks subprocesses, so enable coverage here when appropriate
|
||||||
|
if vim.env.LEAN_NVIM_COVERAGE then
|
||||||
|
local luapath = vim.fs.joinpath(lean_nvim_dir, 'luapath')
|
||||||
|
package.path = package.path
|
||||||
|
.. ';'
|
||||||
|
.. luapath
|
||||||
|
.. '/share/lua/5.1/?.lua;'
|
||||||
|
.. luapath
|
||||||
|
.. '/share/lua/5.1/?/init.lua;;'
|
||||||
|
package.cpath = package.cpath .. ';' .. luapath .. '/lib/lua/5.1/?.so;'
|
||||||
|
require 'luacov'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if vim.env.LEAN_NVIM_DEBUG then
|
||||||
|
-- local port = 8088
|
||||||
|
-- if vim.env.LEAN_NVIM_DEBUG ~= '' and vim.env.LEAN_NVIM_DEBUG ~= '1' then
|
||||||
|
-- port = tonumber(vim.env.LEAN_NVIM_DEBUG)
|
||||||
|
-- end
|
||||||
|
-- require('osv').launch { host = '127.0.0.1', port = port }
|
||||||
|
-- vim.wait(5000)
|
||||||
|
-- end
|
68
editors/neovim/spec/fixtures.lua
Normal file
68
editors/neovim/spec/fixtures.lua
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
local this_file = debug.getinfo(1).source:match '@(.*)$'
|
||||||
|
|
||||||
|
local root = vim.fs.joinpath(vim.fs.dirname(this_file), '../workspaces')
|
||||||
|
local book_root = vim.fs.joinpath(root, 'book')
|
||||||
|
local ind = vim.fs.joinpath(root, 'individuals')
|
||||||
|
-- local indent = vim.fs.joinpath(root, 'indent')
|
||||||
|
-- local widgets = vim.fs.joinpath(root, 'widgets')
|
||||||
|
-- local project_root = vim.fs.normalize(vim.fs.joinpath(root, 'example-project'))
|
||||||
|
|
||||||
|
local function child(name)
|
||||||
|
return vim.fs.joinpath(book_root, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
local fixtures = {
|
||||||
|
-- indent = function()
|
||||||
|
-- return vim.iter(vim.fs.dir(indent)):map(function(each)
|
||||||
|
-- local name, replaced = each:gsub('.in.lean$', '')
|
||||||
|
-- if replaced == 0 then
|
||||||
|
-- return
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- ---@class IndentFixture
|
||||||
|
-- ---@field description string the name of the fixture
|
||||||
|
-- ---@field unindented string the path to the unindented version
|
||||||
|
-- ---@field expected string[] the expected indented lines
|
||||||
|
|
||||||
|
-- ---@type IndentFixture
|
||||||
|
-- return {
|
||||||
|
-- description = name:gsub('_', ' '),
|
||||||
|
-- unindented = vim.fs.joinpath(indent, each),
|
||||||
|
-- expected = vim.fn.readfile(vim.fs.joinpath(indent, name .. '.lean')),
|
||||||
|
-- }
|
||||||
|
-- end)
|
||||||
|
-- end,
|
||||||
|
-- widgets = widgets,
|
||||||
|
|
||||||
|
project = {
|
||||||
|
root = book_root,
|
||||||
|
child = child,
|
||||||
|
|
||||||
|
some_existing_file = child 'main.typ',
|
||||||
|
some_nonexisting_file = child 'bad.typ',
|
||||||
|
some_nested_existing_file = child 'chapters/chapter1.typ',
|
||||||
|
some_nested_nonexisting_file = child 'chapters/chapter3.typ',
|
||||||
|
|
||||||
|
-- some_dependency_file = child '.lake/packages/importGraph/ImportGraph/Imports.lean',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixtures.project:files()
|
||||||
|
return vim.iter {
|
||||||
|
['existing'] = self.some_existing_file,
|
||||||
|
['nonexisting'] = self.some_nonexisting_file,
|
||||||
|
['nested existing'] = self.some_nested_existing_file,
|
||||||
|
['nested nonexisting'] = self.some_nested_nonexisting_file,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
assert.is.truthy(vim.uv.fs_stat(fixtures.project.some_existing_file))
|
||||||
|
assert.is.falsy(vim.uv.fs_stat(fixtures.project.some_nonexisting_file))
|
||||||
|
assert.is.truthy(vim.uv.fs_stat(fixtures.project.some_nested_existing_file))
|
||||||
|
assert.is.falsy(vim.uv.fs_stat(fixtures.project.some_nested_nonexisting_file))
|
||||||
|
-- assert.is.truthy(vim.uv.fs_stat(fixtures.project.some_dependency_file))
|
||||||
|
|
||||||
|
-- assert.is.truthy(vim.uv.fs_stat(indent))
|
||||||
|
-- assert.is.truthy(vim.uv.fs_stat(widgets))
|
||||||
|
|
||||||
|
return fixtures
|
294
editors/neovim/spec/helpers.lua
Normal file
294
editors/neovim/spec/helpers.lua
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
local Tab = require 'std.nvim.tab'
|
||||||
|
local Window = require 'std.nvim.window'
|
||||||
|
local assert = require 'luassert'
|
||||||
|
local text = require 'std.text'
|
||||||
|
local fixtures = require 'spec.fixtures'
|
||||||
|
|
||||||
|
-- local progress = require 'lean.progress'
|
||||||
|
-- local util = require 'lean._util'
|
||||||
|
|
||||||
|
---@class LeanClientCapabilities : lsp.ClientCapabilities
|
||||||
|
---@field silentDiagnosticSupport? boolean Whether the client supports `DiagnosticWith.isSilent = true`.
|
||||||
|
|
||||||
|
---@class LeanClientConfig : vim.lsp.ClientConfig
|
||||||
|
---@field lean? LeanClientCapabilities
|
||||||
|
|
||||||
|
---Find the `vim.lsp.Client` attached to the given buffer.
|
||||||
|
---@param bufnr? number
|
||||||
|
---@return vim.lsp.Client?
|
||||||
|
function client_for(bufnr)
|
||||||
|
local clients = vim.lsp.get_clients { name = 'tinymist', bufnr = bufnr or 0 }
|
||||||
|
return clients[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
local helpers = { _clean_buffer_counter = 1 }
|
||||||
|
|
||||||
|
---Feed some keystrokes into the current buffer, replacing termcodes.
|
||||||
|
function helpers.feed(contents, feed_opts)
|
||||||
|
feed_opts = feed_opts or 'mtx'
|
||||||
|
local to_feed = vim.api.nvim_replace_termcodes(contents, true, false, true)
|
||||||
|
vim.api.nvim_feedkeys(to_feed, feed_opts, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Insert some text into the current buffer.
|
||||||
|
function helpers.insert(contents, feed_opts)
|
||||||
|
feed_opts = feed_opts or 'x'
|
||||||
|
helpers.feed('i' .. contents, feed_opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
function helpers.all_lean_extmarks(buffer, start, end_)
|
||||||
|
local extmarks = {}
|
||||||
|
for namespace, ns_id in pairs(vim.api.nvim_get_namespaces()) do
|
||||||
|
if namespace:match '^lean.' then
|
||||||
|
vim.list_extend(
|
||||||
|
extmarks,
|
||||||
|
vim.api.nvim_buf_get_extmarks(buffer, ns_id, start, end_, { details = true })
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return extmarks
|
||||||
|
end
|
||||||
|
|
||||||
|
---Move the cursor to a new location.
|
||||||
|
---
|
||||||
|
---Ideally this function wouldn't exist, and one would call `set_cursor`
|
||||||
|
---directly, but it does not fire `CursorMoved` autocmds.
|
||||||
|
---This function exists therefore to make tests which have slightly
|
||||||
|
---less implementation details in them (the manual firing of that autocmd).
|
||||||
|
---
|
||||||
|
---@param opts MoveCursorOpts
|
||||||
|
function helpers.move_cursor(opts)
|
||||||
|
local window = opts.window or Window:current()
|
||||||
|
window:move_cursor(opts.to)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Search forward in the buffer for the given text.
|
||||||
|
---
|
||||||
|
---Fires `CursorMoved` if the cursor moves and fails if it does not.
|
||||||
|
function helpers.search(string)
|
||||||
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||||
|
vim.fn.search(string)
|
||||||
|
assert.are_not.same(cursor, vim.api.nvim_win_get_cursor(0), 'Cursor did not move!')
|
||||||
|
vim.api.nvim_exec_autocmds('CursorMoved', {})
|
||||||
|
end
|
||||||
|
|
||||||
|
function helpers.wait_for_ready_lsp()
|
||||||
|
local succeeded, _ = vim.wait(15000, function()
|
||||||
|
local client = client_for(0)
|
||||||
|
return client and client.initialized or false
|
||||||
|
end)
|
||||||
|
assert.message('LSP server was never ready.').True(succeeded)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Wait until a window that isn't one of the known ones shows up.
|
||||||
|
---@param known table
|
||||||
|
function helpers.wait_for_new_window(known)
|
||||||
|
local ids = vim
|
||||||
|
.iter(known)
|
||||||
|
:map(function(window)
|
||||||
|
return window.id
|
||||||
|
end)
|
||||||
|
:totable()
|
||||||
|
|
||||||
|
local new_window
|
||||||
|
local succeeded = vim.wait(1000, function()
|
||||||
|
new_window = vim.iter(vim.api.nvim_tabpage_list_wins(0)):find(function(window)
|
||||||
|
return not vim.tbl_contains(ids, window)
|
||||||
|
end)
|
||||||
|
return new_window
|
||||||
|
end)
|
||||||
|
assert.message('Never found a new window').is_true(succeeded)
|
||||||
|
return Window:from_id(new_window)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Even though we can delete a buffer, so should be able to reuse names,
|
||||||
|
-- we do this to ensure if a test fails, future ones still get new "files".
|
||||||
|
local function set_unique_name_so_we_always_have_a_separate_fake_file(bufnr)
|
||||||
|
local counter = helpers._clean_buffer_counter
|
||||||
|
helpers._clean_buffer_counter = helpers._clean_buffer_counter + 1
|
||||||
|
local unique_name = fixtures.project.child(('unittest-%d.typ'):format(counter))
|
||||||
|
vim.api.nvim_buf_set_name(bufnr, unique_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Create a clean Lean buffer with the given contents.
|
||||||
|
---
|
||||||
|
---Waits for the LSP to be ready before proceeding with a given callback.
|
||||||
|
--
|
||||||
|
---Yes c(lean) may be a double entendre, and no I don't feel bad.
|
||||||
|
function helpers.clean_buffer(contents, callback)
|
||||||
|
local lines
|
||||||
|
|
||||||
|
-- Support a 1-arg version where we assume the contents is an empty buffer.
|
||||||
|
if callback == nil then
|
||||||
|
callback = contents
|
||||||
|
lines = {}
|
||||||
|
else
|
||||||
|
lines = vim.split(text.dedent(contents:gsub('^\n', '')):gsub('\n$', ''), '\n')
|
||||||
|
end
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local bufnr = vim.api.nvim_create_buf(false, false)
|
||||||
|
set_unique_name_so_we_always_have_a_separate_fake_file(bufnr)
|
||||||
|
|
||||||
|
vim.bo[bufnr].swapfile = false
|
||||||
|
vim.bo[bufnr].filetype = 'typst'
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||||
|
|
||||||
|
-- isn't it fun how fragile the order of the below lines is, and how
|
||||||
|
-- BufWinEnter seems automatically called by `nvim_set_current_buf`, but
|
||||||
|
-- `BufEnter` seems not automatically called by `nvim_buf_call` so we
|
||||||
|
-- manually trigger it?
|
||||||
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
|
vim.api.nvim_exec_autocmds('BufEnter', { buffer = bufnr })
|
||||||
|
vim.api.nvim_buf_call(bufnr, callback)
|
||||||
|
|
||||||
|
-- FIXME: Deleting buffers seems good for keeping our tests clean, but is
|
||||||
|
-- broken on 0.11 with impossible to diagnose invalid buffer errors.
|
||||||
|
-- vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Wait a few seconds for line diagnostics, erroring if none arrive.
|
||||||
|
function helpers.wait_for_line_diagnostics()
|
||||||
|
local params = vim.lsp.util.make_position_params(0, 'utf-16')
|
||||||
|
local succeeded, _ = vim.wait(15000, function()
|
||||||
|
if progress.at(params) == progress.Kind.processing then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local diagnostics = util.lean_lsp_diagnostics { lnum = params.position.line }
|
||||||
|
return #diagnostics > 0
|
||||||
|
end)
|
||||||
|
assert.message('Waited for line diagnostics but none came.').True(succeeded)
|
||||||
|
end
|
||||||
|
|
||||||
|
function helpers.wait_for_filetype()
|
||||||
|
local result, _ = vim.wait(15000, function()
|
||||||
|
return vim.bo.filetype == 'typst'
|
||||||
|
end)
|
||||||
|
assert.message('filetype was never set').is_truthy(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Assert about the current word.
|
||||||
|
local function has_current_word(_, arguments)
|
||||||
|
assert.is.equal(arguments[1], vim.fn.expand '<cword>')
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
assert:register('assertion', 'current_word', has_current_word)
|
||||||
|
|
||||||
|
---Assert about the current line.
|
||||||
|
local function has_current_line(_, arguments)
|
||||||
|
assert.is.equal(arguments[1], vim.api.nvim_get_current_line())
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
assert:register('assertion', 'current_line', has_current_line)
|
||||||
|
|
||||||
|
---Assert about the current cursor location.
|
||||||
|
local function has_current_cursor(_, arguments)
|
||||||
|
local window = arguments[1].window
|
||||||
|
if not window then
|
||||||
|
window = Window:current()
|
||||||
|
elseif type(window) == 'number' then
|
||||||
|
window = Window:from_id(window)
|
||||||
|
end
|
||||||
|
local got = window:cursor()
|
||||||
|
|
||||||
|
local column = arguments[1][2] or arguments[1].column or 0
|
||||||
|
local expected = { arguments[1][1] or got[1], column }
|
||||||
|
|
||||||
|
assert.are.same(expected, got)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
assert:register('assertion', 'current_cursor', has_current_cursor)
|
||||||
|
|
||||||
|
---Assert about the current tabpage.
|
||||||
|
local function has_current_tabpage(_, arguments)
|
||||||
|
assert.are.same(Tab:current(), arguments[1])
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
assert:register('assertion', 'current_tabpage', has_current_tabpage)
|
||||||
|
|
||||||
|
---Assert about the current window.
|
||||||
|
local function has_current_window(_, arguments)
|
||||||
|
assert.are.same(Window:current(), arguments[1])
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
assert:register('assertion', 'current_window', has_current_window)
|
||||||
|
|
||||||
|
local function _expected(arguments)
|
||||||
|
local expected = arguments[1][1] or arguments[1]
|
||||||
|
-- Handle cases where we're indeed checking for a real trailing newline.
|
||||||
|
local dedented = text.dedent(expected)
|
||||||
|
if dedented ~= expected then
|
||||||
|
expected = dedented:gsub('\n$', '')
|
||||||
|
end
|
||||||
|
return expected
|
||||||
|
end
|
||||||
|
|
||||||
|
---Assert about the entire buffer contents.
|
||||||
|
local function has_buf_contents(_, arguments)
|
||||||
|
local bufnr = arguments[1].bufnr or 0
|
||||||
|
local got = table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), '\n')
|
||||||
|
assert.is.equal(_expected(arguments), got)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
assert:register('assertion', 'contents', has_buf_contents)
|
||||||
|
|
||||||
|
assert:register('assertion', 'diff_contents', has_diff_contents)
|
||||||
|
|
||||||
|
local function has_highlighted_text(_, arguments)
|
||||||
|
local inspected = vim.inspect_pos(0)
|
||||||
|
local highlight = vim.iter(inspected.extmarks):find(function(mark)
|
||||||
|
return mark.opts.hl_group == 'widgetElementHighlight'
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert.is_not_nil(highlight, ('No highlighted text found in %s'):format(vim.inspect(inspected)))
|
||||||
|
|
||||||
|
local got = vim.api.nvim_buf_get_text(
|
||||||
|
0,
|
||||||
|
highlight.row,
|
||||||
|
highlight.col,
|
||||||
|
highlight.end_row,
|
||||||
|
highlight.end_col,
|
||||||
|
{}
|
||||||
|
)[1]
|
||||||
|
assert.are.same(arguments[1], got)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
assert:register('assertion', 'highlighted_text', has_highlighted_text)
|
||||||
|
|
||||||
|
local function has_all(_, arguments)
|
||||||
|
local contents = arguments[1]
|
||||||
|
|
||||||
|
if type(contents) == 'table' then
|
||||||
|
contents = table.concat(contents, '\n')
|
||||||
|
end
|
||||||
|
local expected = arguments[2]
|
||||||
|
for _, string in pairs(expected) do
|
||||||
|
assert.has_match(string, contents, nil, true)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
assert:register('assertion', 'has_all', has_all)
|
||||||
|
|
||||||
|
---Assert a tabpage has the given windows open in it.
|
||||||
|
local function has_open_windows(_, arguments)
|
||||||
|
local expected
|
||||||
|
if arguments.n == 1 and type(arguments[1]) == 'table' then
|
||||||
|
expected = arguments[1]
|
||||||
|
expected.n = #expected
|
||||||
|
else
|
||||||
|
expected = arguments
|
||||||
|
end
|
||||||
|
local got = vim.api.nvim_tabpage_list_wins(0)
|
||||||
|
got.n = #got
|
||||||
|
table.sort(expected)
|
||||||
|
table.sort(got)
|
||||||
|
assert.are.same(expected, got)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
assert:register('assertion', 'windows', has_open_windows)
|
||||||
|
|
||||||
|
return helpers
|
30
editors/neovim/spec/lsp_spec.lua
Normal file
30
editors/neovim/spec/lsp_spec.lua
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
---@brief [[
|
||||||
|
--- Tests for basic (auto-)attaching of LSP clients.
|
||||||
|
---@brief ]]
|
||||||
|
|
||||||
|
local fixtures = require 'spec.fixtures'
|
||||||
|
local helpers = require 'spec.helpers'
|
||||||
|
|
||||||
|
require('tinymist').setup {}
|
||||||
|
|
||||||
|
describe('LSP', function()
|
||||||
|
assert.is.empty(vim.lsp.get_clients { bufnr = 0, name = 'tinymist', _uninitialized = true })
|
||||||
|
|
||||||
|
it('is attached to .typ files within projects', function()
|
||||||
|
vim.cmd.edit(fixtures.project.some_existing_file)
|
||||||
|
assert.is.same(1, #vim.lsp.get_clients { bufnr = 0, name = 'tinymist', _uninitialized = true })
|
||||||
|
end)
|
||||||
|
|
||||||
|
it(
|
||||||
|
'is attached to single .typ files',
|
||||||
|
helpers.clean_buffer(function()
|
||||||
|
assert.is.same(1, #vim.lsp.get_clients { bufnr = 0, name = 'tinymist', _uninitialized = true })
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
|
||||||
|
it('is not attached to non-typst files', function()
|
||||||
|
vim.cmd.split 'some_non_typst_file.tmp'
|
||||||
|
assert.is.empty(vim.lsp.get_clients { bufnr = 0, name = 'tinymist', _uninitialized = true })
|
||||||
|
vim.cmd.close { bang = true }
|
||||||
|
end)
|
||||||
|
end)
|
64
editors/neovim/spec/main.py
Normal file
64
editors/neovim/spec/main.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# -- packpath := justfile_directory() / "packpath"
|
||||||
|
# -- scripts := justfile_directory() / "scripts"
|
||||||
|
# -- doc := justfile_directory() / "doc"
|
||||||
|
# -- src := justfile_directory() / "lua"
|
||||||
|
# -- lean := src / "lean"
|
||||||
|
# -- spec := justfile_directory() / "spec"
|
||||||
|
# -- fixtures := spec / "fixtures"
|
||||||
|
# -- demos := justfile_directory() / "demos"
|
||||||
|
|
||||||
|
# -- init_lua := scripts / "minimal_init.lua"
|
||||||
|
# -- clean_config := justfile_directory() / ".test-config"
|
||||||
|
|
||||||
|
# -- # Rebuild some test fixtures used in the test suite.
|
||||||
|
# -- _rebuild-test-fixtures:
|
||||||
|
# -- cd "{{ fixtures }}/example-project/"; lake build && lake build ProofWidgets Mathlib.Tactic.Widget.Conv
|
||||||
|
|
||||||
|
# -- # Run the lean.nvim test suite.
|
||||||
|
# -- [group('testing')]
|
||||||
|
# -- test: _rebuild-test-fixtures _clone-test-dependencies
|
||||||
|
# -- @just retest
|
||||||
|
|
||||||
|
# -- # Run the test suite without rebuilding or recloning any dependencies.
|
||||||
|
# -- [group('testing')]
|
||||||
|
# -- retest *test_files=spec:
|
||||||
|
# -- nvim --headless --clean -u {{ init_lua }} -c 'lua require("inanis").run{ specs = vim.split("{{ test_files }}", " "), minimal_init = "{{ init_lua }}", sequential = vim.env.TEST_SEQUENTIAL ~= nil }'
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def run_tests(test_files=None):
|
||||||
|
"""
|
||||||
|
Run the Neovim test suite with the specified test files.
|
||||||
|
If no test files are specified, it runs all tests in the 'spec' directory.
|
||||||
|
"""
|
||||||
|
init_lua = os.path.realpath(os.path.join(__file__, '../../scripts/minimal_init.lua'))
|
||||||
|
|
||||||
|
if test_files is None:
|
||||||
|
# all test files in the 'spec' directory
|
||||||
|
test_files = []
|
||||||
|
for root, _, files in os.walk(os.path.dirname(__file__)):
|
||||||
|
test_files.extend( os.path.join(root, f) for f in files if f.endswith('_spec.lua') )
|
||||||
|
test_files = ' '.join(test_files)
|
||||||
|
|
||||||
|
command = [
|
||||||
|
'nvim',
|
||||||
|
'--headless',
|
||||||
|
'--clean',
|
||||||
|
'-u', init_lua,
|
||||||
|
'-c', f'lua require("inanis").run{{ specs = vim.split("{test_files}", " "), minimal_init = "{init_lua}", sequential = vim.env.TEST_SEQUENTIAL ~= nil }}'
|
||||||
|
]
|
||||||
|
|
||||||
|
subprocess.run(command, check=True)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Check if any test files are provided as command line arguments
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
test_files = ' '.join(sys.argv[1:])
|
||||||
|
else:
|
||||||
|
test_files = None
|
||||||
|
|
||||||
|
run_tests(test_files)
|
||||||
|
|
41
lazyvim/Dockerfile
Normal file
41
lazyvim/Dockerfile
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
FROM ubuntu:24.04 AS builder
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
file \
|
||||||
|
ninja-build gettext cmake unzip curl build-essential
|
||||||
|
|
||||||
|
RUN git clone --filter=blob:none --branch stable https://github.com/neovim/neovim && cd neovim && make CMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
|
RUN cd neovim/build && cpack -G DEB && dpkg -i nvim-linux64.deb
|
||||||
|
|
||||||
|
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
COPY --from=builder /neovim/build/nvim-linux64.deb /tmp/nvim-linux64.deb
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
/tmp/nvim-linux64.deb \
|
||||||
|
curl \
|
||||||
|
gcc \
|
||||||
|
git \
|
||||||
|
make \
|
||||||
|
ripgrep \
|
||||||
|
zsh \
|
||||||
|
&& rm /tmp/nvim-linux64.deb
|
||||||
|
|
||||||
|
RUN useradd --create-home --shell /bin/zsh lean
|
||||||
|
USER lean
|
||||||
|
WORKDIR /home/lean
|
||||||
|
|
||||||
|
RUN curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain none
|
||||||
|
|
||||||
|
ENV PATH="/home/lean/.elan/bin:${PATH}"
|
||||||
|
|
||||||
|
RUN git clone --filter=blob:none https://github.com/LazyVim/starter ~/.config/nvim
|
||||||
|
COPY lean.lua .config/nvim/lua/plugins/lean.lua
|
||||||
|
|
||||||
|
ARG LEAN_PROJECT=https://github.com/leanprover-community/mathlib4
|
||||||
|
|
||||||
|
RUN git clone --filter=blob:none $LEAN_PROJECT && cd $(basename "$LEAN_PROJECT") && lake exe cache get && elan default "$(cat lean-toolchain || echo stable)"
|
||||||
|
|
||||||
|
# SHELL isn't supported by OCI images
|
||||||
|
CMD ["zsh", "-l"]
|
5
lazyvim/devcontainer.json
Normal file
5
lazyvim/devcontainer.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "lazylean.nvim",
|
||||||
|
"build": { "dockerfile": "Dockerfile" },
|
||||||
|
"onCreateCommand": "git clone --filter=blob:none https://github.com/leanprover-community/Mathlib4.git && cd Mathlib4 && lake exe cache get! && nvim +quit"
|
||||||
|
}
|
18
lazyvim/lean.lua
Normal file
18
lazyvim/lean.lua
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
'Julian/lean.nvim',
|
||||||
|
dependencies = {
|
||||||
|
'nvim-lua/plenary.nvim',
|
||||||
|
'neovim/nvim-lspconfig',
|
||||||
|
},
|
||||||
|
---@module 'lean'
|
||||||
|
---@type lean.Config
|
||||||
|
opts = {
|
||||||
|
infoview = {
|
||||||
|
horizontal_position = 'top',
|
||||||
|
show_processing = false,
|
||||||
|
},
|
||||||
|
mappings = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
"build:editor-tools": "cd tools/editor-tools/ && yarn run build",
|
"build:editor-tools": "cd tools/editor-tools/ && yarn run build",
|
||||||
"build:preview": "cd tools/typst-preview-frontend && yarn run build && rimraf ../../crates/tinymist-assets/src/typst-preview.html && cpr ./dist/index.html ../../crates/tinymist-assets/src/typst-preview.html",
|
"build:preview": "cd tools/typst-preview-frontend && yarn run build && rimraf ../../crates/tinymist-assets/src/typst-preview.html && cpr ./dist/index.html ../../crates/tinymist-assets/src/typst-preview.html",
|
||||||
"build:l10n": "yarn extract:l10n && node scripts/build-l10n.mjs",
|
"build:l10n": "yarn extract:l10n && node scripts/build-l10n.mjs",
|
||||||
|
"build:docker": "docker build -t myriaddreamin/tinymist:0.13.14 .",
|
||||||
"extract:l10n": "yarn extract:l10n:ts && yarn extract:l10n:rs",
|
"extract:l10n": "yarn extract:l10n:ts && yarn extract:l10n:rs",
|
||||||
"extract:l10n:ts": "cargo run --release --bin tinymist-l10n -- --kind ts --dir ./editors/vscode --output ./locales/tinymist-vscode-rt.toml",
|
"extract:l10n:ts": "cargo run --release --bin tinymist-l10n -- --kind ts --dir ./editors/vscode --output ./locales/tinymist-vscode-rt.toml",
|
||||||
"extract:l10n:rs": "cargo run --release --bin tinymist-l10n -- --kind rs --dir ./crates --output ./locales/tinymist-rt.toml && rimraf ./crates/tinymist-assets/src/tinymist-rt.toml && cpr ./locales/tinymist-rt.toml ./crates/tinymist-assets/src/tinymist-rt.toml",
|
"extract:l10n:rs": "cargo run --release --bin tinymist-l10n -- --kind rs --dir ./crates --output ./locales/tinymist-rt.toml && rimraf ./crates/tinymist-assets/src/tinymist-rt.toml && cpr ./locales/tinymist-rt.toml ./crates/tinymist-assets/src/tinymist-rt.toml",
|
||||||
|
|
4
tests/workspaces/book/chapters/chapter1.typ
Normal file
4
tests/workspaces/book/chapters/chapter1.typ
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
= Chapter 1
|
||||||
|
|
||||||
|
#lorem(50)
|
4
tests/workspaces/book/chapters/chapter2.typ
Normal file
4
tests/workspaces/book/chapters/chapter2.typ
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
= Chapter 2
|
||||||
|
|
||||||
|
#lorem(50)
|
4
tests/workspaces/book/main.typ
Normal file
4
tests/workspaces/book/main.typ
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#include "chapters/chapter1.typ"
|
||||||
|
#include "chapters/chapter2.typ"
|
||||||
|
|
||||||
|
|
5
tests/workspaces/individuals/tiny.typ
Normal file
5
tests/workspaces/individuals/tiny.typ
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
= Hello
|
||||||
|
|
||||||
|
#lorem(50)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue