Code Monkey home page Code Monkey logo

lean.nvim's Introduction

lean.nvim

neovim support for the Lean Theorem Prover.

demo.mp4

Prerequisites

lean.nvim supports the latest stable neovim release (currently 0.9.x) as well as the latest nightly.

(This matches what neovim itself supports upstream, which often guides what plugins end up working with).

Installation

Install via your favorite plugin manager.

For example with lazy.nvim:

{
  'Julian/lean.nvim',
  event = { 'BufReadPre *.lean', 'BufNewFile *.lean' },

  dependencies = {
    'neovim/nvim-lspconfig',
    'nvim-lua/plenary.nvim',
    -- you also will likely want nvim-cmp or some completion engine
  },

  -- see details below for full configuration options
  opts = {
    lsp = {
      on_attach = on_attach,
    },
    mappings = true,
  }
}

or with vim-plug:

Plug 'Julian/lean.nvim'
Plug 'neovim/nvim-lspconfig'
Plug 'nvim-lua/plenary.nvim'

" Optional Dependencies:

Plug 'hrsh7th/nvim-cmp'        " For LSP completion
Plug 'hrsh7th/cmp-nvim-lsp'
Plug 'hrsh7th/cmp-buffer'
Plug 'hrsh7th/vim-vsnip'       " For snippets
Plug 'andrewradev/switch.vim'  " For Lean switch support
Plug 'tomtom/tcomment_vim'     " For commenting motions
Plug 'nvim-telescope/telescope.nvim' " For Loogle search

lean.nvim already includes syntax highlighting and Lean filetype support, so installing the lean.vim (i.e. non-neovim) plugin is not required or recommended.

Features

  • Abbreviation (unicode character) insertion (in insert mode & the command window accessible via q/)
  • An infoview which can show persistent goal, term & tactic state, as well as interactive widget support (which should function for most widgets renderable as text)
  • Hover (preview) commands:
    • :LeanGoal for showing goal state in a preview window
    • :LeanTermGoal for showing term-mode type information in a preview window
  • switch.vim base definitions for Lean
  • Simple snippets (in VSCode-compatible format, usable with e.g. vim-vsnip)
  • Lean library search path access via lean.current_search_path(), suitable for use with e.g. telescope.nvim for live grepping. See the wiki for a sample configuration.
  • If telescope.nvim is present a :Telescope loogle command is available as a frontend for the Loogle JSON API.

Configuration & Usage

The short version -- if you followed the instructions above for lazy.nvim, simply ensure your opts table contains at least an lsp table defining on_attach to be your preferred LSP attach handler, and mappings = true to enable key mappings, as shown in the example above.

Note that in particular your on_attach handler should likely bind things like vim.lsp.buf.code_action (AKA "the lightbulb") to ensure that you have easy access to code actions in Lean buffers. In particular, Lean (or really Std) uses code actions for replacing "Try this:" suggestions, which you will almost certainly want to be able to perform. If you do not already have a preferred setup which includes LSP key mappings and (auto)completion, you may find the fuller example here in the wiki helpful.

If you are using another plugin manager such as vim-plug, after following the installation instructions, add the below to ~/.config/nvim/plugin/lean.lua or an equivalent:

require('lean').setup{
  lsp = { on_attach = on_attach },
  mappings = true,
}

More detail on the full list of supported configuration options can be found below.

(If you find you can't modify your source files due to the nvim E21 error, this might be due to lean.nvim's effort prevent users from accidentally shooting themselves in the foot by modifying the Lean standard library. See the definition of nomodifiable below.)

Semantic Highlighting

Lean 4 supports semantic highlighting, in which the Lean server itself will signal how to highlight terms and symbols within the editor using information available to it.

Note that even though neovim supports this highlighting, you still will want to map the semantic highlighting groups to your color scheme appropriately. For a sample setup, see the wiki.

Mappings

If you've set mappings = true in your configuration (or have called lean.use_suggested_mappings() explicitly), a number of keys will be mapped either within Lean source files or within Infoview windows:

In Lean Files

The key binding <LocalLeader> below refers to a configurable prefix key within neovim. You can check what this key is set to within neovim by running the command :echo maplocalleader. An error like E121: Undefined variable: maplocalleader indicates that it may not be set to any key. This can be configured by putting a line at the top of your ~/.config/nvim/init.lua of the form vim.g.maplocalleader = ' ' (in this example, mapping <LocalLeader> to hitting the space key twice).

