Skip to content

Commit e86b5fb

Browse files
committed
Optimize APIReference loading
1 parent 4e5e091 commit e86b5fb

File tree

2 files changed

+72
-41
lines changed

2 files changed

+72
-41
lines changed

spx-gui/src/components/editor/code-editor/code-editor.ts

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ import {
3030
type CompletionContext,
3131
type CompletionItem,
3232
InsertTextFormat,
33-
CompletionItemKind
33+
CompletionItemKind,
34+
type IAPIReferenceProvider,
35+
type APIReferenceContext
3436
} from './ui/code-editor-ui'
3537
import {
3638
type Action,
@@ -57,11 +59,66 @@ import {
5759
makeBasicMarkdownString,
5860
type WorkspaceDiagnostics,
5961
type TextDocumentDiagnostics,
60-
fromLSPDiagnostic
62+
fromLSPDiagnostic,
63+
isTextDocumentStageCode
6164
} from './common'
6265
import { TextDocument, createTextDocument } from './text-document'
6366
import { type Monaco } from './monaco'
6467

68+
class APIReferenceProvider implements IAPIReferenceProvider {
69+
constructor(
70+
private documentBase: DocumentBase,
71+
private lspClient: SpxLSPClient
72+
) {}
73+
74+
private async getFallbackItems(ctx: APIReferenceContext) {
75+
const isStage = isTextDocumentStageCode(ctx.textDocument.id)
76+
const allItems = await this.documentBase.getAllDocumentations()
77+
const overviewSet = new Set<string>()
78+
const fallbackItems: DefinitionDocumentationItem[] = []
79+
for (const item of allItems) {
80+
if (item.hiddenFromList) continue
81+
if (item.definition.package === packageSpx) {
82+
const namespace = (item.definition.name ?? '').split('.')[0] // `Sprite` / `Game`
83+
if (isStage && namespace === 'Sprite') continue
84+
}
85+
if (overviewSet.has(item.overview)) continue // Skip duplicated items, e.g., `Sprite.onStart` & `Game.onStart`
86+
overviewSet.add(item.overview)
87+
fallbackItems.push(item)
88+
}
89+
return fallbackItems
90+
}
91+
92+
async provideAPIReference(ctx: APIReferenceContext, position: Position | null) {
93+
if (position == null) return this.getFallbackItems(ctx)
94+
95+
const definitions = await this.lspClient
96+
.workspaceExecuteCommandSpxGetDefinitions({
97+
textDocument: ctx.textDocument.id,
98+
position: toLSPPosition(position)
99+
})
100+
.catch((e) => {
101+
console.warn('Failed to get definitions', e)
102+
return null
103+
})
104+
ctx.signal.throwIfAborted()
105+
let apiReferenceItems: DefinitionDocumentationItem[]
106+
if (definitions != null && definitions.length > 0) {
107+
const maybeDocumentationItems = await Promise.all(
108+
definitions.map(async (def) => {
109+
const doc = await this.documentBase.getDocumentation(def)
110+
if (doc == null || doc.hiddenFromList) return null
111+
return doc
112+
})
113+
)
114+
apiReferenceItems = maybeDocumentationItems.filter((d) => d != null) as DefinitionDocumentationItem[]
115+
} else {
116+
apiReferenceItems = await this.getFallbackItems(ctx)
117+
}
118+
return apiReferenceItems
119+
}
120+
}
121+
65122
class ResourceReferencesProvider implements IResourceReferencesProvider {
66123
constructor(private lspClient: SpxLSPClient) {}
67124
async provideResourceReferences(ctx: ResourceReferencesContext): Promise<ResourceReference[]> {
@@ -388,6 +445,9 @@ export class CodeEditor extends Disposable {
388445
private copilot: Copilot
389446
private documentBase: DocumentBase
390447
private lspClient: SpxLSPClient
448+
private apiReferenceProvider: APIReferenceProvider
449+
private completionProvider: CompletionProvider
450+
private contextMenuProvider: ContextMenuProvider
391451
private resourceReferencesProvider: ResourceReferencesProvider
392452
private diagnosticsProvider: DiagnosticsProvider
393453
private hoverProvider: HoverProvider
@@ -402,6 +462,9 @@ export class CodeEditor extends Disposable {
402462
this.copilot = new Copilot(i18n, project)
403463
this.documentBase = new DocumentBase()
404464
this.lspClient = new SpxLSPClient(project)
465+
this.apiReferenceProvider = new APIReferenceProvider(this.documentBase, this.lspClient)
466+
this.completionProvider = new CompletionProvider(this.lspClient, this.documentBase)
467+
this.contextMenuProvider = new ContextMenuProvider(this.lspClient, this.documentBase)
405468
this.resourceReferencesProvider = new ResourceReferencesProvider(this.lspClient)
406469
this.diagnosticsProvider = new DiagnosticsProvider(this.runtime, this.lspClient)
407470
this.hoverProvider = new HoverProvider(this.lspClient, this.documentBase)
@@ -497,46 +560,15 @@ export class CodeEditor extends Disposable {
497560
const idx = this.uis.indexOf(ui)
498561
if (idx >= 0) this.uis.splice(idx, 1)
499562
this.uis.push(ui)
500-
;(window as any).ui = ui // for debugging only
501-
const { copilot, documentBase, lspClient } = this
502-
503-
ui.registerAPIReferenceProvider({
504-
async provideAPIReference(ctx, position) {
505-
const definitions = await lspClient
506-
.workspaceExecuteCommandSpxGetDefinitions({
507-
textDocument: ctx.textDocument.id,
508-
position: toLSPPosition(position)
509-
})
510-
.catch((e) => {
511-
console.warn('Failed to get definitions', e)
512-
return null
513-
})
514-
ctx.signal.throwIfAborted()
515-
let apiReferenceItems: DefinitionDocumentationItem[]
516-
if (definitions != null && definitions.length > 0) {
517-
const maybeDocumentationItems = await Promise.all(
518-
definitions.map(async (def) => {
519-
const doc = await documentBase.getDocumentation(def)
520-
if (doc == null || doc.hiddenFromList) return null
521-
return doc
522-
})
523-
)
524-
apiReferenceItems = maybeDocumentationItems.filter((d) => d != null) as DefinitionDocumentationItem[]
525-
} else {
526-
// When compiling errors encountered, we fallback to "all items".
527-
apiReferenceItems = await documentBase.getAllDocumentations()
528-
}
529-
return apiReferenceItems
530-
}
531-
})
532563

533-
ui.registerCompletionProvider(new CompletionProvider(this.lspClient, documentBase))
534-
ui.registerContextMenuProvider(new ContextMenuProvider(lspClient, documentBase))
535-
ui.registerCopilot(copilot)
564+
ui.registerAPIReferenceProvider(this.apiReferenceProvider)
565+
ui.registerCompletionProvider(this.completionProvider)
566+
ui.registerContextMenuProvider(this.contextMenuProvider)
567+
ui.registerCopilot(this.copilot)
536568
ui.registerDiagnosticsProvider(this.diagnosticsProvider)
537569
ui.registerHoverProvider(this.hoverProvider)
538570
ui.registerResourceReferencesProvider(this.resourceReferencesProvider)
539-
ui.registerDocumentBase(documentBase)
571+
ui.registerDocumentBase(this.documentBase)
540572
}
541573

542574
detachUI(ui: ICodeEditorUI) {

spx-gui/src/components/editor/code-editor/ui/api-reference/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export type APIReferenceItem = DefinitionDocumentationItem
1111
export type APIReferenceContext = BaseContext
1212

1313
export interface IAPIReferenceProvider {
14-
provideAPIReference(ctx: APIReferenceContext, position: Position): Promise<APIReferenceItem[]>
14+
provideAPIReference(ctx: APIReferenceContext, position: Position | null): Promise<APIReferenceItem[]>
1515
}
1616

1717
export class APIReferenceController extends Disposable {
@@ -29,8 +29,7 @@ export class APIReferenceController extends Disposable {
2929
if (provider == null) throw new Error('No provider registered')
3030
const { activeTextDocument: textDocument, cursorPosition } = this.ui
3131
if (textDocument == null) throw new Error('No active text document')
32-
const position = cursorPosition ?? { line: 1, column: 1 }
33-
return provider.provideAPIReference({ textDocument, signal }, position)
32+
return provider.provideAPIReference({ textDocument, signal }, cursorPosition)
3433
})
3534

3635
get items() {

0 commit comments

Comments
 (0)