Merge #8955
8955: feature: Support standalone Rust files r=matklad a=SomeoneToIgnore  Closes https://github.com/rust-analyzer/rust-analyzer/issues/6388 Caveats: * I've decided to support multiple detached files in the code (anticipating the scratch files), but I found no way to open multiple files in VSCode at once: running `code *.rs` makes the plugin to register in the `vscode.workspace.textDocuments` only the first file, while code actually displays all files later. Apparently what happens is the same as when you have VSCode open at some workplace already and then run `code some_other_file.rs`: it gets opened in the same workspace of the same VSCode with no server to support it. If there's a way to override it, I'd appreciate the pointer. * No way to toggle inlay hints, since the setting is updated for the workspace (which does not exist for a single file opened) > [2021-05-24 00:22:49.100] [exthost] [error] Error: Unable to write to Workspace Settings because no workspace is opened. Please open a workspace first and try again. * No runners/lens to run or check the code are implemented for this mode. In theory, we can detect `rustc`, run it on a file and run the resulting binary, but not sure if worth doing it at this stage. Otherwise imports, hints, completion and other features work. Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import * as ra from '../src/lsp_ext';
|
||||
import * as Is from 'vscode-languageclient/lib/common/utils/is';
|
||||
import { assert } from './util';
|
||||
import { WorkspaceEdit } from 'vscode';
|
||||
import { Workspace } from './ctx';
|
||||
|
||||
export interface Env {
|
||||
[name: string]: string;
|
||||
@@ -23,7 +24,7 @@ function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownStri
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc.LanguageClient {
|
||||
export function createClient(serverPath: string, workspace: Workspace, extraEnv: Env): lc.LanguageClient {
|
||||
// '.' Is the fallback if no folder is open
|
||||
// TODO?: Workspace folders support Uri's (eg: file://test.txt).
|
||||
// It might be a good idea to test if the uri points to a file.
|
||||
@@ -31,6 +32,11 @@ export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc
|
||||
const newEnv = Object.assign({}, process.env);
|
||||
Object.assign(newEnv, extraEnv);
|
||||
|
||||
let cwd = undefined;
|
||||
if (workspace.kind === "Workspace Folder") {
|
||||
cwd = workspace.folder.fsPath;
|
||||
};
|
||||
|
||||
const run: lc.Executable = {
|
||||
command: serverPath,
|
||||
options: { cwd, env: newEnv },
|
||||
@@ -43,9 +49,14 @@ export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc
|
||||
'Rust Analyzer Language Server Trace',
|
||||
);
|
||||
|
||||
let initializationOptions = vscode.workspace.getConfiguration("rust-analyzer");
|
||||
if (workspace.kind === "Detached Files") {
|
||||
initializationOptions = { "detachedFiles": workspace.files.map(file => file.uri.fsPath), ...initializationOptions };
|
||||
}
|
||||
|
||||
const clientOptions: lc.LanguageClientOptions = {
|
||||
documentSelector: [{ scheme: 'file', language: 'rust' }],
|
||||
initializationOptions: vscode.workspace.getConfiguration("rust-analyzer"),
|
||||
initializationOptions,
|
||||
diagnosticCollectionName: "rustc",
|
||||
traceOutputChannel,
|
||||
middleware: {
|
||||
|
||||
@@ -7,6 +7,16 @@ import { createClient } from './client';
|
||||
import { isRustEditor, RustEditor } from './util';
|
||||
import { ServerStatusParams } from './lsp_ext';
|
||||
|
||||
export type Workspace =
|
||||
{
|
||||
kind: 'Workspace Folder';
|
||||
folder: vscode.Uri;
|
||||
}
|
||||
| {
|
||||
kind: 'Detached Files';
|
||||
files: vscode.TextDocument[];
|
||||
};
|
||||
|
||||
export class Ctx {
|
||||
private constructor(
|
||||
readonly config: Config,
|
||||
@@ -22,9 +32,9 @@ export class Ctx {
|
||||
config: Config,
|
||||
extCtx: vscode.ExtensionContext,
|
||||
serverPath: string,
|
||||
cwd: string,
|
||||
workspace: Workspace,
|
||||
): Promise<Ctx> {
|
||||
const client = createClient(serverPath, cwd, config.serverExtraEnv);
|
||||
const client = createClient(serverPath, workspace, config.serverExtraEnv);
|
||||
|
||||
const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
extCtx.subscriptions.push(statusBar);
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as commands from './commands';
|
||||
import { activateInlayHints } from './inlay_hints';
|
||||
import { Ctx } from './ctx';
|
||||
import { Config } from './config';
|
||||
import { log, assert, isValidExecutable } from './util';
|
||||
import { log, assert, isValidExecutable, isRustDocument } from './util';
|
||||
import { PersistentState } from './persistent_state';
|
||||
import { fetchRelease, download } from './net';
|
||||
import { activateTaskProvider } from './tasks';
|
||||
@@ -28,26 +28,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
async function tryActivate(context: vscode.ExtensionContext) {
|
||||
// Register a "dumb" onEnter command for the case where server fails to
|
||||
// start.
|
||||
//
|
||||
// FIXME: refactor command registration code such that commands are
|
||||
// **always** registered, even if the server does not start. Use API like
|
||||
// this perhaps?
|
||||
//
|
||||
// ```TypeScript
|
||||
// registerCommand(
|
||||
// factory: (Ctx) => ((Ctx) => any),
|
||||
// fallback: () => any = () => vscode.window.showErrorMessage(
|
||||
// "rust-analyzer is not available"
|
||||
// ),
|
||||
// )
|
||||
const defaultOnEnter = vscode.commands.registerCommand(
|
||||
'rust-analyzer.onEnter',
|
||||
() => vscode.commands.executeCommand('default:type', { text: '\n' }),
|
||||
);
|
||||
context.subscriptions.push(defaultOnEnter);
|
||||
|
||||
const config = new Config(context);
|
||||
const state = new PersistentState(context.globalState);
|
||||
const serverPath = await bootstrap(config, state).catch(err => {
|
||||
@@ -67,14 +47,52 @@ async function tryActivate(context: vscode.ExtensionContext) {
|
||||
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
if (workspaceFolder === undefined) {
|
||||
throw new Error("no folder is opened");
|
||||
const rustDocuments = vscode.workspace.textDocuments.filter(document => isRustDocument(document));
|
||||
if (rustDocuments.length > 0) {
|
||||
ctx = await Ctx.create(config, context, serverPath, { kind: 'Detached Files', files: rustDocuments });
|
||||
} else {
|
||||
throw new Error("no rust files are opened");
|
||||
}
|
||||
} else {
|
||||
// Note: we try to start the server before we activate type hints so that it
|
||||
// registers its `onDidChangeDocument` handler before us.
|
||||
//
|
||||
// This a horribly, horribly wrong way to deal with this problem.
|
||||
ctx = await Ctx.create(config, context, serverPath, { kind: "Workspace Folder", folder: workspaceFolder.uri });
|
||||
ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config));
|
||||
}
|
||||
await initCommonContext(context, ctx);
|
||||
|
||||
// Note: we try to start the server before we activate type hints so that it
|
||||
// registers its `onDidChangeDocument` handler before us.
|
||||
activateInlayHints(ctx);
|
||||
warnAboutExtensionConflicts();
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration(
|
||||
_ => ctx?.client?.sendNotification('workspace/didChangeConfiguration', { settings: "" }),
|
||||
null,
|
||||
ctx.subscriptions,
|
||||
);
|
||||
}
|
||||
|
||||
async function initCommonContext(context: vscode.ExtensionContext, ctx: Ctx) {
|
||||
// Register a "dumb" onEnter command for the case where server fails to
|
||||
// start.
|
||||
//
|
||||
// This a horribly, horribly wrong way to deal with this problem.
|
||||
ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath);
|
||||
// FIXME: refactor command registration code such that commands are
|
||||
// **always** registered, even if the server does not start. Use API like
|
||||
// this perhaps?
|
||||
//
|
||||
// ```TypeScript
|
||||
// registerCommand(
|
||||
// factory: (Ctx) => ((Ctx) => any),
|
||||
// fallback: () => any = () => vscode.window.showErrorMessage(
|
||||
// "rust-analyzer is not available"
|
||||
// ),
|
||||
// )
|
||||
const defaultOnEnter = vscode.commands.registerCommand(
|
||||
'rust-analyzer.onEnter',
|
||||
() => vscode.commands.executeCommand('default:type', { text: '\n' }),
|
||||
);
|
||||
context.subscriptions.push(defaultOnEnter);
|
||||
|
||||
await setContextValue(RUST_PROJECT_CONTEXT_NAME, true);
|
||||
|
||||
@@ -134,17 +152,6 @@ async function tryActivate(context: vscode.ExtensionContext) {
|
||||
ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction);
|
||||
ctx.registerCommand('applyActionGroup', commands.applyActionGroup);
|
||||
ctx.registerCommand('gotoLocation', commands.gotoLocation);
|
||||
|
||||
ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config));
|
||||
|
||||
activateInlayHints(ctx);
|
||||
warnAboutExtensionConflicts();
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration(
|
||||
_ => ctx?.client?.sendNotification('workspace/didChangeConfiguration', { settings: "" }),
|
||||
null,
|
||||
ctx.subscriptions,
|
||||
);
|
||||
}
|
||||
|
||||
export async function deactivate() {
|
||||
|
||||
Reference in New Issue
Block a user