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

[move-ide] Custom compiler edition notification #20146

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ import { log } from './log';
import { assert } from 'console';
import { IndentAction } from 'vscode';

/**
* Info about compiler edition passed to the IDE
*/
interface EditionInfo {
edition: string;
release?: string;
}

function version(path: string, args?: readonly string[]): string | null {
const versionString = childProcess.spawnSync(
path, args, { encoding: 'utf8' },
Expand Down Expand Up @@ -42,9 +50,9 @@ function semanticVersion(versionString: string | null): semver.SemVer | null {
}

function shouldInstall(bundledVersionString: string | null,
bundledVersion: semver.SemVer | null,
highestVersionString: string | null,
highestVersion: semver.SemVer | null): boolean {
bundledVersion: semver.SemVer | null,
highestVersionString: string | null,
highestVersion: semver.SemVer | null): boolean {
if (bundledVersionString === null || bundledVersion === null) {
log.info('No bundled binary');
return false;
Expand Down Expand Up @@ -124,7 +132,7 @@ export class Context {
): void {
const disposable = vscode.commands.registerCommand(
`move.${name}`,
async (...args: Array<any>) : Promise<any> => {
async (...args: Array<any>): Promise<any> => {
const ret = await command(this, ...args);
return ret;
},
Expand Down Expand Up @@ -204,6 +212,17 @@ export class Context {
serverOptions,
clientOptions,
);

// Register the handler for the custom notification
client.onNotification('customNotification/edition', (result: EditionInfo) => {
let msg = 'Compiler edition: ' + result.edition;
if (result.release !== undefined) {
msg += ' release: ' + result.release;
}
log.info(msg);
});


log.info('Starting client...');
const res = client.start();
this.extensionContext.subscriptions.push({ dispose: async () => client.stop() });
Expand Down Expand Up @@ -260,10 +279,10 @@ export class Context {
this.inlayHintsParam = this.configuration.inlayHintsForParam;
try {
await this.stopClient();
if (pathsChanged) {
await this.installServerBinary(this.extensionContext);
}
await this.startClient();
if (pathsChanged) {
await this.installServerBinary(this.extensionContext);
}
await this.startClient();
} catch (err) {
// Handle error
log.info(String(err));
Expand All @@ -288,13 +307,13 @@ export class Context {
log.info(`Deleting existing move-analyzer binary at '${this.configuration.defaultServerPath}'`);
await vscode.workspace.fs.delete(this.configuration.defaultServerPath);
}
} else {
} else {
log.info(`Creating directory for move-analyzer binary at '${this.configuration.defaultServerDir}'`);
await vscode.workspace.fs.createDirectory(this.configuration.defaultServerDir);
}
}

log.info(`Copying move-analyzer binary to '${this.configuration.defaultServerPath}'`);
await vscode.workspace.fs.copy(bundledServerPath, this.configuration.defaultServerPath);
log.info(`Copying move-analyzer binary to '${this.configuration.defaultServerPath}'`);
await vscode.workspace.fs.copy(bundledServerPath, this.configuration.defaultServerPath);
}

/**
Expand Down Expand Up @@ -333,8 +352,8 @@ export class Context {

// Check if server binary is bundled with the extension
const bundledServerPath = vscode.Uri.joinPath(extensionContext.extensionUri,
'language-server',
this.configuration.serverName);
'language-server',
this.configuration.serverName);
const bundledVersionString = version(bundledServerPath.fsPath, serverVersionArgs);
const bundledVersion = semanticVersion(bundledVersionString);
log.info(`Bundled version: ${bundledVersion}`);
Expand Down Expand Up @@ -380,7 +399,7 @@ export class Context {
this.resolvedServerPath = this.configuration.serverPath;
this.resolvedServerArgs = serverArgs;
log.info(`Setting v${standaloneVersion.version} of installed standalone move-analyzer ` +
` at '${this.resolvedServerPath}' as the highest one`);
` at '${this.resolvedServerPath}' as the highest one`);
}

if (cliVersion !== null && (highestVersion === null || semver.gt(cliVersion, highestVersion))) {
Expand All @@ -389,7 +408,7 @@ export class Context {
this.resolvedServerPath = this.configuration.suiPath;
this.resolvedServerArgs = cliArgs;
log.info(`Setting v${cliVersion.version} of installed CLI move-analyzer ` +
` at '${this.resolvedServerPath}' as the highest one`);
` at '${this.resolvedServerPath}' as the highest one`);
}

if (shouldInstall(bundledVersionString, bundledVersion, highestVersionString, highestVersion)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import * as path from 'path';
import * as vscode from 'vscode';
import * as commands from './commands';


/**
* An extension command that displays the version of the server that this extension
* interfaces with.
Expand Down
114 changes: 83 additions & 31 deletions external-crates/move/crates/move-analyzer/src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ use anyhow::Result;
use crossbeam::channel::{bounded, select};
use lsp_server::{Connection, Message, Notification, Request, Response};
use lsp_types::{
notification::Notification as _, request::Request as _, CompletionOptions, Diagnostic,
HoverProviderCapability, InlayHintOptions, InlayHintServerCapabilities, OneOf, SaveOptions,
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
notification::{self, Notification as _},
request::Request as _,
CompletionOptions, HoverProviderCapability, InlayHintOptions, InlayHintServerCapabilities,
OneOf, SaveOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
TypeDefinitionProviderCapability, WorkDoneProgressOptions,
};
use move_compiler::linters::LintLevel;
use move_compiler::{editions::Edition, linters::LintLevel};
use serde::{Deserialize, Serialize};
use std::{
collections::BTreeMap,
path::PathBuf,
Expand All @@ -29,6 +31,23 @@ const LINT_NONE: &str = "none";
const LINT_DEFAULT: &str = "default";
const LINT_ALL: &str = "all";

/// Info about compiler edition passed to the IDE
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EditionInfo {
pub edition: String,
pub release: Option<String>,
}
Comment on lines +36 to +39
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason not to use move-compiler::editions::Edition?


/// Custom notification for the language server to report compiler edition
/// to the IDE client.
#[derive(Debug)]
struct EditionNotification;

impl notification::Notification for EditionNotification {
type Params = EditionInfo;
const METHOD: &'static str = "customNotification/edition";
}

#[allow(deprecated)]
pub fn run() {
// stdio is used to communicate Language Server Protocol requests and responses.
Expand Down Expand Up @@ -112,7 +131,7 @@ pub fn run() {
})
.expect("could not serialize server capabilities");

let (diag_sender, diag_receiver) = bounded::<Result<BTreeMap<PathBuf, Vec<Diagnostic>>>>(0);
let (custom_sender, custom_receiver) = bounded::<Result<symbols::CustomNotification>>(0);
let initialize_params: lsp_types::InitializeParams =
serde_json::from_value(client_response).expect("could not deserialize client capabilities");

Expand All @@ -134,35 +153,41 @@ pub fn run() {
};
eprintln!("linting level {:?}", lint);

let symbolicator_runner = symbols::SymbolicatorRunner::new(
ide_files_root.clone(),
symbols_map.clone(),
pkg_deps.clone(),
diag_sender,
lint,
);

// If initialization information from the client contains a path to the directory being
// opened, try to initialize symbols before sending response to the client. Do not bother
// with diagnostics as they will be recomputed whenever the first source file is opened. The
// main reason for this is to enable unit tests that rely on the symbolication information
// to be available right after the client is initialized.
let mut edition_opt = None;
if let Some(uri) = initialize_params.root_uri {
let build_path = uri.to_file_path().unwrap();
if let Some(p) = symbols::SymbolicatorRunner::root_dir(&build_path) {
if let Ok((Some(new_symbols), _)) = symbols::get_symbols(
Arc::new(Mutex::new(BTreeMap::new())),
if let Ok(symbols::SymbolicationResult {
symbols: Some(new_symbols),
edition,
..
}) = symbols::get_symbols(
pkg_deps.clone(),
ide_files_root.clone(),
p.as_path(),
lint,
None,
) {
edition_opt = edition;
let mut old_symbols_map = symbols_map.lock().unwrap();
old_symbols_map.insert(p, new_symbols);
}
}
}

let symbolicator_runner = symbols::SymbolicatorRunner::new(
ide_files_root.clone(),
symbols_map.clone(),
pkg_deps.clone(),
custom_sender,
lint,
);

let context = Context {
connection,
symbols: symbols_map.clone(),
Expand Down Expand Up @@ -193,33 +218,45 @@ pub fn run() {
)
.expect("could not finish connection initialization");

if let Some(e) = edition_opt {
eprintln!("sending initial edition info");
edition_notify(e, &context.connection);
}

let mut shutdown_req_received = false;
loop {
select! {
recv(diag_receiver) -> message => {
recv(custom_receiver) -> message => {
match message {
Ok(result) => {
match result {
Ok(diags) => {
for (k, v) in diags {
let url = Url::from_file_path(k).unwrap();
let params = lsp_types::PublishDiagnosticsParams::new(url, v, None);
let notification = Notification::new(lsp_types::notification::PublishDiagnostics::METHOD.to_string(), params);
if let Err(err) = context
.connection
.sender
.send(lsp_server::Message::Notification(notification)) {
eprintln!("could not send diagnostics response: {:?}", err);
};
Ok(custom_notification) => {
use symbols::CustomNotification as CN;
match custom_notification {
CN::Diags(diags) => {
eprintln!("sending diagnostics");
for (k, v) in diags {
let url = Url::from_file_path(k).unwrap();
let params = lsp_types::PublishDiagnosticsParams::new(url, v, None);
let notification = Notification::new(lsp_types::notification::PublishDiagnostics::METHOD.to_string(), params);
if let Err(err) = context
.connection
.sender
.send(lsp_server::Message::Notification(notification)) {
eprintln!("could not send diagnostics response: {:?}", err);
};
}
},
CN::Edition(e) => {
eprintln!("sending edition info");
edition_notify(e, &context.connection);
}
}
},
Err(err) => {
let typ = lsp_types::MessageType::ERROR;
let message = format!("{err}");
// report missing manifest only once to avoid re-generating
// user-visible error in cases when the developer decides to
// keep editing a file that does not belong to a packages
let params = lsp_types::ShowMessageParams { typ, message };
let params = lsp_types::ShowMessageParams { typ, message };
let notification = Notification::new(lsp_types::notification::ShowMessage::METHOD.to_string(), params);
if let Err(err) = context
.connection
Expand Down Expand Up @@ -271,6 +308,21 @@ pub fn run() {
eprintln!("Shut down language server '{}'.", exe);
}

/// Notify the IDE client about the edition of the compiler being used.
fn edition_notify(e: Edition, connection: &Connection) {
let edition_info = EditionInfo {
edition: e.edition.to_string(),
release: e.release.map(|r| r.to_string()),
};
let notification = Notification::new(EditionNotification::METHOD.to_string(), edition_info);
if let Err(err) = connection
.sender
.send(lsp_server::Message::Notification(notification))
{
eprintln!("could not send edition response: {:?}", err);
};
}

/// This function returns `true` if shutdown request has been received, and `false` otherwise.
/// The reason why this information is also passed as an argument is that according to the LSP
/// spec, if any additional requests are received after shutdownd then the LSP implementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use crate::{
utils::{completion_item, PRIMITIVE_TYPE_COMPLETIONS},
},
context::Context,
symbols::{self, CursorContext, PrecomputedPkgDepsInfo, SymbolicatorRunner, Symbols},
symbols::{
get_symbols, CursorContext, PrecomputedPkgDepsInfo, SymbolicationResult,
SymbolicatorRunner, Symbols,
},
};
use lsp_server::Request;
use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, Position};
Expand Down Expand Up @@ -166,7 +169,7 @@ fn compute_completions_new_symbols(
};
let cursor_path = path.to_path_buf();
let cursor_info = Some((&cursor_path, cursor_position));
let (symbols, _diags) = symbols::get_symbols(
let SymbolicationResult { symbols, .. } = get_symbols(
pkg_dependencies,
ide_files_root,
&pkg_path,
Expand Down
Loading
Loading