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
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ Rename/Formatting/Code Action/Resolve/Execute Command 目前仍是
LSP-only,未声明能力时返回明确的 capability 错误。Provider 抛错不会
让路由提前结束,用于隔离第三方语言服务器故障。

### 连续输入时刷新补全

补全结果不仅包含候选项,还包含服务器的 `isIncomplete`(候选尚不完整)标记。
Core 必须保留这个列表级标记,空列表也不能丢;平台合并本地候选时保留任一
provider 的未完整状态。两端把它映射为 Monaco 的 `incomplete`,把 Monaco
触发原因换算为 LSP 数值,再交给 Core 校验并生成请求 context。继续输入需要新候选时,由 Monaco
重新请求,不在 Mac 和 Windows 各实现缓存刷新算法。旧的只读候选数组接口
只供不消费列表状态的兼容调用,编辑器必须使用完整列表接口。

参考 OpenIDEA `fff2c711` 的 `convert.rs` 和 `completion.ts` 保留列表状态的
处理方式;它使用 CodeMirror,我们复用现有 Monaco 0.55.1 的对应机制,不迁移
其编辑器实现,也不增加语言语义引擎。仅把 `isIncomplete` 固定为 true 会增加
不必要的服务器请求,因此保留上游实际值。验证包括共享 fixture、Swift 路由、
Windows 适配器,以及真实 WebKit/Monaco 中从 `a` 继续输入 `b` 后出现第二次
请求才返回的 `abacus` 候选;原有过期结果与资源清理边界继续适用。

### Windows 语义颜色请求