Key Function
<LocalLeader>i toggle the infoview open or closed
<LocalLeader>p pause the current infoview
<LocalLeader>x place an infoview pin
<LocalLeader>c clear all current infoview pins
<LocalLeader>dx place an infoview diff pin
<LocalLeader>dc clear current infoview diff pin
<LocalLeader>dd toggle auto diff pin mode
<LocalLeader>dt toggle auto diff pin mode without clearing diff pin
<LocalLeader>s insert a sorry for each open goal
<LocalLeader><Tab> jump into the infoview window associated with the current lean file
<LocalLeader>\\ show what abbreviation produces the symbol under the cursor

Note

See :help <LocalLeader> if you haven't previously interacted with the local leader key. Some nvim users remap this key to make it easier to reach, so you may want to consider what key that means for your own keyboard layout. My (Julian's) <Leader> is set to <Space>, and my <LocalLeader> to <Space><Space>, which may be a good choice for you if you have no other preference.

In Infoview Windows

Key Function
<CR> click a widget or interactive area of the infoview
K click a widget or interactive area of the infoview
<Tab> jump into a tooltip (from a widget click)
<Shift-Tab> jump out of a tooltip and back to its parent
<Esc> clear all open tooltips
J jump into a tooltip (from a widget click)
C clear all open tooltips
I mouse-enter what is under the cursor
i mouse-leave what is under the cursor
gd go-to-definition of what is under the cursor
gD go-to-declaration of what is under the cursor
gy go-to-type of what is under the cursor
<LocalLeader><Tab> jump to the lean file associated with the current infoview window

Lean 3

Support for the (end of life-d) Lean 3 is also available. In addition to the instructions above, and in addition to installing Lean 3 itself, you will need to install the separate Lean 3 lean-language-server, which can be done via e.g.:

$ npm install -g lean-language-server

Full Configuration & Settings Information

require('lean').setup{
  -- Enable the Lean language server(s)?
  --
  -- false to disable, otherwise should be a table of options to pass to `leanls`
  -- See https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#leanls for details.
  lsp = {
    on_attach = on_attach,
    init_options = {
      -- See Lean.Lsp.InitializationOptions for details and further options.

      -- Time (in milliseconds) which must pass since latest edit until elaboration begins.
      -- Lower values may make editing feel faster at the cost of higher CPU usage.
      -- Note that lean.nvim changes the Lean default for this value!
      editDelay = 0,

      -- Whether to signal that widgets are supported.
      -- Enabled by default, as support for most widgets is implemented in lean.nvim.
      hasWidgets = true,
    }
  },

  ft = {
    -- A list of patterns which will be used to protect any matching
    -- Lean file paths from being accidentally modified (by marking the
    -- buffer as `nomodifiable`).
    nomodifiable = {
        -- by default, this list includes the Lean standard libraries,
        -- as well as files within dependency directories (e.g. `_target`)
        -- Set this to an empty table to disable.
    }
  },

  -- Abbreviation support
  abbreviations = {
    -- Enable expanding of unicode abbreviations?
    enable = true,
    -- additional abbreviations:
    extra = {
      -- Add a \wknight abbreviation to insert ♘
      --
      -- Note that the backslash is implied, and that you of
      -- course may also use a snippet engine directly to do
      -- this if so desired.
      wknight = '',
    },
    -- Change if you don't like the backslash
    -- (comma is a popular choice on French keyboards)
    leader = '\\',
  },

  -- Enable suggested mappings?
  --
  -- false by default, true to enable
  mappings = false,

  -- Infoview support
  infoview = {
    -- Automatically open an infoview on entering a Lean buffer?
    -- Should be a function that will be called anytime a new Lean file
    -- is opened. Return true to open an infoview, otherwise false.
    -- Setting this to `true` is the same as `function() return true end`,
    -- i.e. autoopen for any Lean file, or setting it to `false` is the
    -- same as `function() return false end`, i.e. never autoopen.
    autoopen = true,

    -- Set infoview windows' starting dimensions.
    -- Windows are opened horizontally or vertically depending on spacing.
    width = 50,
    height = 20,

    -- Put the infoview on the top or bottom when horizontal?
    -- top | bottom
    horizontal_position = "bottom",

    -- Always open the infoview window in a separate tabpage.
    -- Might be useful if you are using a screen reader and don't want too
    -- many dynamic updates in the terminal at the same time.
    -- Note that `height` and `width` will be ignored in this case.
    separate_tab = false,

    -- Show indicators for pin locations when entering an infoview window?
    -- always | never | auto (= only when there are multiple pins)
    indicators = "auto",
  },

  -- Progress bar support
  progress_bars = {
    -- Enable the progress bars?
    enable = true,
    -- What character should be used for the bars?
    character = '',
    -- Use a different priority for the signs
    priority = 10,
  },

  -- Redirect Lean's stderr messages somehwere (to a buffer by default)
  stderr = {
    enable = true,
    -- height of the window
    height = 5,
    -- a callback which will be called with (multi-line) stderr output
    -- e.g., use:
    --   on_lines = function(lines) vim.notify(lines) end
    -- if you want to redirect stderr to `vim.notify`.
    -- The default implementation will redirect to a dedicated stderr
    -- window.
    on_lines = nil,
  },

  -- Legacy Lean 3 support (on_attach is as above, your LSP handler)
  lsp3 = { on_attach = on_attach },

  -- mouse_events = true will simulate mouse events in the Lean 3 infoview, this is buggy at the moment
  -- so you can use the I/i keybindings to manually trigger these
  lean3 = { mouse_events = false },
}

