feat: add Solargraph as the built-in Ruby LSP preset
This commit is contained in:
@@ -253,6 +253,7 @@ LSP runs in Rust, independently of Ruby plugin hosts. **Every language server is
|
||||
```ruby
|
||||
Vamp.configure do |config|
|
||||
config.lsp["rust"] = true # rust-analyzer, extensions: ["rs"]
|
||||
config.lsp["ruby"] = true # solargraph stdio, extensions: ["rb"]
|
||||
config.lsp["python"] = false # disabled (also the default when absent)
|
||||
|
||||
config.lsp["my-language"] = {
|
||||
@@ -267,10 +268,12 @@ Vamp.configure do |config|
|
||||
end
|
||||
```
|
||||
|
||||
Keys are arbitrary nonempty strings, used as the protocol's `languageId`; they need no Tree-sitter grammar or hardcoded language enum. Absent/`false` entries are disabled. `true` enables a built-in preset: currently `"rust"` uses `rust-analyzer`/`rs`, and `"python"` uses `pylsp`/`py`. Unknown keys need a configuration object with nonempty `command` argv and `extensions` (without leading dots). Objects also explicitly enable servers; known presets supply omitted command/extensions. For example, `config.lsp["rust"] = { command: ["/custom/path/rust-analyzer"] }` overrides the executable while retaining `rs`.
|
||||
Keys are arbitrary nonempty strings, used as the protocol's `languageId`; they need no Tree-sitter grammar or hardcoded language enum. Absent/`false` entries are disabled. `true` enables a built-in preset: `"rust"` uses `rust-analyzer`/`rs`, `"python"` uses `pylsp`/`py`, and `"ruby"` uses `solargraph stdio`/`rb`. Unknown keys need a configuration object with nonempty `command` argv and `extensions` (without leading dots). Objects also explicitly enable servers; known presets supply omitted command/extensions. For example, `config.lsp["rust"] = { command: ["/custom/path/rust-analyzer"] }` overrides the executable while retaining `rs`.
|
||||
|
||||
Only `command`, `extensions`, `initialization_options`, and `settings` are accepted. Both option/settings values must be JSON-compatible objects and default to `{}`. Invalid declarations reject configuration with a notice rather than activating a guessed server. Server commands are executable/argument arrays, never shell commands; use an absolute path if the executable is not on PATH. No server installation or restart is automatic.
|
||||
|
||||
Install Solargraph yourself with `gem install solargraph` in the project's Ruby environment. The Ruby preset launches `["solargraph", "stdio"]`, following [Solargraph's stdio integration](https://solargraph.org/guides/language-server), and uses [YARD annotations](https://solargraph.org/guides/yard) for type information. Explicit `command` overrides replace the entire argv: for a bundled installation, use `config.lsp["ruby"] = { command: ["bundle", "exec", "solargraph", "stdio"] }`; Vamp retains the `rb` extension but does not append `stdio` to your override.
|
||||
|
||||
Servers start lazily only when a matching buffer opens. Extension matching is case-sensitive; if enabled languages overlap, the first language in sorted key order wins. One worker runs per language configuration/project root, with at most 16 enabled languages/server-project keys. Start vamp from your project root: its startup working directory is the workspace; automatic root discovery is not implemented.
|
||||
|
||||
The native module initializes each server, sends `initialization_options` as `initializationOptions`, answers `workspace/configuration` from `settings` (including dotted sections; missing sections return `null`), honors open/change/save capabilities, synchronizes in-memory text including hidden dirty buffers, and sends close events. Incremental servers receive whole-document range replacements. Process/pipe I/O and requests stay on bounded background workers; input and idle diagnostic redraw do not wait for server responses.
|
||||
|
||||
@@ -15,6 +15,7 @@ Vamp.configure do |config|
|
||||
config.plugin "plugins/greeting.rb"
|
||||
# Native LSP is disabled by default; install a server and explicitly opt in.
|
||||
# config.lsp["rust"] = true # built-in rust-analyzer preset
|
||||
# config.lsp["ruby"] = true # built-in Solargraph stdio preset (gem install solargraph)
|
||||
# config.lsp["my-language"] = {
|
||||
# command: ["my-language-server", "--stdio"], extensions: ["myl"],
|
||||
# initialization_options: {}, settings: {}
|
||||
|
||||
+5
-4
@@ -10,9 +10,10 @@ pub fn resolve(value: Value) -> Result<Vec<ServerConfig>, String> {
|
||||
if language_id.is_empty() || language_id.chars().any(char::is_control) {
|
||||
return Err("LSP language keys must not be empty or contain control characters".into());
|
||||
}
|
||||
let preset = match language_id.as_str() {
|
||||
"rust" => Some(("rust-analyzer", "rs")),
|
||||
"python" => Some(("pylsp", "py")),
|
||||
let preset: Option<(&[&str], &str)> = match language_id.as_str() {
|
||||
"rust" => Some((&["rust-analyzer"], "rs")),
|
||||
"python" => Some((&["pylsp"], "py")),
|
||||
"ruby" => Some((&["solargraph", "stdio"], "rb")),
|
||||
_ => None,
|
||||
};
|
||||
let mut options = match value {
|
||||
@@ -45,7 +46,7 @@ pub fn resolve(value: Value) -> Result<Vec<ServerConfig>, String> {
|
||||
let command = match options.remove("command") {
|
||||
Some(value) => command(value, &language_id)?,
|
||||
None => match preset {
|
||||
Some((command, _)) => vec![command.into()],
|
||||
Some((command, _)) => command.iter().map(|argument| (*argument).into()).collect(),
|
||||
None => {
|
||||
return Err(format!(
|
||||
"LSP language {language_id:?} requires command argv"
|
||||
|
||||
@@ -37,6 +37,7 @@ Ruby config: $XDG_CONFIG_HOME/vamp/config.rb or ~/.config/vamp/config.rb
|
||||
Set VAMP_RUBY to choose the Ruby executable.
|
||||
|
||||
Native LSP is disabled by default: config.lsp[\"rust\"] = true enables a preset
|
||||
Presets: rust -> rust-analyzer, python -> pylsp, ruby -> solargraph stdio
|
||||
Custom languages: config.lsp[\"my-language\"] = { command: [...], extensions: [...] }
|
||||
LSP commands: :lsp.definition, :lsp.diagnostics, :lsp.hover
|
||||
Hover binding: config.bind \"K\", config.core.lsp.hover (no default key)";
|
||||
|
||||
+19
-8
@@ -969,19 +969,21 @@ end"#,
|
||||
r#"Vamp.configure do |config|
|
||||
config.lsp["rust"] = true
|
||||
config.lsp["python"] = true
|
||||
config.lsp["ruby"] = true
|
||||
end"#,
|
||||
);
|
||||
let config = load_config(&path).unwrap();
|
||||
assert_eq!(config.lsp.len(), 2);
|
||||
for (language, command, extension) in
|
||||
[("rust", "rust-analyzer", "rs"), ("python", "pylsp", "py")]
|
||||
{
|
||||
for (language, command, extension) in [
|
||||
("rust", &["rust-analyzer"][..], "rs"),
|
||||
("python", &["pylsp"][..], "py"),
|
||||
("ruby", &["solargraph", "stdio"][..], "rb"),
|
||||
] {
|
||||
let server = config
|
||||
.lsp
|
||||
.iter()
|
||||
.find(|server| server.language_id == language)
|
||||
.unwrap();
|
||||
assert_eq!(server.command, [command]);
|
||||
assert_eq!(server.command, command);
|
||||
assert_eq!(server.extensions, [extension]);
|
||||
assert_eq!(server.initialization_options, serde_json::json!({}));
|
||||
assert_eq!(server.settings, serde_json::json!({}));
|
||||
@@ -996,6 +998,7 @@ end"#,
|
||||
settings: {"rust-analyzer" => {check: {command: "clippy"}}}
|
||||
}
|
||||
config.lsp["python"] = {command: ["custom pylsp", "--stdio"]}
|
||||
config.lsp["ruby"] = {command: ["bundle", "exec", "solargraph", "stdio"]}
|
||||
config.lsp["my-language.c++"] = {
|
||||
command: ["custom server", "--flag", ""],
|
||||
extensions: ["custom", "لغة"],
|
||||
@@ -1004,7 +1007,6 @@ end"#,
|
||||
end"#,
|
||||
);
|
||||
let config = load_config(&path).unwrap();
|
||||
assert_eq!(config.lsp.len(), 3);
|
||||
let rust = config
|
||||
.lsp
|
||||
.iter()
|
||||
@@ -1029,6 +1031,13 @@ end"#,
|
||||
assert_eq!(python.extensions, ["py"]);
|
||||
assert_eq!(python.initialization_options, serde_json::json!({}));
|
||||
assert_eq!(python.settings, serde_json::json!({}));
|
||||
let ruby = config
|
||||
.lsp
|
||||
.iter()
|
||||
.find(|server| server.language_id == "ruby")
|
||||
.unwrap();
|
||||
assert_eq!(ruby.command, ["bundle", "exec", "solargraph", "stdio"]);
|
||||
assert_eq!(ruby.extensions, ["rb"]);
|
||||
let custom = config
|
||||
.lsp
|
||||
.iter()
|
||||
@@ -1087,12 +1096,14 @@ end"#,
|
||||
}
|
||||
let path = fixture.file(
|
||||
"invalid.rb",
|
||||
r#"Vamp.configure { |config| config.lsp["ruby"] = true }"#,
|
||||
r#"Vamp.configure { |config| config.lsp["unsupported-language"] = true }"#,
|
||||
);
|
||||
let error = load_config(&path).unwrap_err();
|
||||
assert!(!error.contains("Malformed Ruby protocol frame"), "{error}");
|
||||
assert!(
|
||||
error.contains("\"ruby\"") && error.contains("command") && error.contains("extensions"),
|
||||
error.contains("\"unsupported-language\"")
|
||||
&& error.contains("command")
|
||||
&& error.contains("extensions"),
|
||||
"{error}"
|
||||
);
|
||||
}
|
||||
|
||||
+2
-2
@@ -2355,8 +2355,8 @@ end
|
||||
for (config, reason, dismiss) in [
|
||||
("raise 'broken config'", "broken config", Input::Enter),
|
||||
(
|
||||
"Vamp.configure { |config| config.lsp['ruby'] = true }",
|
||||
"ruby",
|
||||
"Vamp.configure { |config| config.lsp['unsupported-language'] = true }",
|
||||
"unsupported-language",
|
||||
Input::Escape,
|
||||
),
|
||||
] {
|
||||
|
||||
Reference in New Issue
Block a user