Windows 的语义高亮复用 Core 已有 `semanticTokens` 操作与请求时捕获的图例,
Expand Down
7 changes: 4 additions & 3 deletions frontend/editor/src/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1738,7 +1738,7 @@ export function mountWorkbench(host: WorkbenchHost) {
}>();
monaco.languages.registerCompletionItemProvider("*", {
triggerCharacters: ["."],
async provideCompletionItems(model, position, _context, token) {
async provideCompletionItems(model, position, context, token) {
const pair = [...entries].find(([, entry]) => entry.model === model);
const id = pair?.[0]; const entry = pair?.[1];
if (!id || !entry || entry.model !== model) return { suggestions: [] };
Expand All @@ -1748,10 +1748,11 @@ export function mountWorkbench(host: WorkbenchHost) {
const revision = entry.revision, version = model.getVersionId();
const request = {};
completionRequests.set(entry, request);
const reply = await languageRequest({ type: "completion", id, revision, line: position.lineNumber - 1, column: position.column - 1 });
const reply = await languageRequest({ type: "completion", id, revision, line: position.lineNumber - 1, column: position.column - 1,
triggerKind: context.triggerKind + 1, triggerCharacter: context.triggerCharacter });
if (!current() || token.isCancellationRequested || entries.get(id) !== entry || model.isDisposed() || entry.revision !== revision || model.getVersionId() !== version || completionRequests.get(entry) !== request || reply.cancelled) return { suggestions: [] };
const word = model.getWordUntilPosition(position);
return { suggestions: (reply.items ?? []).map((item: any) => {
return { incomplete: reply.isIncomplete === true, suggestions: (reply.items ?? []).map((item: any) => {
const suggestion = { ...item, kind: mapCompletionKind(item.kind),
insertTextRules: item.insertTextFormat === 2 ? monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet : undefined,
range: item.range ?? new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn) };
Expand Down
5 changes: 5 additions & 0 deletions macos/Experiments/Monaco/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,11 @@ final class Probe: NSObject, NSApplicationDelegate, WKScriptMessageHandlerWithRe
"textEdits": [["range": ["startLineNumber": 1, "startColumn": 5, "endLineNumber": 1, "endColumn": 5], "text": "value: "]]]]], nil)
case "hover": replyHandler(["contents": "Host hover"], nil)
case "debugHover": replyHandler(["contents": "second = <value> [literal]"], nil)
case "completion" where body["id"] as? String == "completion-refresh":
let refreshed = body["triggerKind"] as? Int == 3
replyHandler(["isIncomplete": !refreshed, "items": [[
"label": refreshed ? "abacus" : "alpha", "insertText": refreshed ? "abacus" : "alpha", "kind": 6
]]], nil)
case "completion": replyHandler(["items": [
["label": "sampleMethod", "insertText": "sampleMethod(${1:value})$0", "kind": 2, "insertTextFormat": 2,
"sortText": "001", "filterText": "sample", "completionList": "fixture-list", "completionIndex": 0] as [String: Any]
Expand Down
30 changes: 30 additions & 0 deletions macos/Experiments/Monaco/workbench.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1773,6 +1773,36 @@ async function verify() {
} finally { await window.lithe.retain([]); }
}
});
await check("typing after an incomplete list asks the host for new candidates", async () => {
const id = "completion-refresh", text = "a";
await send({ type: "open", id, text });
await window.lithe.activate({ id, text, revision: 0, language: "java", readonly: false });
editor.setPosition(new Position(1, 2));
const controller = SuggestController.get(editor)!;
const suggestModel = (controller as any).model;
const listeners: { dispose(): void }[] = [];
const candidate = (label: string) => new Promise<void>((resolve) => {
const listener = suggestModel.onDidSuggest((event: any) => {
if (event.completionModel.items.some((item: any) => item.completion.label === label)) {
listener.dispose(); resolve();
}
});
listeners.push(listener);
});
try {
const initial = candidate("alpha");
suggestModel.trigger({ auto: false });
await withDeadline(initial, "initial completion was not displayed");
const refreshed = candidate("abacus");
editor.trigger("keyboard", "type", { text: "b" });
await withDeadline(refreshed, "continued typing reused an incomplete list instead of requesting new candidates");
assert(editor.getModel()!.getValue() === "ab", "completion refresh changed the buffer");
} finally {
for (const listener of listeners) listener.dispose();
suggestModel.cancel();
await window.lithe.retain([]);
}
});
await check("accepted completion expands snippets imports and tab stops in one undo", async () => {
const text = "// header\nsample", id = "snippet-accept";
await send({ type: "open", text });
Expand Down
4 changes: 4 additions & 0 deletions macos/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@
"Complete commit message" = "Complete commit message";
"Complete sign-in in your browser. Your credentials are managed by Codex." = "Complete sign-in in your browser. Your credentials are managed by Codex.";
"Completed" = "Completed";
"Completion trigger character is required" = "Completion trigger character is required";
"Completions" = "Completions";
"Compression is unavailable. Showing complete retained output." = "Compression is unavailable. Showing complete retained output.";
"Concise" = "Concise";
Expand Down Expand Up @@ -1839,6 +1840,7 @@
"Invalid JVM performance output." = "Invalid JVM performance output.";
"Invalid MCP tool configuration" = "Invalid MCP tool configuration";
"Invalid TOML. Use straight quotes (\") instead of smart quotes (“ ”)." = "Invalid TOML. Use straight quotes (\") instead of smart quotes (“ ”).";
"Invalid completion trigger kind" = "Invalid completion trigger kind";
"Invalid plugin icon resource: %@" = "Invalid plugin icon resource: %@";
"Invalid plugin web entry: %@" = "Invalid plugin web entry: %@";
"Invalid shared instruction request." = "Invalid shared instruction request.";
Expand Down Expand Up @@ -4653,6 +4655,8 @@
"https://github.com/owner/repository" = "https://github.com/owner/repository";
"just now" = "just now";
"local commit(s)" = "local commit(s)";
"lsp.completion.invalidTriggerKind" = "Invalid completion trigger kind";
"lsp.completion.missingTriggerCharacter" = "Completion trigger character is required";
"macOS could not prepare the update disk image. Download the release manually and try again." = "macOS could not prepare the update disk image. Download the release manually and try again.";
"macOS could not prepare the update disk image. Open the Release page and install it manually." = "macOS could not prepare the update disk image. Open the Release page and install it manually.";
"main Java" = "main Java";
Expand Down
4 changes: 4 additions & 0 deletions macos/Resources/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@
"Complete commit message" = "完整提交消息";
"Complete sign-in in your browser. Your credentials are managed by Codex." = "请在浏览器中完成登录,凭据由 Codex 管理。";
"Completed" = "已完成";
"Completion trigger character is required" = "补全请求缺少有效的触发字符";
"Completions" = "补全";
"Compression is unavailable. Showing complete retained output." = "暂时无法压缩,正在显示完整保留的输出。";
"Concise" = "简洁";
Expand Down Expand Up @@ -1839,6 +1840,7 @@
"Invalid JVM performance output." = "JVM 性能输出无效。";
"Invalid MCP tool configuration" = "MCP 工具配置无效";
"Invalid TOML. Use straight quotes (\") instead of smart quotes (“ ”)." = "TOML 格式无效,请使用直引号(\"),不要使用智能引号(“ ”)。";
"Invalid completion trigger kind" = "补全请求的触发类型无效";
"Invalid plugin icon resource: %@" = "无效的插件图标资源:%@";
"Invalid plugin web entry: %@" = "无效的插件网页入口:%@";
"Invalid shared instruction request." = "共享指令请求无效。";
Expand Down Expand Up @@ -4653,6 +4655,8 @@
"https://github.com/owner/repository" = "https://github.com/owner/repository";
"just now" = "刚刚";
"local commit(s)" = "个本地提交";
"lsp.completion.invalidTriggerKind" = "补全请求的触发类型无效";
"lsp.completion.missingTriggerCharacter" = "补全请求缺少有效的触发字符";
"macOS could not prepare the update disk image. Download the release manually and try again." = "macOS 无法准备更新磁盘映像,请手动下载版本后重试。";
"macOS could not prepare the update disk image. Open the Release page and install it manually." = "macOS 无法准备更新磁盘映像。请打开 Release 页面手动安装。";
"main Java" = "主代码";
Expand Down
5 changes: 5 additions & 0 deletions macos/Sources/Lithe/Core/Rust/RustCoreBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2009,6 +2009,7 @@ struct RustCoreBridge: Sendable, IncrementalLanguageServerRuntimeCore {
let newName: String?
let range: LspTextEditsRequest.TextEdit.Range?
let diagnostics: [LspClientDiagnosticRequest]
let completionContext: LanguageServerCompletionContext?
let completionItem: LspClientCompletionItemRequest?
let codeAction: LspClientCodeActionRequest?
let command: LspClientCommandRequest?
Expand All @@ -2023,6 +2024,7 @@ struct RustCoreBridge: Sendable, IncrementalLanguageServerRuntimeCore {
newName: String? = nil,
range: LspTextEditsRequest.TextEdit.Range? = nil,
diagnostics: [LspClientDiagnosticRequest] = [],
completionContext: LanguageServerCompletionContext? = nil,
completionItem: LspClientCompletionItemRequest? = nil,
codeAction: LspClientCodeActionRequest? = nil,
command: LspClientCommandRequest? = nil
Expand All @@ -2036,6 +2038,7 @@ struct RustCoreBridge: Sendable, IncrementalLanguageServerRuntimeCore {
self.newName = newName
self.range = range
self.diagnostics = diagnostics
self.completionContext = completionContext
self.completionItem = completionItem
self.codeAction = codeAction
self.command = command
Expand Down Expand Up @@ -3932,6 +3935,7 @@ struct RustCoreBridge: Sendable, IncrementalLanguageServerRuntimeCore {
newName: String? = nil,
range: LanguageServerRange? = nil,
diagnostics: [LanguageServerDiagnostic] = [],
completionContext: LanguageServerCompletionContext? = nil,
completionItem: LanguageServerCompletionItem? = nil,
codeAction: LanguageServerCodeAction? = nil,
command: LanguageServerCommand? = nil
Expand All @@ -3949,6 +3953,7 @@ struct RustCoreBridge: Sendable, IncrementalLanguageServerRuntimeCore {
newName: newName,
range: range.map(Self.makeRangeRequest),
diagnostics: diagnostics.map(Self.makeDiagnosticRequest),
completionContext: completionContext,
completionItem: completionItem.map(Self.makeCompletionItemRequest),
codeAction: codeAction.map(Self.makeCodeActionRequest),
command: command.map(Self.makeCommandRequest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ extension RustCoreBridge: LanguageServerRuntimeCore {
newName: String?,
range: LanguageServerRange?,
diagnostics: [LanguageServerDiagnostic],
completionContext: LanguageServerCompletionContext?,
completionItem: LanguageServerCompletionItem?,
codeAction: LanguageServerCodeAction?,
command: LanguageServerCommand?
Expand All @@ -176,6 +177,7 @@ extension RustCoreBridge: LanguageServerRuntimeCore {
newName: newName,
range: range,
diagnostics: diagnostics,
completionContext: completionContext,
completionItem: completionItem,
codeAction: codeAction,
command: command
Expand Down Expand Up @@ -418,9 +420,19 @@ extension RustCoreBridge: LanguageServerRuntimeCore {
}

private static func runtimeFailure(_ error: CoreCallError) -> LanguageServerRuntimeFailure {
LanguageServerRuntimeFailure(
// Only translate our own request validation; upstream diagnostics stay intact.
let message: String
switch (error.code, error.message) {
case ("invalid_request", "Invalid completion trigger kind"):
message = L10n.string("Invalid completion trigger kind")
case ("invalid_request", "Completion trigger character is required"):
message = L10n.string("Completion trigger character is required")
default:
message = error.message
}
return LanguageServerRuntimeFailure(
code: error.code,
message: error.message,
message: message,
details: error.details
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,23 @@ extension AppModel {
}

func requestLanguageCompletions(
for requestedDocument: EditorDocument? = nil, line: Int, utf16Column: Int,
completion: @escaping ([LanguageServerCompletionItem]) -> Void
) {
requestLanguageCompletionList(for: requestedDocument, line: line, utf16Column: utf16Column) {
completion($0.items)
}
}

func requestLanguageCompletionList(
for requestedDocument: EditorDocument? = nil,
line: Int,
utf16Column: Int,
completion: @escaping ([LanguageServerCompletionItem]) -> Void
context: LanguageServerCompletionContext? = nil,
completion: @escaping (LanguageServerCompletionList) -> Void
) {
guard let document = requestedDocument ?? focusedEditorDocument else {
completion([])
completion(.init())
return
}
let springCompletions = springFeature.completions(
Expand All @@ -92,32 +102,34 @@ extension AppModel {
guard
featureGraph.languageCapabilityPolicy.supports(.completion, documentURL: document.url, sessions: languageToolingSessionsIfActive),
let workspaceURL else {
completion(springCompletions)
completion(.init(items: springCompletions))
return
}
do {
try languageToolingSessionsIfActive?.completions(
try languageToolingSessionsIfActive?.completionList(
fileURL: document.url,
text: document.text,
position: featureGraph.languageEditing.position(
line: line,
utf16Column: utf16Column
),
rootURL: workspaceURL
rootURL: workspaceURL, completionContext: context
) { [weak self] result in
guard let self else {
completion(springCompletions)
completion(.init(items: springCompletions))
return
}
self.featureGraph.languageEditing.handleCompletionResult(
result,
result.map(\.items),
fallback: springCompletions,
completion: completion
completion: { items in
completion(.init(items: items, isIncomplete: (try? result.get().isIncomplete) ?? false))
}
)
}
} catch {
showNotification(error.localizedDescription)
completion(springCompletions)
completion(.init(items: springCompletions))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1614,7 +1614,10 @@ final class MonacoWorkbenchSession: NSObject, ObservableObject, WKNavigationDele
} else {
let key = UUID().uuidString
completionLists[id] = (context, revision, key, [])
model.requestLanguageCompletions(for: document, line: line, utf16Column: column) { [weak self] items in
model.requestLanguageCompletionList(for: document, line: line, utf16Column: column,
context: .init(triggerKind: body["triggerKind"] as? Int ?? 1,
triggerCharacter: body["triggerCharacter"] as? String)) { [weak self] list in
let items = list.items
guard let self, self.revisions[id] == revision, self.isCurrent(context),
self.completionLists[id]?.key == key else { reply(["items": []], nil); return }
self.completionLists[id] = (context, revision, key, items)
Expand All @@ -1624,7 +1627,7 @@ final class MonacoWorkbenchSession: NSObject, ObservableObject, WKNavigationDele
value["completionIndex"] = index
return value
}
reply(["items": values], nil)
reply(["items": values, "isIncomplete": list.isIncomplete], nil)
}
}
case "definitionHover":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ package protocol LanguageServerRuntimeCore: Sendable {
newName: String?,
range: LanguageServerRange?,
diagnostics: [LanguageServerDiagnostic],
completionContext: LanguageServerCompletionContext?,
completionItem: LanguageServerCompletionItem?,
codeAction: LanguageServerCodeAction?,
command: LanguageServerCommand?
Expand Down
Loading
Loading