Other Plugins

Particularly if you're also a VSCode user, there may be other plugins you're interested in. Below is a (hopelessly incomplete) list of a few:

Contributing

Contributions are most welcome. Feel free to send pull requests for anything you'd like to see, or open an issue if you'd like to discuss.

Running the tests can be done via just using the adjacent justfile:

$ just

which will execute against a minimal init.lua isolated from your own setup.

After running the test suite once, you can save some time re-cloning dependencies by instead now running:

$ just retest

You can also run single test files by running:

$ just retest lua/tests/ft_spec.lua

Some linting and style checking is done via pre-commit, which once installed (via the linked instructions) is run via:

$ just lint

or on each commit automatically if you have run pre-commit install in your repository checkout.

You can also use

$ just nvim '{ lsp = { enable = true }, mappings = true }'

to get a normal running neovim (again isolated from your own configuration), where the provided argument is a (Lua) table like one would pass to lean.setup. Any further arguments will be passed to nvim.

lean.nvim's People

Contributors

4e554c4c avatar alexdikelsky avatar clslaid avatar crvdgc avatar dependabot[bot] avatar gebner avatar gihanmarasingha avatar hargonix avatar jcommelin avatar julian avatar kovach avatar langfield avatar pre-commit-ci[bot] avatar ram02z avatar rish987 avatar williamboman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lean.nvim's Issues

Support infoview auto-close

Currently, :quitting the last Lean buffer in a tab leaves the infoview dangling in a detached state. Instead, it should automatically close the associated infoview, and :close should leave it in a detached state.

This will likely involve including the set of associated windows as part of the Infoview object, adding/removing these windows on Lean file BufEnter/BufExit, and once the last such buffer is exited, this will trigger the infoview to close as well. We should also make it (dynamically) configurable per-infoview whether or not this autoclose happens, in case someone doesn't like this -- that is, some kind of pin function that keeps the infoview around even when no Lean buffers are open.

Snippet test

Just the basics -- enter \a, assert conversion to alpha.

Infoview should scroll up automatically

When you scroll down the infoview, it remembers the position. When the infoview then updates and the new content is shorter than the old one, you have an empty infoview. In that case it scroll back up again so that you at least see that it's not empty.

Occasional errors on startup

Similar to #26 in that I don't yet see how to deterministically reproduce, I occasionally get:

Error detected while processing CursorHold Autocommands for "*.lean":                                                                                                                                                                                                                       
E5108: Error executing lua /Users/julian/Development/lean.nvim/lua/lean/infoview.lua:37: attempt to index local 'result' (a number value)                                                                                                                                                   

when opening a lean file. Here it's rare, and only happens occasionally.

I don't know how result is getting to be a number there. Perhaps just ignoring it when it is will silence the error, but it'd be nice to know why it's happening.

