Skip to content

Commit

Permalink
Send structured InitializationOptions to the server (#121)
Browse files Browse the repository at this point in the history
* Send structured `InitializationOptions` to the server

In particular, this lets us respect `air.logLevel` and `air.dependencyLogLevels` on server startup

User and workspace level settings are not used yet but are included for completeness

* Only send over `logLevel` and `dependencyLogLevels` as flat initialization options

* Remove unused cfg-attr

* Simplify `InitializationOptions` creation

* No need for `async`, and don't use interfacing naming
  • Loading branch information
DavisVaughan authored Jan 7, 2025
1 parent 5446d7e commit 92887d3
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 8 deletions.
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
"editor.formatOnSaveMode": "file",
"editor.defaultFormatter": "rust-lang.rust-analyzer"
},
"[typescript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"rust-analyzer.check.command": "clippy",
"rust-analyzer.imports.prefix": "crate",
"rust-analyzer.imports.granularity.group": "item",
Expand Down
11 changes: 8 additions & 3 deletions crates/lsp/src/handlers_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use crate::documents::Document;
use crate::logging;
use crate::logging::LogMessageSender;
use crate::main_loop::LspState;
use crate::settings::InitializationOptions;
use crate::state::workspace_uris;
use crate::state::WorldState;

Expand Down Expand Up @@ -65,9 +66,13 @@ pub(crate) fn initialize(
state: &mut WorldState,
log_tx: LogMessageSender,
) -> anyhow::Result<InitializeResult> {
// TODO: Get user specified options from `params.initialization_options`
let log_level = None;
let dependency_log_levels = None;
let InitializationOptions {
log_level,
dependency_log_levels,
} = params.initialization_options.map_or_else(
InitializationOptions::default,
InitializationOptions::from_value,
);

logging::init_logging(
log_tx,
Expand Down
1 change: 1 addition & 0 deletions crates/lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod handlers_state;
pub mod logging;
pub mod main_loop;
pub mod rust_analyzer;
pub mod settings;
pub mod state;
pub mod to_proto;
pub mod tower_lsp;
Expand Down
2 changes: 1 addition & 1 deletion crates/lsp/src/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ pub(crate) struct GlobalState {
log_tx: Option<LogMessageSender>,
}

/// Unlike `WorldState`, `ParserState` cannot be cloned and is only accessed by
/// Unlike `WorldState`, `LspState` cannot be cloned and is only accessed by
/// exclusive handlers.
pub(crate) struct LspState {
/// The negociated encoding for document positions. Note that documents are
Expand Down
24 changes: 24 additions & 0 deletions crates/lsp/src/settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use serde::Deserialize;
use serde_json::Value;

/// This is the exact schema for initialization options sent in by the client
/// during initialization. Remember that initialization options are ones that are
/// strictly required at startup time, and most configuration options should really be
/// "pulled" dynamically by the server after startup and whenever we receive a
/// configuration change notification (#121).
#[derive(Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub(crate) struct InitializationOptions {
pub(crate) log_level: Option<crate::logging::LogLevel>,
pub(crate) dependency_log_levels: Option<String>,
}

impl InitializationOptions {
pub(crate) fn from_value(value: Value) -> Self {
serde_json::from_value(value)
.map_err(|err| {
tracing::error!("Failed to deserialize initialization options: {err}. Falling back to default settings.");
})
.unwrap_or_default()
}
}
59 changes: 58 additions & 1 deletion editors/code/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,29 @@
]
}
],
"configuration": {
"properties": {
"air.logLevel": {
"default": null,
"markdownDescription": "Controls the log level of the language server.\n\n**This setting requires a restart to take effect.**",
"enum": [
"error",
"warning",
"info",
"debug",
"trace"
],
"scope": "application",
"type": "string"
},
"air.dependencyLogLevels": {
"default": null,
"markdownDescription": "Controls the log level of the Rust crates that the language server depends on.\n\n**This setting requires a restart to take effect.**",
"scope": "application",
"type": "string"
}
}
},
"configurationDefaults": {
"[r]": {
"editor.defaultFormatter": "Posit.air"
Expand Down
10 changes: 7 additions & 3 deletions editors/code/src/lsp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from "vscode";
import * as lc from "vscode-languageclient/node";
import { default as PQueue } from "p-queue";
import { getInitializationOptions } from "./settings";

// All session management operations are put on a queue. They can't run
// concurrently and either result in a started or stopped state. Starting when
Expand Down Expand Up @@ -52,7 +53,9 @@ export class Lsp {
return;
}

let options: lc.ServerOptions = {
const initializationOptions = getInitializationOptions("air");

let serverOptions: lc.ServerOptions = {
command: "air",
args: ["language-server"],
};
Expand All @@ -71,13 +74,14 @@ export class Lsp {
vscode.workspace.createFileSystemWatcher("**/*.[Rr]"),
},
outputChannel: this.channel,
initializationOptions: initializationOptions,
};

const client = new lc.LanguageClient(
"airLanguageServer",
"Air Language Server",
options,
clientOptions,
serverOptions,
clientOptions
);
await client.start();

Expand Down
41 changes: 41 additions & 0 deletions editors/code/src/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ConfigurationScope, workspace, WorkspaceConfiguration } from "vscode";

type LogLevel = "error" | "warn" | "info" | "debug" | "trace";

// This is a direct representation of the Client settings sent to the Server in the
// `initializationOptions` field of `InitializeParams`. These are only pulled at the
// user level since they are global settings on the server side (and are scoped to
// `"scope": "application"` in `package.json` so they can't even be set at workspace level).
export type InitializationOptions = {
logLevel?: LogLevel;
dependencyLogLevels?: string;
};

export function getInitializationOptions(
namespace: string
): InitializationOptions {
const config = getConfiguration(namespace);

return {
logLevel: getOptionalUserValue<LogLevel>(config, "logLevel"),
dependencyLogLevels: getOptionalUserValue<string>(
config,
"dependencyLogLevels"
),
};
}

function getOptionalUserValue<T>(
config: WorkspaceConfiguration,
key: string
): T | undefined {
const inspect = config.inspect<T>(key);
return inspect?.globalValue;
}

function getConfiguration(
config: string,
scope?: ConfigurationScope
): WorkspaceConfiguration {
return workspace.getConfiguration(config, scope);
}

0 comments on commit 92887d3

Please sign in to comment.