@mrclrchtr/supi-lsp 7.4.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -6
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/src/query-result.ts +22 -4
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +110 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +6 -1
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +2 -1
- package/package.json +3 -3
- package/src/api.ts +25 -3
- package/src/client/client-diagnostic-host.ts +2 -1
- package/src/client/client-diagnostic-refresh.ts +5 -2
- package/src/client/client-diagnostics.ts +39 -6
- package/src/client/client-document-sync.ts +82 -12
- package/src/client/client-semantic-input-barrier.ts +28 -5
- package/src/client/client.ts +606 -30
- package/src/config/capabilities.ts +9 -0
- package/src/config/config.ts +69 -8
- package/src/config/operation-support.ts +196 -0
- package/src/config/server-config.ts +15 -0
- package/src/config/types.ts +9 -1
- package/src/manager/manager-project-info.ts +2 -0
- package/src/manager/manager-workspace-symbol.ts +31 -2
- package/src/manager/manager.ts +72 -2
- package/src/provider/lsp-refactor-provider.ts +9 -43
- package/src/provider/refactor-planning.ts +102 -50
- package/src/provider/semantic-edit-normalizer.ts +49 -3
- package/src/session/runtime-controller.ts +78 -18
- package/src/session/runtime-registry.ts +46 -2
- package/src/session/workspace-lsp-runtime.ts +14 -0
- package/src/workspace-path-policy.ts +30 -4
- package/src/config/lsp-settings.ts +0 -22
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ SuPi advertises static and dynamic pull support. It advertises server-requested
|
|
|
46
46
|
|
|
47
47
|
A server gets pull diagnostics when it declares a valid `diagnosticProvider` during initialization. A server also gets pull diagnostics after it registers `textDocument/diagnostic`. The pull support stays active until the server removes the registration. SuPi validates registration parameters. Invalid parameters do not enable pull support. SuPi ignores other registration methods.
|
|
48
48
|
|
|
49
|
-
When a server sends `workspace/diagnostic/refresh`, SuPi returns `null` immediately. It then invalidates diagnostic evidence only and refreshes the owning client's tracked documents in the background. Unchanged open documents keep their input synchronization; SuPi sends no no-op `didChange`, `didClose`, or `didOpen`. It uses native pull or the tested TypeScript request adapter when available. Push-only routes keep partial or unconfirmed evidence. Actual disk changes use the normal document synchronization path. Overlapping refresh requests share one active pass and one newer pending demand; an active diagnostic transport stays owned until it settles. The refresh covers open, cached, and failed tracked documents. SuPi does not add workspace-wide `workspace/diagnostic` pulls.
|
|
49
|
+
When a server sends `workspace/diagnostic/refresh`, SuPi returns `null` immediately. It then invalidates diagnostic evidence only and refreshes the owning client's tracked documents in the background. Unchanged open documents keep their input synchronization; SuPi sends no no-op `didChange`, `didClose`, or `didOpen`. It uses native pull or the tested TypeScript request adapter when available. Push-only routes keep partial or unconfirmed evidence. Actual disk changes use the normal document synchronization path, followed by one `didSave` when the server requested saves; repeated observations of the same content send no duplicate save. Overlapping refresh requests share one active pass and one newer pending demand; an active diagnostic transport stays owned until it settles. The refresh covers open, cached, and failed tracked documents. SuPi does not add workspace-wide `workspace/diagnostic` pulls.
|
|
50
50
|
|
|
51
51
|
Protocol support is separate from the configured mode. A server may support pull diagnostics and still use SuPi's push mode because the built-in configuration does not enable pull mode.
|
|
52
52
|
|
|
@@ -108,25 +108,27 @@ Gopls pull diagnostics stay opt-in while golang/go#70199 is open; without the op
|
|
|
108
108
|
|
|
109
109
|
Automatic LSP work uses one path policy that does not change for each workspace runtime. It covers project discovery, route startup, warm-up, sentinel and source-file lists, created-file tracking, runtime guidance, and diagnostic summaries not tied to one request.
|
|
110
110
|
|
|
111
|
-
The policy excludes these directories by default: `.git`, `.cache`, `.pi`, `.pnpm`, `node_modules`, `dist`, `build`, `out`, `coverage`, `.next`, `.nuxt`, `.turbo`, and `__pycache__`. It also applies `
|
|
111
|
+
The policy excludes these directories by default: `.git`, `.cache`, `.pi`, `.pnpm`, `node_modules`, `dist`, `build`, `out`, `coverage`, `.next`, `.nuxt`, `.turbo`, and `__pycache__`. It also applies `code-intelligence.exclude` patterns and root or nested `.gitignore` rules. Patterns use gitignore syntax, including rules relative to each directory and `!` rules. Built-in exclusions cannot be enabled again. Symbolic-link directories are not visited. Other dot-directories, such as `.github` and `.storybook`, remain allowed.
|
|
112
112
|
|
|
113
|
-
Set `
|
|
113
|
+
Set `code-intelligence.exclude` in project or global SuPi configuration. A project list replaces the global list; `[]` clears configured patterns. Use `/supi-settings` to edit this shared list when the settings extension is installed. Reload or restart Pi after saving:
|
|
114
114
|
|
|
115
115
|
```json
|
|
116
116
|
{
|
|
117
|
-
"
|
|
117
|
+
"code-intelligence": {
|
|
118
118
|
"exclude": ["generated/**", "!generated/keep.ts"]
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
An old `lsp.exclude` value is a migration error. Move it to `code-intelligence.exclude`; SuPi does not edit personal configuration for you.
|
|
124
|
+
|
|
123
125
|
An exact semantic request can still route an excluded file when a compatible server is available. This does not add the file to automatic work. Configured diagnostic suppression still applies to diagnostic output.
|
|
124
126
|
|
|
125
127
|
Tsconfig and jsconfig filtering applies only to TypeScript and JavaScript-family files (`.ts`, `.tsx`, `.mts`, `.cts`, `.js`, `.jsx`, `.mjs`, and `.cjs`). Other-language diagnostics are not filtered by a nearby TypeScript or JavaScript project config. Automatic path exclusions and configured diagnostic suppression still apply. A single-file health result omits the `Tsconfig` coverage line for other languages.
|
|
126
128
|
|
|
127
129
|
### Custom server configuration
|
|
128
130
|
|
|
129
|
-
A custom server needs a command and at least one file type:
|
|
131
|
+
A custom server needs a command and at least one file type. Advanced server settings are JSON objects. They replace inherited settings as a whole; `initializationOptions` remains a separate initialize value:
|
|
130
132
|
|
|
131
133
|
```json
|
|
132
134
|
{
|
|
@@ -137,6 +139,7 @@ A custom server needs a command and at least one file type:
|
|
|
137
139
|
"args": ["--stdio"],
|
|
138
140
|
"fileTypes": ["custom"],
|
|
139
141
|
"env": { "CUSTOM_LSP_LOG": "debug" },
|
|
142
|
+
"settings": { "custom": { "mode": "project" } },
|
|
140
143
|
"initializationOptions": { "mode": "project" }
|
|
141
144
|
}
|
|
142
145
|
}
|
|
@@ -144,6 +147,14 @@ A custom server needs a command and at least one file type:
|
|
|
144
147
|
}
|
|
145
148
|
```
|
|
146
149
|
|
|
150
|
+
Server settings use one snapshot for the active workspace runtime:
|
|
151
|
+
|
|
152
|
+
- An omitted `settings` field inherits the earlier object. `settings: {}` clears inherited settings.
|
|
153
|
+
- `workspace/configuration` reads that snapshot. A request without a section receives the complete object; an unknown section receives `null`.
|
|
154
|
+
- A configured snapshot is also sent through `workspace/didChangeConfiguration` after initialization.
|
|
155
|
+
- SuPi validates the JSON object shape, not server-specific setting names or values.
|
|
156
|
+
- Saved changes apply after reload or restart. A saved value does not prove that the server accepted it.
|
|
157
|
+
|
|
147
158
|
File types do not include a leading dot. If `rootMarkers` is omitted, the server uses the session root.
|
|
148
159
|
|
|
149
160
|
Disable one language with `lsp.servers.<language>.enabled: false`. Use `/supi-ci-status` from `@mrclrchtr/supi-code-intelligence` to see detected, running, and missing servers.
|
|
@@ -155,6 +166,18 @@ Disable one language with `lsp.servers.<language>.enabled: false`. Use `/supi-ci
|
|
|
155
166
|
- explicit ready, pending, inactive, disabled, and unavailable registry states
|
|
156
167
|
- a `SemanticProvider` adapter for `supi-code-runtime`
|
|
157
168
|
- precise rename and code-action edit conversion
|
|
169
|
+
- operation-specific code-action kind filters and standard lazy edit resolution
|
|
170
|
+
- negotiated operation support in project-server status
|
|
171
|
+
|
|
172
|
+
Operation-specific planners request `source.organizeImports` for import updates and
|
|
173
|
+
`source.removeUnused` for dead-code removal. They match the returned `CodeAction.kind`,
|
|
174
|
+
not its title. Edit-only actions are validated before planning. Command actions,
|
|
175
|
+
resource operations, snippets, annotations, malformed actions, and stale responses
|
|
176
|
+
stay unavailable. Distinct matching plans return an ambiguous result instead of
|
|
177
|
+
selecting the first action. Rename uses `prepareRename` when the server advertises
|
|
178
|
+
it and otherwise uses direct standard rename. Standard LSP does not provide
|
|
179
|
+
a safe exact extraction-name contract, so `extract_function` and `extract_variable`
|
|
180
|
+
plans report a SuPi planning limitation instead of selecting a generated name.
|
|
158
181
|
|
|
159
182
|
Clients, `LspManager`, and the default runtime implementation remain internal.
|
|
160
183
|
|
|
@@ -164,7 +187,7 @@ Clients, `LspManager`, and the default runtime implementation remain internal.
|
|
|
164
187
|
|
|
165
188
|
- language-server detection and startup
|
|
166
189
|
- shutdown
|
|
167
|
-
-
|
|
190
|
+
- server configuration and missing-server inventory
|
|
168
191
|
- publishing workspace runtime state and aggregate lifecycle transitions
|
|
169
192
|
- projecting concrete client readiness into semantic capability state
|
|
170
193
|
|
|
@@ -14,7 +14,7 @@ export type {
|
|
|
14
14
|
StructuralProvider,
|
|
15
15
|
StructuralResult,
|
|
16
16
|
} from "./capability/types.ts";
|
|
17
|
-
export type { CodeQueryResult } from "./query-result.ts";
|
|
17
|
+
export type { CodeQueryPartialMetadata, CodeQueryResult } from "./query-result.ts";
|
|
18
18
|
// Shared query-result constructors and contract
|
|
19
19
|
export {
|
|
20
20
|
completedCodeQuery,
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/** Structured metadata for a partial query result. */
|
|
2
|
+
export interface CodeQueryPartialMetadata {
|
|
3
|
+
/** Automatic path-policy evidence removed from a collected result. */
|
|
4
|
+
readonly automaticPathExclusion?: {
|
|
5
|
+
readonly excludedCount: number;
|
|
6
|
+
readonly examples: readonly string[];
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
11
|
* Result of a read-only code-provider query.
|
|
3
12
|
*
|
|
@@ -8,7 +17,12 @@
|
|
|
8
17
|
*/
|
|
9
18
|
export type CodeQueryResult<T> =
|
|
10
19
|
| { readonly kind: "completed"; readonly data: T }
|
|
11
|
-
| {
|
|
20
|
+
| {
|
|
21
|
+
readonly kind: "partial";
|
|
22
|
+
readonly data: T;
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
readonly metadata?: CodeQueryPartialMetadata;
|
|
25
|
+
}
|
|
12
26
|
| { readonly kind: "unavailable"; readonly reason: string };
|
|
13
27
|
|
|
14
28
|
/** Construct a successfully completed code-query result, including empty data. */
|
|
@@ -17,8 +31,12 @@ export function completedCodeQuery<T>(data: T): CodeQueryResult<T> {
|
|
|
17
31
|
}
|
|
18
32
|
|
|
19
33
|
/** Construct a usable but incomplete code-query result. */
|
|
20
|
-
export function partialCodeQuery<T>(
|
|
21
|
-
|
|
34
|
+
export function partialCodeQuery<T>(
|
|
35
|
+
data: T,
|
|
36
|
+
reason: string,
|
|
37
|
+
metadata?: CodeQueryPartialMetadata,
|
|
38
|
+
): CodeQueryResult<T> {
|
|
39
|
+
return { kind: "partial", data, reason, ...(metadata ? { metadata } : {}) };
|
|
22
40
|
}
|
|
23
41
|
|
|
24
42
|
/** Construct a code-query result that could not be established. */
|
|
@@ -35,7 +53,7 @@ export function mapCodeQueryResult<T, U>(
|
|
|
35
53
|
case "completed":
|
|
36
54
|
return completedCodeQuery(map(result.data));
|
|
37
55
|
case "partial":
|
|
38
|
-
return partialCodeQuery(map(result.data), result.reason);
|
|
56
|
+
return partialCodeQuery(map(result.data), result.reason, result.metadata);
|
|
39
57
|
case "unavailable":
|
|
40
58
|
return unavailableCodeQuery(result.reason);
|
|
41
59
|
}
|
|
@@ -70,6 +70,116 @@ export interface SupiConfigOptions {
|
|
|
70
70
|
homeDir?: string;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/** Options for loading automatic code-intelligence exclusions. */
|
|
74
|
+
export interface AutomaticExclusionOptions extends SupiConfigOptions {
|
|
75
|
+
/** Whether the project config is trusted and can apply. Defaults to true. */
|
|
76
|
+
projectTrusted?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Load the one configured exclusion list shared by automatic LSP work and
|
|
81
|
+
* broad AST searches.
|
|
82
|
+
*
|
|
83
|
+
* Project values replace global values. An explicit empty project list clears
|
|
84
|
+
* the global list. The old `lsp.exclude` key is rejected in every applicable
|
|
85
|
+
* scope so automatic work cannot start without its former protection.
|
|
86
|
+
*/
|
|
87
|
+
export function loadAutomaticExclusionPatterns(
|
|
88
|
+
cwd: string,
|
|
89
|
+
options: AutomaticExclusionOptions = {},
|
|
90
|
+
): readonly string[] {
|
|
91
|
+
const projectTrusted = options.projectTrusted !== false;
|
|
92
|
+
const globalCodeIntelligence = loadSupiConfigSectionForScope("code-intelligence", cwd, {
|
|
93
|
+
scope: "global",
|
|
94
|
+
homeDir: options.homeDir,
|
|
95
|
+
});
|
|
96
|
+
const projectCodeIntelligence = projectTrusted
|
|
97
|
+
? loadSupiConfigSectionForScope("code-intelligence", cwd, { scope: "project" })
|
|
98
|
+
: null;
|
|
99
|
+
const globalLsp = loadSupiConfigSectionForScope("lsp", cwd, {
|
|
100
|
+
scope: "global",
|
|
101
|
+
homeDir: options.homeDir,
|
|
102
|
+
});
|
|
103
|
+
const projectLsp = projectTrusted
|
|
104
|
+
? loadSupiConfigSectionForScope("lsp", cwd, { scope: "project" })
|
|
105
|
+
: null;
|
|
106
|
+
|
|
107
|
+
const legacyPath = findLegacyExclusionPath(cwd, options, globalLsp, projectLsp);
|
|
108
|
+
if (legacyPath) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Unsupported ${legacyPath.scope} configuration at ${legacyPath.path}: "lsp.exclude" was removed. Move its value to "code-intelligence.exclude". Automatic LSP and broad AST work did not start; reload after migration.`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const source = findExclusionSource(cwd, options, {
|
|
115
|
+
projectTrusted,
|
|
116
|
+
globalCodeIntelligence,
|
|
117
|
+
projectCodeIntelligence,
|
|
118
|
+
});
|
|
119
|
+
return validateExclusionSource(source);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface ExclusionSource {
|
|
123
|
+
value: unknown;
|
|
124
|
+
path: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface LegacyExclusionPath {
|
|
128
|
+
scope: "global" | "project";
|
|
129
|
+
path: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function findLegacyExclusionPath(
|
|
133
|
+
cwd: string,
|
|
134
|
+
options: AutomaticExclusionOptions,
|
|
135
|
+
globalLsp: Record<string, unknown> | null,
|
|
136
|
+
projectLsp: Record<string, unknown> | null,
|
|
137
|
+
): LegacyExclusionPath | null {
|
|
138
|
+
if (globalLsp && Object.hasOwn(globalLsp, "exclude")) {
|
|
139
|
+
return { scope: "global", path: getSupiConfigPath("global", cwd, options) };
|
|
140
|
+
}
|
|
141
|
+
if (projectLsp && Object.hasOwn(projectLsp, "exclude")) {
|
|
142
|
+
return { scope: "project", path: getSupiConfigPath("project", cwd) };
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function findExclusionSource(
|
|
148
|
+
cwd: string,
|
|
149
|
+
options: AutomaticExclusionOptions,
|
|
150
|
+
sections: {
|
|
151
|
+
projectTrusted: boolean;
|
|
152
|
+
globalCodeIntelligence: Record<string, unknown> | null;
|
|
153
|
+
projectCodeIntelligence: Record<string, unknown> | null;
|
|
154
|
+
},
|
|
155
|
+
): ExclusionSource | null {
|
|
156
|
+
const { projectTrusted, globalCodeIntelligence, projectCodeIntelligence } = sections;
|
|
157
|
+
if (
|
|
158
|
+
projectTrusted &&
|
|
159
|
+
projectCodeIntelligence &&
|
|
160
|
+
Object.hasOwn(projectCodeIntelligence, "exclude")
|
|
161
|
+
) {
|
|
162
|
+
return { value: projectCodeIntelligence.exclude, path: getSupiConfigPath("project", cwd) };
|
|
163
|
+
}
|
|
164
|
+
if (globalCodeIntelligence && Object.hasOwn(globalCodeIntelligence, "exclude")) {
|
|
165
|
+
return {
|
|
166
|
+
value: globalCodeIntelligence.exclude,
|
|
167
|
+
path: getSupiConfigPath("global", cwd, options),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function validateExclusionSource(source: ExclusionSource | null): readonly string[] {
|
|
174
|
+
if (!source) return Object.freeze([]);
|
|
175
|
+
if (!Array.isArray(source.value) || !source.value.every((entry) => typeof entry === "string")) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Invalid "code-intelligence.exclude" in ${source.path}: expected an array of gitignore-style strings. Automatic LSP and broad AST work did not start.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return Object.freeze([...source.value]);
|
|
181
|
+
}
|
|
182
|
+
|
|
73
183
|
/**
|
|
74
184
|
* Load and merge config for a given extension section.
|
|
75
185
|
*
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
// supi-core config domain — config loading.
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
AutomaticExclusionOptions,
|
|
4
|
+
SupiConfigLocation,
|
|
5
|
+
SupiConfigOptions,
|
|
6
|
+
} from "./config/config.ts";
|
|
3
7
|
export {
|
|
4
8
|
getSupiConfigPath,
|
|
9
|
+
loadAutomaticExclusionPatterns,
|
|
5
10
|
loadSupiConfig,
|
|
6
11
|
loadSupiConfigForScope,
|
|
7
12
|
loadSupiConfigSectionForScope,
|
|
@@ -151,6 +151,7 @@ export interface CustomField extends BaseField {
|
|
|
151
151
|
cwd: string,
|
|
152
152
|
action: SettingsAction,
|
|
153
153
|
helpers: ConfigHelpers,
|
|
154
|
+
ctx?: ExtensionContext,
|
|
154
155
|
) => void | Promise<void>;
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -404,7 +405,7 @@ async function applyConfigAction(
|
|
|
404
405
|
const helpers = createConfigHelpers(options.section, scope, cwd, options.homeDir);
|
|
405
406
|
let storedValue: unknown;
|
|
406
407
|
if (field.kind === "custom") {
|
|
407
|
-
await field.persist(scope, cwd, action, helpers);
|
|
408
|
+
await field.persist(scope, cwd, action, helpers, ctx);
|
|
408
409
|
storedValue = action.kind === "set" ? action.value : undefined;
|
|
409
410
|
} else if (action.kind === "set") {
|
|
410
411
|
storedValue = parseTypedValue(action.value, field);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-lsp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Language Server Protocol runtime for SuPi code intelligence",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"vscode-jsonrpc": "^9.0.0",
|
|
38
38
|
"vscode-languageserver-protocol": "^3.18.0",
|
|
39
39
|
"vscode-languageserver-types": "^3.18.0",
|
|
40
|
-
"@mrclrchtr/supi-
|
|
41
|
-
"@mrclrchtr/supi-
|
|
40
|
+
"@mrclrchtr/supi-code-runtime": "8.0.0",
|
|
41
|
+
"@mrclrchtr/supi-core": "8.0.0"
|
|
42
42
|
},
|
|
43
43
|
"bundledDependencies": [
|
|
44
44
|
"@mrclrchtr/supi-code-runtime",
|
package/src/api.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
// Public API surface for the LSP session-scoped service.
|
|
2
2
|
|
|
3
3
|
export { isMissingFileError } from "./client/client-file-state.ts";
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export {
|
|
5
|
+
getExplicitlyDisabledLanguages,
|
|
6
|
+
type LoadConfigOptions,
|
|
7
|
+
loadConfig,
|
|
8
|
+
} from "./config/config.ts";
|
|
9
|
+
export {
|
|
10
|
+
codeActionKindsForOperation,
|
|
11
|
+
getServerOperationSupport,
|
|
12
|
+
isCodeActionKindInRange,
|
|
13
|
+
REFACTOR_CODE_ACTION_KINDS,
|
|
14
|
+
supportsCodeActionResolve,
|
|
15
|
+
supportsCodeActions,
|
|
16
|
+
supportsPrepareRename,
|
|
17
|
+
supportsRename,
|
|
18
|
+
supportsRequestedCodeActionKinds,
|
|
19
|
+
} from "./config/operation-support.ts";
|
|
7
20
|
export {
|
|
8
21
|
clearTsconfigCache,
|
|
9
22
|
type FileScopeDecision,
|
|
@@ -21,6 +34,8 @@ export type {
|
|
|
21
34
|
DocumentSymbol,
|
|
22
35
|
FileEvent,
|
|
23
36
|
Hover,
|
|
37
|
+
JsonObject,
|
|
38
|
+
JsonValue,
|
|
24
39
|
Location,
|
|
25
40
|
LocationLink,
|
|
26
41
|
LspConfig,
|
|
@@ -29,6 +44,10 @@ export type {
|
|
|
29
44
|
ProjectServerInfo,
|
|
30
45
|
ProjectServerStatusReason,
|
|
31
46
|
Range,
|
|
47
|
+
ServerAdvertisement,
|
|
48
|
+
ServerOperationName,
|
|
49
|
+
ServerOperationSupport,
|
|
50
|
+
ServerOperationSupportMap,
|
|
32
51
|
SymbolInformation,
|
|
33
52
|
WorkspaceEdit,
|
|
34
53
|
WorkspaceSymbol,
|
|
@@ -52,6 +71,7 @@ export {
|
|
|
52
71
|
export { raceReadinessValue, raceRequestControl } from "./session/readiness.ts";
|
|
53
72
|
export type {
|
|
54
73
|
LspControllerState,
|
|
74
|
+
LspRuntimeControllerOptions,
|
|
55
75
|
LspRuntimeTransition,
|
|
56
76
|
LspRuntimeTransitionKind,
|
|
57
77
|
LspRuntimeTransitionListener,
|
|
@@ -77,6 +97,7 @@ export type {
|
|
|
77
97
|
StartupRetryNextAction,
|
|
78
98
|
StartupRetryOutcome,
|
|
79
99
|
StartupRetryReport,
|
|
100
|
+
WorkspaceCodeActionRequestOptions,
|
|
80
101
|
WorkspaceDiagnosticReport,
|
|
81
102
|
WorkspaceDiagnosticSnapshot,
|
|
82
103
|
WorkspaceDiagnosticSummaryEntry,
|
|
@@ -98,6 +119,7 @@ export { MAX_BULK_TRACK_FILES } from "./session/workspace-lsp-runtime.ts";
|
|
|
98
119
|
export {
|
|
99
120
|
AUTOMATIC_LSP_EXCLUDED_DIRECTORIES,
|
|
100
121
|
type AutomaticLspPathPolicy,
|
|
122
|
+
type AutomaticLspPathPolicyOptions,
|
|
101
123
|
createAutomaticLspPathPolicy,
|
|
102
124
|
createDefaultAutomaticLspPathPolicy,
|
|
103
125
|
} from "./workspace-path-policy.ts";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DiagnosticRequestAdapter } from "./client-diagnostic-request.ts";
|
|
2
|
+
import type { NormalizedDocumentSync } from "./client-document-sync.ts";
|
|
2
3
|
|
|
3
4
|
/** Transport and capability operations required by diagnostic state. */
|
|
4
5
|
export interface ClientDiagnosticsHost {
|
|
@@ -9,6 +10,6 @@ export interface ClientDiagnosticsHost {
|
|
|
9
10
|
isOperational(): boolean;
|
|
10
11
|
/** Native pull and server-specific request evidence share this priority adapter. */
|
|
11
12
|
diagnosticRequestAdapter: DiagnosticRequestAdapter;
|
|
12
|
-
|
|
13
|
+
documentSync(): NormalizedDocumentSync;
|
|
13
14
|
sendNotification(method: string, params: unknown): void;
|
|
14
15
|
}
|
|
@@ -289,7 +289,9 @@ interface ClientDiagnosticRefreshOptions {
|
|
|
289
289
|
/** Invalidate route evidence before applying a disk content change. */
|
|
290
290
|
readonly noteInputContentChange: () => number;
|
|
291
291
|
/** Keep the shared semantic barrier's verified disk observation current. */
|
|
292
|
-
readonly observeDiskContent: (uri: string, content: string) =>
|
|
292
|
+
readonly observeDiskContent: (uri: string, content: string) => boolean;
|
|
293
|
+
/** Send one save for an externally observed disk change. */
|
|
294
|
+
readonly noteExternalDiskChange: (uri: string, content: string) => void;
|
|
293
295
|
readonly clearFile: (uri: string) => void;
|
|
294
296
|
readonly invalidateEvidence: (uri: string) => void;
|
|
295
297
|
readonly markUnversionedSyncMoment: (uri: string) => void;
|
|
@@ -343,9 +345,10 @@ function prepareRefreshDocuments(
|
|
|
343
345
|
nextVersion: (uri) => nextDocumentVersion(options.versionHistory, uri),
|
|
344
346
|
nextSynchronizationId: options.nextSynchronizationId,
|
|
345
347
|
evidenceRevision,
|
|
348
|
+
documentSync: options.host.documentSync(),
|
|
346
349
|
noteInputContentChange: options.noteInputContentChange,
|
|
347
350
|
observeDiskContent: options.observeDiskContent,
|
|
348
|
-
|
|
351
|
+
noteExternalDiskChange: options.noteExternalDiskChange,
|
|
349
352
|
sendNotification: (method, params) => options.host.sendNotification(method, params),
|
|
350
353
|
uriToFile,
|
|
351
354
|
preloadedContent: classification.preloadedContent,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// biome-ignore-all lint/style/noExcessiveLinesPerFile: one client's document sync, diagnostics, refresh, and sync-file flow stay in one cohesive class.
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import {
|
|
4
5
|
type CodeQueryResult,
|
|
@@ -116,6 +117,8 @@ export class ClientDiagnostics {
|
|
|
116
117
|
isOperational: () => this.host.isOperational(),
|
|
117
118
|
getOpenDocuments: () => this.#getSemanticInputDocuments(),
|
|
118
119
|
applyDocumentUpdates: (updates) => this.#applySemanticInputUpdates(updates),
|
|
120
|
+
noteExternalDiskChange: (filePath, content) =>
|
|
121
|
+
this.#noteExternalDiskChange(filePath, content),
|
|
119
122
|
closeMissingDocument: (filePath) => this.didClose(filePath),
|
|
120
123
|
markUnreadableDocument: (filePath) => this.markFailedFile(filePath),
|
|
121
124
|
});
|
|
@@ -200,7 +203,7 @@ export class ClientDiagnostics {
|
|
|
200
203
|
nextVersion: () => nextDocumentVersion(this.#versionHistory, uri),
|
|
201
204
|
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
202
205
|
evidenceRevision: this.#evidenceRevision,
|
|
203
|
-
|
|
206
|
+
documentSync: this.host.documentSync(),
|
|
204
207
|
waiters: this.#waiters,
|
|
205
208
|
sendNotification: (method, params) => this.host.sendNotification(method, params),
|
|
206
209
|
markUnversionedSyncMoment: () => this.#unversionedPushSyncMoments.set(uri, Date.now()),
|
|
@@ -213,10 +216,6 @@ export class ClientDiagnostics {
|
|
|
213
216
|
this.#inputBarrier.initializeDocumentContent(uri, content);
|
|
214
217
|
}
|
|
215
218
|
|
|
216
|
-
#observeDiskContent(uri: string, content: string): void {
|
|
217
|
-
this.#inputBarrier.observeDiskContent(uri, content);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
219
|
#forgetDocumentContent(uri: string): void {
|
|
221
220
|
this.#inputBarrier.forgetDocumentContent(uri);
|
|
222
221
|
}
|
|
@@ -333,6 +332,38 @@ export class ClientDiagnostics {
|
|
|
333
332
|
this.#synchronizeTrackedDocument(uri, filePath, content, doc);
|
|
334
333
|
}
|
|
335
334
|
|
|
335
|
+
/** Send a save after a disk change that the input barrier observed. */
|
|
336
|
+
#noteExternalDiskChange(filePath: string, content: string): void {
|
|
337
|
+
if (!this.host.isOperational()) return;
|
|
338
|
+
const documentSync = this.host.documentSync();
|
|
339
|
+
if (!documentSync.save) return;
|
|
340
|
+
this.host.sendNotification("textDocument/didSave", {
|
|
341
|
+
textDocument: { uri: fileToUri(filePath) },
|
|
342
|
+
...(documentSync.includeText ? { text: content } : {}),
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Synchronize and notify one tracked document after an observed disk write. */
|
|
347
|
+
noteWorkspaceWrite(filePath: string): void {
|
|
348
|
+
if (!this.host.isOperational()) return;
|
|
349
|
+
|
|
350
|
+
const uri = fileToUri(filePath);
|
|
351
|
+
const document = this.#openDocs.get(uri);
|
|
352
|
+
if (!document) return;
|
|
353
|
+
|
|
354
|
+
let content: string;
|
|
355
|
+
try {
|
|
356
|
+
content = readFileSync(filePath, "utf-8");
|
|
357
|
+
} catch {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const diskChanged = this.#inputBarrier.observeDiskContent(uri, content);
|
|
361
|
+
if (document.contentFingerprint !== fingerprintDocumentContent(content)) {
|
|
362
|
+
this.didChange(filePath, content);
|
|
363
|
+
}
|
|
364
|
+
if (diskChanged) this.#noteExternalDiskChange(filePath, content);
|
|
365
|
+
}
|
|
366
|
+
|
|
336
367
|
didClose(filePath: string): void {
|
|
337
368
|
const uri = fileToUri(filePath);
|
|
338
369
|
const wasOpen = this.#openDocs.has(uri);
|
|
@@ -527,7 +558,9 @@ export class ClientDiagnostics {
|
|
|
527
558
|
isRelatedUriTracked: (uri) => this.#openDocs.has(uri) || this.#versionHistory.has(uri),
|
|
528
559
|
nextSynchronizationId: () => ++this.#nextSynchronizationId,
|
|
529
560
|
noteInputContentChange: () => this.#noteInputContentChange(),
|
|
530
|
-
observeDiskContent: (uri, content) => this.#observeDiskContent(uri, content),
|
|
561
|
+
observeDiskContent: (uri, content) => this.#inputBarrier.observeDiskContent(uri, content),
|
|
562
|
+
noteExternalDiskChange: (uri, content) =>
|
|
563
|
+
this.#noteExternalDiskChange(uriToFile(uri), content),
|
|
531
564
|
invalidateEvidence: (uri) => {
|
|
532
565
|
this.#advanceInputRevision(false, true, "failure");
|
|
533
566
|
this.#cancelDiagnosticRequest(uri);
|