Improve how sub-functionality is configured

  • Provide a unified API for (re)-configuring sub-functionality, rather than ad hoc functions like infoview.set_autoopen. Inspiration probably can be taken from vim.diagnostic.config (i.e. we probably should use a similar API).
  • Unknown options provided to a config table should warn or error. Right now an unknown option is silently ignored, which allows typos (or mistakes due to backward incompatibility).

Implement goal state diff-ing

Given a sequence of tactics, load the goal state text at the end of each tactic into the quickfix list and allow a sequential (text) vimdiff of the state from one to the next.

compe abbreviations do not remove the inserted slash automatically

When using the abbreviation support in compe, writing \mu and completing the mu will insert the mu but leave the slash in place, resulting in in the buffer.

I cannot tell if this is a compe bug (e.g. that it is calculating ranges to remove on word boundaries or something) or me writing the compe code we have wrongly. Defining a .confirm() function seems like a possible way to evade even the possible bug.

(Filing this as another "known issue", though I have not been able to figure it out... :/)

compe also isn't tested in CI, which it should be alongside the snippets.nvim tests.

Infoview doesn't render until second update

When first opening a file, the infoview doesn't display the first state of the file (until a second update e.g. by moving the cursor).

Most likely the CursorHold triggers before the server is ready.

Demo below (for Lean 3):

asciicast

Allow for default infoview closed.

Someone may want to have infoviews be closed by default, and selectively open them for specific tabs. Currently, setting enable = false disables infoviews altogether. Instead, this should be a "soft" option that just disables auto-open, still allowing for manual open if desired.

Add (auto)indent support

In simple cases I think there are simple changes we can make.

E.g. probably we should set indentkeys+==where and := for Lean 4.

For more complex cases, if or when tree-sitter-lean is good enough to use, nvim-treesitter allows us to define indentation rules via it (via an indents.scm file).

Support pinnable infoview messages

Similar to VSCode, it should be possible to pin/unpin infoview messages.

VSCode seems to support "live" messages that are automatically updated as the surrounding code changes. To me this seems a bit unexpected, and I would prefer to just have pinned messages be permanently "paused" and only update when specifically requested. Not to mention this would make the code simpler. Obviously if someone wants to simulate live updates this could be done with autocmds. Though keep in mind I don't have as much experience writing Lean code myself as reading it, so do let me know if there are other opinions.

Enhance lean.trythis.swap to trim redundant term->tactic->term mode reentry

E.g. pass this test:

  -- Emitted by e.g. show_term
  it('replaces redundant term and tactic mode', helpers.clean_buffer([[
namespace tactic.interactive
meta def foo (t: itactic) : itactic :=
  (do tactic.trace "Try this: exact λ x y hxy, hf (hg hxy)\n")
end tactic.interactive

example {X Y Z : Type} {f : X → Y} {g : Y → Z} (hf : function.injective f) (hg : function.injective g) : function.injective (g ∘ f) :=
begin
  foo {
    intros x y hxy,
    apply hf,
    apply hg,
    apply hxy,
  }
end]], function()
    local expected = vim.api.nvim_buf_get_lines(0, 0, 5, true)

    vim.api.nvim_command('normal 8gg3|')
    helpers.wait_for_line_diagnostics()

    require('lean.trythis').swap()
    vim.list_extend(
      expected, {
        'example {X Y Z : Type} {f : X → Y} {g : Y → Z} (hf : function.injective f) (hg : function.injective g) : function.injective (g ∘ f) :=',
        'λ x y hxy, hf (hg hxy)',
      }
    )

    assert.contents.are(expected)
  end))

  -- Emitted by e.g. show_term
  it('replaces trythis messages before the position', helpers.clean_buffer([[
]], function()
    assert.is.same(2, 2)
  end))
end)

The relevant vscode-lean (3) heuristic is here: https://github.com/leanprover/vscode-lean/blob/8ad0609f560f279512ff792589f06d18aa92fb3f/src/tacticsuggestions.ts#L76

Add a helper for peeking into simps

Given an execution of the simp tactic, show a completion menu that peeks the definition of all the lemmas used by the simp (without needing to replace the text inside the buffer).

Make the builtin abbreviation expansion not insert a space on <tab>

The builtin abbreviation expansion inserts a trailing space when hitting both <Space> or <Tab>.

