🌐 AI搜索 & 代理 主页
Skip to content
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
191 changes: 111 additions & 80 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- [Code Lenses \(`textDocument/codeLens`\)](#code-lenses-textdocumentcodelens)
- [Inlay hints \(`textDocument/inlayHint`\)](#inlay-hints-textdocumentinlayhint)
- [TypeScript Version Notification](#typescript-version-notification)
- [Workspace Configuration request for formatting settings](#workspace-configuration-request-for-formatting-settings)
- [Development](#development)
- [Build](#build)
- [Dev](#dev)
Expand Down Expand Up @@ -108,89 +109,104 @@ Most of the time, you'll execute commands with arguments retrieved from another

#### Go to Source Definition

- Request:
```ts
{
command: `_typescript.goToSourceDefinition`
arguments: [
lsp.DocumentUri, // String URI of the document
lsp.Position, // Line and character position (zero-based)
]
}
```
- Response:
```ts
lsp.Location[] | null
```
Request:

```ts
{
command: '_typescript.goToSourceDefinition'
arguments: [
lsp.DocumentUri, // String URI of the document
lsp.Position, // Line and character position (zero-based)
]
}
```

Response:

```ts
lsp.Location[] | null
```

(This command is supported from Typescript 4.7.)

#### Apply Refactoring

- Request:
```ts
{
command: `_typescript.applyRefactoring`
arguments: [
tsp.GetEditsForRefactorRequestArgs,
]
}
```
- Response:
```ts
void
```
Request:

```ts
{
command: '_typescript.applyRefactoring'
arguments: [
tsp.GetEditsForRefactorRequestArgs,
]
}
```

Response:

```ts
void
```

#### Organize Imports

- Request:
```ts
{
command: `_typescript.organizeImports`
arguments: [
// The "skipDestructiveCodeActions" argument is supported from Typescript 4.4+
[string] | [string, { skipDestructiveCodeActions?: boolean }],
]
}
```
- Response:
```ts
void
```
Request:

```ts
{
command: '_typescript.organizeImports'
arguments: [
// The "skipDestructiveCodeActions" argument is supported from Typescript 4.4+
[string] | [string, { skipDestructiveCodeActions?: boolean }],
]
}
```

Response:

```ts
void
```

#### Rename File

- Request:
```ts
{
command: `_typescript.applyRenameFile`
arguments: [
{ sourceUri: string; targetUri: string; },
]
}
```
- Response:
```ts
void
```
Request:

```ts
{
command: '_typescript.applyRenameFile'
arguments: [
{ sourceUri: string; targetUri: string; },
]
}
```

Response:

```ts
void
```

#### Send Tsserver Command

- Request:
```ts
{
command: `typescript.tsserverRequest`
arguments: [
string, // command
any, // command arguments in a format that the command expects
ExecuteInfo, // configuration object used for the tsserver request (see below)
]
}
```
- Response:
```ts
any
```
Request:

```ts
{
command: 'typescript.tsserverRequest'
arguments: [
string, // command
any, // command arguments in a format that the command expects
ExecuteInfo, // configuration object used for the tsserver request (see below)
]
}
```

Response:

```ts
any
```

The `ExecuteInfo` object is defined as follows:

Expand All @@ -205,17 +221,20 @@ type ExecuteInfo = {

#### Configure plugin

- Request:
```ts
{
command: `_typescript.configurePlugin`
arguments: [pluginName: string, configuration: any]
}
```
- Response:
```ts
void
```
Request:

```ts
{
command: '_typescript.configurePlugin'
arguments: [pluginName: string, configuration: any]
}
```

Response:

```ts
void
```

### Code Lenses (`textDocument/codeLens`)

Expand Down Expand Up @@ -275,6 +294,18 @@ The `$/typescriptVersion` notification params include two properties:
- `version` - a semantic version (for example `4.8.4`)
- `source` - a string specifying whether used TypeScript version comes from the local workspace (`workspace`), is explicitly specified through a `initializationOptions.tsserver.path` setting (`user-setting`) or was bundled with the server (`bundled`)


### Workspace Configuration request for formatting settings

Server asks the client for file-specific configuration options (`tabSize` and `insertSpaces`) that are required by `tsserver` to properly format the file edits when for example using "Organize imports" or performing other file modifications. Those options have to be dynamically provided by the client/editor since the values can differ for each file. For this reason server sends a `workspace/configuration` request with `scopeUri` equal to file's URI and `section` equal to `formattingOptions`. The client is expected to return a configuration that includes the following properties:

```js
{
"tabSize": number
"insertSpaces": boolean
}
```

## Development

### Build
Expand Down
20 changes: 16 additions & 4 deletions src/features/fileConfigurationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CommandTypes, ModuleKind, ScriptTarget, type ts, type TypeScriptInitial
import { ITypeScriptServiceClient } from '../typescriptService.js';
import { isTypeScriptDocument } from '../configuration/languageIds.js';
import { LspDocument } from '../document.js';
import type { LspClient } from '../lsp-client.js';
import API from '../utils/api.js';
import { equals } from '../utils/objects.js';
import { ResourceMap } from '../utils/resourceMap.js';
Expand Down Expand Up @@ -147,6 +148,7 @@ export default class FileConfigurationManager {

public constructor(
private readonly client: ITypeScriptServiceClient,
private readonly lspClient: LspClient,
onCaseInsensitiveFileSystem: boolean,
) {
this.formatOptions = new ResourceMap(undefined, { onCaseInsensitiveFileSystem });
Expand Down Expand Up @@ -208,12 +210,22 @@ export default class FileConfigurationManager {
document: LspDocument,
token?: lsp.CancellationToken,
): Promise<void> {
return this.ensureConfigurationOptions(document, undefined, token);
const formattingOptions = await this.getFormattingOptions(document);
return this.ensureConfigurationOptions(document, formattingOptions, token);
}

private async getFormattingOptions(document: LspDocument): Promise<Partial<lsp.FormattingOptions>> {
const formatConfiguration = await this.lspClient.getWorkspaceConfiguration<Partial<lsp.FormattingOptions> | undefined>(document.uri.toString(), 'formattingOptions') || {};

return {
tabSize: typeof formatConfiguration.tabSize === 'number' ? formatConfiguration.tabSize : undefined,
insertSpaces: typeof formatConfiguration.insertSpaces === 'boolean' ? formatConfiguration.insertSpaces : undefined,
};
}

public async ensureConfigurationOptions(
document: LspDocument,
options?: lsp.FormattingOptions,
options?: Partial<lsp.FormattingOptions>,
token?: lsp.CancellationToken,
): Promise<void> {
const currentOptions = this.getFileOptions(document, options);
Expand Down Expand Up @@ -260,7 +272,7 @@ export default class FileConfigurationManager {

private getFileOptions(
document: LspDocument,
options?: lsp.FormattingOptions,
options?: Partial<lsp.FormattingOptions>,
): FileConfiguration {
return {
formatOptions: this.getFormatOptions(document, options),
Expand All @@ -270,7 +282,7 @@ export default class FileConfigurationManager {

private getFormatOptions(
document: LspDocument,
formattingOptions?: lsp.FormattingOptions,
formattingOptions?: Partial<lsp.FormattingOptions>,
): ts.server.protocol.FormatCodeSettings {
const workspacePreferences = this.getWorkspacePreferencesForFile(document);

Expand Down
5 changes: 5 additions & 0 deletions src/lsp-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface LspClient {
applyWorkspaceEdit(args: lsp.ApplyWorkspaceEditParams): Promise<lsp.ApplyWorkspaceEditResult>;
rename(args: lsp.TextDocumentPositionParams): Promise<any>;
sendNotification<P>(type: lsp.NotificationType<P>, params: P): Promise<void>;
getWorkspaceConfiguration<R = unknown>(scopeUri: string, section: string): Promise<R>;
}

// Hack around the LSP library that makes it otherwise impossible to differentiate between Null and Client-initiated reporter.
Expand Down Expand Up @@ -66,6 +67,10 @@ export class LspClientImpl implements LspClient {
this.connection.sendNotification(lsp.LogMessageNotification.type, args);
}

async getWorkspaceConfiguration<R = unknown>(scopeUri: string, section: string): Promise<R> {
return await this.connection.workspace.getConfiguration({ scopeUri, section }) as R;
}

async applyWorkspaceEdit(params: lsp.ApplyWorkspaceEditParams): Promise<lsp.ApplyWorkspaceEditResult> {
return this.connection.workspace.applyEdit(params);
}
Expand Down
24 changes: 14 additions & 10 deletions src/lsp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class LspServer {
constructor(private options: LspServerConfiguration) {
this.logger = new PrefixingLogger(options.logger, '[lspserver]');
this.tsClient = new TsClient(onCaseInsensitiveFileSystem(), this.logger, options.lspClient);
this.fileConfigurationManager = new FileConfigurationManager(this.tsClient, onCaseInsensitiveFileSystem());
this.fileConfigurationManager = new FileConfigurationManager(this.tsClient, this.options.lspClient, onCaseInsensitiveFileSystem());
this.commandManager = new CommandManager();
this.diagnosticsManager = new DiagnosticsManager(
diagnostics => this.options.lspClient.publishDiagnostics(diagnostics),
Expand Down Expand Up @@ -816,15 +816,19 @@ export class LspServer {
skipDestructiveCodeActions = documentHasErrors;
mode = OrganizeImportsMode.SortAndCombine;
}
const response = await this.tsClient.interruptGetErr(() => this.tsClient.execute(
CommandTypes.OrganizeImports,
{
scope: { type: 'file', args: fileRangeArgs },
// Deprecated in 4.9; `mode` takes priority.
skipDestructiveCodeActions,
mode,
},
token));
const response = await this.tsClient.interruptGetErr(async () => {
await this.fileConfigurationManager.ensureConfigurationForDocument(document, token);

return this.tsClient.execute(
CommandTypes.OrganizeImports,
{
scope: { type: 'file', args: fileRangeArgs },
// Deprecated in 4.9; `mode` takes priority.
skipDestructiveCodeActions,
mode,
},
token);
});
if (response.type === 'response' && response.body) {
actions.push(...provideOrganizeImports(command, response, this.tsClient));
}
Expand Down
4 changes: 4 additions & 0 deletions src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ export class TestLspClient implements LspClient {
sendNotification<P>(_type: lsp.NotificationType<P>, _params: P): Promise<void> {
throw new Error('unsupported');
}

async getWorkspaceConfiguration(_scopeUri: string, _section: string): Promise<any> {
return Promise.resolve(undefined);
}
}

export class TestLspServer extends LspServer {
Expand Down