Merge pull request #43 from kbwo/fix/data-structure

Change adapterCommand schema
This commit is contained in:
Kodai Kabasawa 2024-10-30 23:43:36 +09:00 committed by GitHub
commit 7da5b98850
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 237 additions and 221 deletions

View file

@ -2,15 +2,13 @@
"testing.enable": true,
"testing.fileTypes": ["rust"],
"testing.adapterCommand": {
"rust": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-test", "--workspace"],
"include": ["/**/*.rs"],
"exclude": ["/demo/**/*"],
"workspace_dir": "."
}
]
"rust": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-test", "--workspace"],
"include": ["/**/*.rs"],
"exclude": ["/demo/**/*"],
"workspace_dir": "."
}
},
"testing.enableWorkspaceDiagnostics": true,
"testing.server.path": "testing-language-server",

111
README.md
View file

@ -26,51 +26,98 @@ cargo install testing-ls-adapter
- [x] Realtime testing diagnostics
- [x] [VSCode extension](https://github.com/kbwo/vscode-testing-ls)
- [x] [Coc.nvim extension](https://github.com/kbwo/coc-testing-ls)
- [x] [coc.nvim extension](https://github.com/kbwo/coc-testing-ls)
- [x] For Neovim builtin LSP, see [demo/README.md](./demo/README.md)
- [ ] More efficient checking of diagnostics
- [ ] Useful commands in each extension
## Configuration
language server config:
### VSCode
```
"languageserver": {
"testing": {
"command": "<server path>/testing-language-server",
"trace.server": "verbose",
"filetypes": [
"rust",
"javascript"
],
"initializationOptions": {
"initializationOptions": {
"adapterCommand": {
"cargo test": [
{
"path": "<adapter path>/testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-test"],
"include": ["**/*.rs"],
"exclude": ["**/target/**"]
}
],
"jest": [
{
"path": "<adapter path>/testing-ls-adapter",
"extra_arg": ["--test-kind=jest"],
"include": ["/**/*.js"],
"exclude": ["/node_modules/**/*"]
}
]
Install from [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=kbwo.testing-language-server).
You should set `adapterCommand` in `initializationOptions` for each project.
You can see the example in [settings.json](./demo/.vscode/settings.json).
### coc.nvim
Install from `:CocInstall coc-testing-ls`.
You should set `adapterCommand` in `initializationOptions` for each project.
You can see the example in [See more example](./demo/.vim/coc-settings.json)
### Neovim (nvim-lspconfig)
```lua
local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')
local util = require "lspconfig/util"
configs.testing_ls = {
default_config = {
cmd = { "testing-language-server" },
filetypes = { "rust" },
root_dir = util.root_pattern(".git", "Cargo.toml"),
init_options = {
enable = true,
fileTypes = {"rust"},
adapterCommand = {
-- See test execution settings for each project
-- This configuration assumes a Rust project
rust = {
path = "testing-ls-adapter",
extra_arg = { "--test-kind=cargo-test", "--workspace" },
include = { "/demo/**/src/**/*.rs"},
exclude = { "/**/target/**"},
}
},
enableWorkspaceDiagnostics = true,
trace = {
server = "verbose"
}
}
},
docs = {
description = [[
https://github.com/kbwo/testing-language-server
Language Server for real-time testing.
]],
},
}
lspconfig.testing_ls.setup{}
```
## ⚠️ Breaking Changes (2024-10-25)
The configuration structure for adapter commands has been changed:
**Before:**
```json
"adapterCommand": {
"rust": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-test"]
// ...
}
]
}
```
**After:**
```json
"adapterCommand": {
"rust": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-test"]
// ...
}
}
```
[See more example](./demo/.vim/coc-settings.json)
The array wrapper has been removed to simplify the configuration structure. Please update your settings accordingly.
## Adapter
- [x] `cargo test`
@ -85,4 +132,4 @@ language server config:
### Writing custom adapter
⚠ The specification of adapter CLI is not stabilized yet.
See [ADAPTER_SPEC.md](./doc/ADAPTER_SPEC.md) and [spec.rs](./src/spec.rs).
See [ADAPTER_SPEC.md](./doc/ADAPTER_SPEC.md) and [spec.rs](./src/spec.rs).

3
demo/.helix/config.toml Normal file
View file

@ -0,0 +1,3 @@
[editor.soft-wrap]
enable = true
max-wrap = 25 # increase value to reduce forced mid-word wrapping

View file

@ -6,70 +6,54 @@
"filetypes": ["rust", "javascript", "go", "typescript", "php"],
"initializationOptions": {
"adapterCommand": {
"cargo-test": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-test"],
"include": ["/**/src/**/*.rs"],
"exclude": ["/**/target/**"]
}
],
"cargo-nextest": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-nextest"],
"include": ["/**/src/**/*.rs"],
"exclude": ["/**/target/**"]
}
],
"jest": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=jest"],
"include": ["/jest/*.js"],
"exclude": ["/jest/**/node_modules/**/*"]
}
],
"vitest": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=vitest"],
"include": ["/vitest/*.test.ts", "/vitest/config/**/*.test.ts"],
"exclude": ["/vitest/**/node_modules/**/*"]
}
],
"deno": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=deno"],
"include": ["/deno/*.ts"],
"exclude": []
}
],
"go": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=go-test"],
"include": ["/**/*.go"],
"exclude": []
}
],
"node-test": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=node-test"],
"include": ["/node-test/*.test.js"],
"exclude": []
}
],
"phpunit": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=phpunit"],
"include": ["/**/*Test.php"],
"exclude": ["/phpunit/vendor/**/*.php"]
}
]
"cargo-test": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-test"],
"include": ["/**/src/**/*.rs"],
"exclude": ["/**/target/**"]
},
"cargo-nextest": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-nextest"],
"include": ["/**/src/**/*.rs"],
"exclude": ["/**/target/**"]
},
"jest": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=jest"],
"include": ["/jest/*.js"],
"exclude": ["/jest/**/node_modules/**/*"]
},
"vitest": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=vitest"],
"include": ["/vitest/*.test.ts", "/vitest/config/**/*.test.ts"],
"exclude": ["/vitest/**/node_modules/**/*"]
},
"deno": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=deno"],
"include": ["/deno/*.ts"],
"exclude": []
},
"go": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=go-test"],
"include": ["/**/*.go"],
"exclude": []
},
"node-test": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=node-test"],
"include": ["/node-test/*.test.js"],
"exclude": []
},
"phpunit": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=phpunit"],
"include": ["/**/*Test.php"],
"exclude": ["/phpunit/vendor/**/*.php"]
}
}
}
}

View file

@ -2,62 +2,48 @@
"testing.enable": true,
"filetypes": ["rust", "javascript", "go", "typescript", "php"],
"testing.adapterCommand": {
"cargo-nextest": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-nextest"],
"include": ["/**/src/**/*.rs"],
"exclude": ["/**/target/**"]
}
],
"jest": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=jest"],
"include": ["/jest/*.js"],
"exclude": ["/jest/**/node_modules/**/*"]
}
],
"vitest": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=vitest"],
"include": ["/vitest/*.test.ts", "/vitest/config/**/*.test.ts"],
"exclude": ["/vitest/**/node_modules/**/*"]
}
],
"deno": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=deno"],
"include": ["/deno/*.ts"],
"exclude": []
}
],
"go": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=go-test"],
"include": ["/**/*.go"],
"exclude": []
}
],
"node-test": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=node-test"],
"include": ["/node-test/*.test.js"],
"exclude": []
}
],
"phpunit": [
{
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=phpunit"],
"include": ["/**/*Test.php"],
"exclude": ["/phpunit/vendor/**/*.php"]
}
]
"cargo-nextest": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=cargo-nextest"],
"include": ["/**/src/**/*.rs"],
"exclude": ["/**/target/**"]
},
"jest": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=jest"],
"include": ["/jest/*.js"],
"exclude": ["/jest/**/node_modules/**/*"]
},
"vitest": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=vitest"],
"include": ["/vitest/*.test.ts", "/vitest/config/**/*.test.ts"],
"exclude": ["/vitest/**/node_modules/**/*"]
},
"deno": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=deno"],
"include": ["/deno/*.ts"],
"exclude": []
},
"go": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=go-test"],
"include": ["/**/*.go"],
"exclude": []
},
"node-test": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=node-test"],
"include": ["/node-test/*.test.js"],
"exclude": []
},
"phpunit": {
"path": "testing-ls-adapter",
"extra_arg": ["--test-kind=phpunit"],
"include": ["/**/*Test.php"],
"exclude": ["/phpunit/vendor/**/*.php"]
}
},
"testing.enableWorkspaceDiagnostics": true,
"testing.server.path": "testing-language-server",

View file

@ -17,7 +17,7 @@ use std::process::Output;
#[derive(Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct InitializedOptions {
adapter_command: HashMap<AdapterId, Vec<AdapterConfiguration>>,
adapter_command: HashMap<AdapterId, AdapterConfiguration>,
enable_workspace_diagnostics: Option<bool>,
}
@ -87,7 +87,7 @@ impl TestingLS {
Ok(())
}
fn adapter_commands(&self) -> HashMap<AdapterId, Vec<AdapterConfiguration>> {
fn adapter_commands(&self) -> HashMap<AdapterId, AdapterConfiguration> {
self.options.adapter_command.clone()
}
@ -150,62 +150,60 @@ impl TestingLS {
let project_dir = self.project_dir()?;
self.workspaces_cache = vec![];
// Nested and multiple loops, but each count is small
for adapter_commands in adapter_commands.into_values() {
for adapter in adapter_commands.into_iter() {
let AdapterConfiguration {
path,
extra_arg,
env,
include,
exclude,
workspace_dir,
..
} = &adapter;
let file_paths = Self::project_files(&project_dir, include, exclude);
if file_paths.is_empty() {
continue;
}
let mut adapter_command = Command::new(path);
let mut args_file_path: Vec<&str> = vec![];
file_paths.iter().for_each(|file_path| {
args_file_path.push("--file-paths");
args_file_path.push(file_path);
});
let output = adapter_command
.arg("detect-workspace")
.args(args_file_path)
.arg("--")
.args(extra_arg)
.envs(env)
.output()
.map_err(|err| LSError::Adapter(err.to_string()))?;
let adapter_result = String::from_utf8(output.stdout)
.map_err(|err| LSError::Adapter(err.to_string()))?;
let workspace: DetectWorkspaceResult = match serde_json::from_str(&adapter_result) {
Ok(result) => result,
Err(err) => {
let stderr = String::from_utf8(output.stderr);
tracing::error!("Failed to parse adapter result: {:?}", err);
tracing::error!("Error: {:?}", stderr);
return Err(LSError::Adapter(err.to_string()));
}
};
let workspace = if let Some(workspace_dir) = workspace_dir {
let workspace_dir = resolve_path(&project_dir, workspace_dir)
.to_str()
.unwrap()
.to_string();
let target_paths = workspace
.into_iter()
.flat_map(|kv| kv.1)
.collect::<Vec<_>>();
HashMap::from([(workspace_dir, target_paths)])
} else {
workspace
};
self.workspaces_cache
.push(WorkspaceAnalysis::new(adapter, workspace))
for adapter in adapter_commands.into_values() {
let AdapterConfiguration {
path,
extra_arg,
env,
include,
exclude,
workspace_dir,
..
} = &adapter;
let file_paths = Self::project_files(&project_dir, include, exclude);
if file_paths.is_empty() {
continue;
}
let mut adapter_command = Command::new(path);
let mut args_file_path: Vec<&str> = vec![];
file_paths.iter().for_each(|file_path| {
args_file_path.push("--file-paths");
args_file_path.push(file_path);
});
let output = adapter_command
.arg("detect-workspace")
.args(args_file_path)
.arg("--")
.args(extra_arg)
.envs(env)
.output()
.map_err(|err| LSError::Adapter(err.to_string()))?;
let adapter_result = String::from_utf8(output.stdout)
.map_err(|err| LSError::Adapter(err.to_string()))?;
let workspace: DetectWorkspaceResult = match serde_json::from_str(&adapter_result) {
Ok(result) => result,
Err(err) => {
let stderr = String::from_utf8(output.stderr);
tracing::error!("Failed to parse adapter result: {:?}", err);
tracing::error!("Error: {:?}", stderr);
return Err(LSError::Adapter(err.to_string()));
}
};
let workspace = if let Some(workspace_dir) = workspace_dir {
let workspace_dir = resolve_path(&project_dir, workspace_dir)
.to_str()
.unwrap()
.to_string();
let target_paths = workspace
.into_iter()
.flat_map(|kv| kv.1)
.collect::<Vec<_>>();
HashMap::from([(workspace_dir, target_paths)])
} else {
workspace
};
self.workspaces_cache
.push(WorkspaceAnalysis::new(adapter, workspace))
}
tracing::info!("workspaces_cache={:#?}", self.workspaces_cache);
send_stdout(&json!({
@ -497,7 +495,7 @@ mod tests {
name: "demo".to_string(),
}]),
options: InitializedOptions {
adapter_command: HashMap::from([(String::from(".rs"), vec![])]),
adapter_command: HashMap::new(),
enable_workspace_diagnostics: Some(true),
},
workspaces_cache: Vec::new(),
@ -527,7 +525,7 @@ mod tests {
name: "demo".to_string(),
}]),
options: InitializedOptions {
adapter_command: HashMap::from([(String::from(".rs"), vec![adapter_conf])]),
adapter_command: HashMap::from([(String::from(".rs"), adapter_conf)]),
enable_workspace_diagnostics: Some(true),
},
workspaces_cache: Vec::new(),
@ -592,7 +590,7 @@ mod tests {
name: "demo".to_string(),
}]),
options: InitializedOptions {
adapter_command: HashMap::from([(String::from(".rs"), vec![adapter_conf.clone()])]),
adapter_command: HashMap::from([(String::from(".rs"), adapter_conf.clone())]),
enable_workspace_diagnostics: Some(true),
},
workspaces_cache: Vec::new(),