So foo x\1<Tab> expands to foo x₁ . This doesn't match my fingers' muscle memory for completion in general, which expect no space there, particularly if I'm inserting something in the middle of an existing line (e.g. in Lean 3 on a line like foo bar|, baz quux, where I'm going back to the middle of the line to insert something).

@gebner let me know if you prefer things this way (if so I suppose I can make it configurable)

More infoview tweaks

These are a few other minor possible tweaks for the infoview. Some probably should be defaults, some configurable:

  • have one infoview per :tab
  • #171
  • split the infoview horizontally if there are already two vertical splits on the tab when it is opened
  • #172

simp-normal form highlighting

Maybe it'd be nice (and reasonably easy?) to have a list of simp-normal forms and to highlight non-simp-normal terms as warnings.

Zulip and the docs have an existing list of these kinds of terms.

Figure out why underline/undercurl isn't working

Unlike in vscode, diagnostics aren't underlining where they apply to.

This is true even though underline is on by default for e.g. native LSP client in neovim, so somehow that information either isn't being passed from the vanilla LSP lean server (and is being done in the vscode extension outside of the LSP server) or isn't being processed within neovim... Will have to hunt down which.

Unloading a buffer stops the server process

Lean 4's LSP server treats the didOpen and didClose notifications specially: it spawns a server process for every opened document. That is, closing a document stops the corresponding server process, and reopening the document causes Lean to restart the server process and the file starts parsing again from the beginning.

For neovim, this has the effect that only open buffers have a server process. For example, go-to-definition typically closes the current buffer and opens another buffer for the file with the definition. Then the Lean server starts parsing the new file.
When you go back to the original file, the Lean server kills the server process for the file with the definition. And it starts parsing the original file again from the beginning.

We might therefore want to set hidden (or something) by default.

Full HTML widget support in a GUI

There are a number of neovim-based GUIs.

Some notable ones are:

uivonim
vimr
gnvim
vv

and likely more, given I don't personally stay too much on top of these. The first one is likely particularly interesting given it has contributors who are neovim core devs.

We currently support TUI-based widgets, but for more complex ones such as e.g. the sudoku solver which truly rely on HTML rendering, a GUI (with access to a real browser) is needed.

We could attempt to follow something like bracey's model even for terminal nvim by opening an optional browser window which updates not on save but on infoview update.

Lean 4: show `Loading...` message when language server still processing file

(probably wise to implement this after #84)

Like in VSCode, we should maintain a per-URI state describing the point in the file that the language server has reached, which is initialized to zero for each URI, updated on fileProgress server notification, and cleared once the file has been parsed (or some other indicator).

We can then short-circuit attempting to update the infoview if the current URI hasn't been fully processed yet, printing a Loading... message, and having the URI progress state trigger an update for all relevant messages on parse completion for that file.

Constant errors from LSP plugin

When editing a Lean 4 file, I am getting the following kind of error very frequently:

LSP[leanls]: Error NO_RESULT_CALLBACK_FOUND: {
  id = 101,
  jsonrpc = "2.0"
}

Sometimes the response is clearly from autocompletion. Is there something obvious that I should be doing?

Closing the infoview buffer causes errors

When I (accidentally) close the infoview buffer, the following error repeats very quickly:

Error executing vim.schedule lua callback: /home/gebner/lean.nvim/lua/lean/infoview.lua:61: Invalid buffer id: 3

It would also be nice to have commands to reopen or toggle the infoview.

Make goto definition and hovering and other LSP commands work in the infoview

In the infoview, it'd be nice if vim.lsp.buf.definition() (or hovering, etc.) on a term which is defined in the corresponding buffer functioned similarly to doing so in the original buffer -- i.e. jumps to the definition, etc.

Doing this for terms embedded in e.g. try this: messages would also be nice albeit require a bit more work.

Infoview and hover LSP support use different positions for term goals

In the infoview, the term goal support uses the current cursor position when requesting $/lean/plainTermGoal, whereas we shift forward by 1 column for the goal state, which is more ergonomic in vim.

However, in the hover support for :LeanPlainGoal and :LeanPlainTermGoal we shift forward for both.

I didn't check which behavior is correct, but given that Gabriel wrote the first one it's likely the correct one and I just didn't notice. Probably the code should be shared between the modules a bit better anyhow.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.