Skip to content

Commit

Permalink
feat!: Large rewrite with testing, hover and partial completions (#4)
Browse files Browse the repository at this point in the history
Co-authored-by: tris203 <[email protected]>
  • Loading branch information
neubaner and tris203 authored Oct 30, 2024
1 parent cbf3714 commit f78a9a3
Show file tree
Hide file tree
Showing 25 changed files with 1,065 additions and 201 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: Format

on:
push:
branches:
- "main"
pull_request:
branches:
- "main"

jobs:
format:
name: Stylua
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: date +%W > weekly

- name: Restore cache
id: cache
uses: actions/cache@v2
with:
path: |
~/.cargo/bin
key: ${{ runner.os }}-cargo-${{ hashFiles('weekly') }}

- name: Install
if: steps.cache.outputs.cache-hit != 'true'
run: cargo install stylua

- name: Format
run: stylua --check lua/ --config-path=.stylua.toml
25 changes: 25 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: Lint

on:
push:
branches:
- "main"
pull_request:
branches:
- "main"

jobs:
lint:
name: Luacheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup
run: |
sudo apt-get update
sudo apt-get install luarocks -y
sudo luarocks install luacheck
- name: Lint
run: luacheck lua/ --globals vim
25 changes: 25 additions & 0 deletions .github/workflows/m.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: Lint

on:
push:
branches:
- "main"
pull_request:
branches:
- "main"

jobs:
lint:
name: Luacheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup
run: |
sudo apt-get update
sudo apt-get install luarocks -y
sudo luarocks install luacheck
- name: Lint
run: luacheck lua/ --globals vim
32 changes: 32 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Test

on:
push:
branches:
- "main"
pull_request:
branches:
- "main"

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
nvim-versions: ['stable', 'nightly']
os: [ubuntu-latest, macos-latest]
# TODO:add windows tests back in when we implement path normalisation
# os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: false
name: Plenary Tests
steps:
- name: checkout
uses: actions/checkout@v4

- uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.nvim-versions }}

- name: run tests
run: make test
2 changes: 2 additions & 0 deletions .luacheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
max_comment_line_length = false
globals = { "vim", "describe", "it", "before_each", "after_each", "assert", "async" }
15 changes: 15 additions & 0 deletions .luarc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"runtime": {
"version": "LuaJIT",
"pathStrict": true
},
"type": {
"checkTableShape": true
},
"diagnostics.globals": [
"describe",
"it",
"before_each",
"after_each"
]
}
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
TESTS_INIT=tests/minimal.lua
TESTS_DIR=tests/

.PHONY: test

test:
@nvim \
--headless \
--noplugin \
-u ${TESTS_INIT} \
-c "PlenaryBustedDirectory ${TESTS_DIR} { minimal_init = '${TESTS_INIT}' }" \
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,35 @@ to bring this support to Neovim.

The LSP can be cloned and compiled from source from the `dotnet/razor` repo.

## Dependencies

You must install the following plugins:

* [nvim-neotest/nvim-nio](https://github.com/nvim-neotest/nvim-nio)
* [seblj/roslyn.nvim](https://github.com/seblj/roslyn.nvim)

The `html-lsp` is also required to provide completions. You can install it with
Mason and configure it via `nvim-lspconfig`.

## Integration

You can pass a configuration table to the `setup` function. The configuration options are:

- `on_attach`: A function that is called when the LSP client attaches to a buffer.
- `capabilities`: A table that defines the capabilities of the LSP client.
- `path`: The path to the rzls executable.
* `on_attach`: A function that is called when the LSP client attaches to a buffer.
* `capabilities`: A table that defines the capabilities of the LSP client.
* `path`: The path to the rzls executable.

You also must configure the [`roslyn.nvim`](https://github.com/seblj/roslyn.nvim) plugin
to communicate with the razor LSP. To do so, you must pass the handlers defined in the
`rzls.roslyn_handlers` module:

```lua
require('roslyn').setup {
config = {
handlers = require('rzls.roslyn_handlers')
}
}
```

## Under Construction

Expand All @@ -32,4 +54,4 @@ Issue. Your input is valuable in making this plugin more robust and efficient.
## Helping Out

If you want to help out, then please see the discussion here, and leave a
comment with your details in this [discussion](https://github.com/tris203/rzls.nvim/discussions/1).
comment matches your details in this [discussion](https://github.com/tris203/rzls.nvim/discussions/1).
169 changes: 169 additions & 0 deletions lua/rzls/documentstore.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
local razor = require("rzls.razor")
local utils = require("rzls.utils")
local VirtualDocument = require("rzls.virtual_document")

---@class rzls.ProjectedDocuments
---@field virtual_html rzls.ProjectedDocument
---@field virtual_csharp rzls.ProjectedDocument

---@class rzls.ProjectedDocument
---@field buf number
---@field hostDocumentVersion number
---@field content string

local M = {}

local virtual_suffixes = {
html = "__virtual.html",
csharp = "__virtual.cs",
}

---@type table<string, table<razor.LanguageKind, rzls.VirtualDocument>>
local virtual_documents = {}

local roslyn_ready = false

---@param name string
local function buffer_with_name(name)
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
local buf_name = vim.api.nvim_buf_get_name(buf)
if buf_name == name then
return buf
end
end

return -1
end

local function get_or_create_buffer_for_filepath(filepath, filetype)
local buf = buffer_with_name(filepath)
if buf == -1 then
vim.print(filepath)
buf = vim.api.nvim_create_buf(true, false)
vim.api.nvim_buf_set_name(buf, filepath)
vim.api.nvim_set_option_value("ft", filetype, { buf = buf })
end

return buf
end

---Registers virtual buffers for the given file path
---@param current_file string
function M.register_vbufs_by_path(current_file)
-- open virtual files
--
virtual_documents[current_file] = virtual_documents[current_file] or {}

if vim.tbl_isempty(virtual_documents[current_file]) then
virtual_documents[current_file] = VirtualDocument:new(current_file, razor.language_kinds.razor)
end

if virtual_documents[current_file][razor.language_kinds.csharp] == nil then
local buf = get_or_create_buffer_for_filepath(current_file .. virtual_suffixes.csharp, "cs")

virtual_documents[current_file][razor.language_kinds.csharp] =
VirtualDocument:new(buf, razor.language_kinds.csharp)
end

if virtual_documents[current_file][razor.language_kinds.html] == nil then
local buf = get_or_create_buffer_for_filepath(current_file .. virtual_suffixes.html, "html")

virtual_documents[current_file][razor.language_kinds.html] = VirtualDocument:new(buf, razor.language_kinds.html)
end
end

---@param result VBufUpdate
---@param language_kind razor.LanguageKind
function M.update_vbuf(result, language_kind)
M.register_vbufs_by_path(result.hostDocumentFilePath)
local virtual_document = virtual_documents[result.hostDocumentFilePath][language_kind]

virtual_document:update_content(result)

local buf_eol = utils.buffer_eol(virtual_document.buf)
local lines = vim.fn.split(virtual_document.content, buf_eol, true)
vim.api.nvim_buf_set_lines(virtual_document.buf, 0, -1, false, lines)
end

---Creates virtual buffers for the given source buffer
---@param source_buf integer
function M.register_vbufs(source_buf)
local currentFile = vim.api.nvim_buf_get_name(source_buf)
return M.register_vbufs_by_path(currentFile)
end

---Converts a RPC return URI to a file path
---@param uri string
---@return string
local function uri_to_path(uri)
local path = uri:gsub("file://", "")
return path
end

---@param uri string
---@param _version integer
---@param type razor.LanguageKind
---@return rzls.VirtualDocument
function M.get_virtual_document(uri, _version, type)
return virtual_documents[uri_to_path(uri)][type]
end

local document_generation_initialized = false

---@param client vim.lsp.Client
function M.initialize(client, root_dir)
if not roslyn_ready or document_generation_initialized then
return
end

document_generation_initialized = true

local razor_files = vim.fs.find(function(name)
return name:match(".*%.razor$")
end, {
type = "file",
limit = math.huge,
path = root_dir,
})

local opened_documents = vim.tbl_keys(virtual_documents)
for _, razor_file in ipairs(razor_files) do
if not vim.tbl_contains(opened_documents, razor_file) then
local razor_buf = get_or_create_buffer_for_filepath(razor_file, "razor")
vim.api.nvim_buf_call(razor_buf, vim.cmd.edit)
end
end

local pipe_name = utils.uuid()

---@type rzls.ProjectedDocument
local virtual_document = vim.tbl_values(virtual_documents)[1]

local function initialize_roslyn()
local initialized = vim.lsp.buf_notify(virtual_document.buf, "razor/initialize", {
pipeName = pipe_name,
})

-- Roslyn might not have been initialized yet. Repeat every seconds until
-- we can send the notification
if not initialized then
local timer = vim.uv.new_timer()
timer:start(1000, 0, initialize_roslyn)
return
end

client.notify("razor/namedPipeConnect", {
pipeName = pipe_name,
})
end

initialize_roslyn()
end

---@param client vim.lsp.Client
function M.rosyln_is_ready(client, root_dir)
roslyn_ready = true
M.initialize(client, root_dir)
end

return M
Loading

0 comments on commit f78a9a3

Please sign in to comment.