Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send structured InitializationOptions to the server #121

Merged
merged 5 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.**",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These static settings are a bit annoying. Probably worth it to make it dynamic right away?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to do in a follow up after we rework testing a bit to allow for dynamicness in the logging code

"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);
}
Loading