Skip to content

Commit e1a5c37

Browse files
committed
optimize code-editor insertion
1 parent 4e5e091 commit e1a5c37

File tree

5 files changed

+128
-11
lines changed

5 files changed

+128
-11
lines changed

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ import {
5454
type CommandArgs,
5555
getTextDocumentId,
5656
containsPosition,
57-
makeBasicMarkdownString,
5857
type WorkspaceDiagnostics,
5958
type TextDocumentDiagnostics,
6059
fromLSPDiagnostic
@@ -273,6 +272,11 @@ class CompletionProvider implements ICompletionProvider {
273272
documentation: null
274273
}
275274

275+
if (item.insertText != null) {
276+
result.insertText = item.insertText
277+
result.insertTextFormat = this.getInsertTextFormat(item.insertTextFormat)
278+
}
279+
276280
const defId = item.data?.definition
277281
const definition = defId != null ? await this.documentBase.getDocumentation(defId) : null
278282

@@ -284,18 +288,14 @@ class CompletionProvider implements ICompletionProvider {
284288
result.kind = definition.kind
285289
result.insertText = definition.insertText
286290
result.insertTextFormat = InsertTextFormat.Snippet
287-
result.documentation = makeBasicMarkdownString(definition.overview)
291+
result.documentation = definition.detail
288292
}
289293

290294
if (item.documentation != null) {
291295
const docStr = lsp.MarkupContent.is(item.documentation) ? item.documentation.value : item.documentation
292296
result.documentation = makeAdvancedMarkdownString(docStr)
293297
}
294298

295-
if (item.insertText != null) {
296-
result.insertText = item.insertText
297-
result.insertTextFormat = this.getInsertTextFormat(item.insertTextFormat)
298-
}
299299
return result
300300
})
301301
)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ export class TextDocument
173173
return this.monacoTextModel.getValueInRange(toMonacoRange(range))
174174
}
175175

176+
getLineContent(line: number): string {
177+
return this.monacoTextModel.getLineContent(line)
178+
}
179+
176180
getWordAtPosition(position: Position): WordAtPosition | null {
177181
return this.monacoTextModel.getWordAtPosition(toMonacoPosition(position))
178182
}

spx-gui/src/components/editor/code-editor/ui/api-reference/APIReferenceItem.vue

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import { useMessageHandle } from '@/utils/exception'
33
import { UITooltip } from '@/components/ui'
4+
import { DefinitionKind } from '../../common'
45
import DefinitionOverviewWrapper from '../definition/DefinitionOverviewWrapper.vue'
56
import DefinitionDetailWrapper from '../definition/DefinitionDetailWrapper.vue'
67
import MarkdownView from '../markdown/MarkdownView.vue'
@@ -14,10 +15,21 @@ const props = defineProps<{
1415
1516
const codeEditorCtx = useCodeEditorUICtx()
1617
17-
const handleInsert = useMessageHandle(() => codeEditorCtx.ui.insertSnippet(props.item.insertText), {
18-
en: 'Failed to insert',
19-
zh: '插入失败'
20-
}).fn
18+
const blockDefinitionKinds = [DefinitionKind.Command, DefinitionKind.Listen, DefinitionKind.Statement]
19+
20+
const handleInsert = useMessageHandle(
21+
() => {
22+
if (blockDefinitionKinds.includes(props.item.kind)) {
23+
codeEditorCtx.ui.insertBlockSnippet(props.item.insertText)
24+
} else {
25+
codeEditorCtx.ui.insertInlineSnippet(props.item.insertText)
26+
}
27+
},
28+
{
29+
en: 'Failed to insert',
30+
zh: '插入失败'
31+
}
32+
).fn
2133
2234
const handleExplain = useMessageHandle(
2335
() =>

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

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,103 @@ export class CodeEditorUI extends Disposable implements ICodeEditorUI {
282282
this.editor.focus()
283283
}
284284

285+
private async insertInlineContent(type: 'text' | 'snippet', content: string, range: Range) {
286+
const textDocument = this.activeTextDocument
287+
if (textDocument == null) return
288+
289+
const insert = async (cnt: string, rg: Range) => {
290+
if (type === 'text') await this.insertText(cnt, rg)
291+
else await this.insertSnippet(cnt, rg)
292+
}
293+
294+
if (!isRangeEmpty(range)) return insert(content, range)
295+
296+
const pos = range.start
297+
const lineCnt = textDocument.getLineContent(pos.line)
298+
if (isEmptyText(lineCnt)) return insert(content, range)
299+
300+
const word = textDocument.getWordAtPosition(pos)
301+
if (word == null) return insert(content, range)
302+
303+
const isPosInWord = pos.column >= word.startColumn && pos.column <= word.endColumn
304+
if (isPosInWord) {
305+
const wordEndPos = { line: pos.line, column: word.endColumn }
306+
this.editor.setPosition(toMonacoPosition(wordEndPos))
307+
return insert(' ' + content, { start: wordEndPos, end: wordEndPos })
308+
}
309+
310+
return insert(content, range)
311+
}
312+
313+
private async insertBlockContent(type: 'text' | 'snippet', content: string, range: Range) {
314+
const textDocument = this.activeTextDocument
315+
if (textDocument == null) return
316+
317+
const insert = async (cnt: string, rg: Range) => {
318+
// Ensure trailing newline if the insertion occurs at the end of the file
319+
const currentContent = textDocument.getValue()
320+
const insertionEndOffset = textDocument.getOffsetAt(rg.end)
321+
if (insertionEndOffset === currentContent.length && !cnt.endsWith('\n')) cnt += '\n'
322+
323+
if (type === 'snippet') {
324+
await this.insertSnippet(cnt, rg)
325+
return
326+
}
327+
328+
await this.insertText(cnt, rg)
329+
330+
// Properly indent the inserted content for type `text`:
331+
// 1. Select inserted content
332+
const cursorPos = this.editor.getPosition()
333+
if (cursorPos == null) return
334+
this.editor.setSelection(toMonacoRange({ start: rg.start, end: fromMonacoPosition(cursorPos) }))
335+
// 2. Indent selected content
336+
this.editor.trigger('insertBlockContent', 'editor.action.reindentselectedlines', null)
337+
// 3. Clear selection, move cursor to end of the selection
338+
const selection = this.editor.getSelection()
339+
if (selection == null) return
340+
this.editor.setSelection({
341+
startLineNumber: selection.endLineNumber,
342+
startColumn: selection.endColumn,
343+
endLineNumber: selection.endLineNumber,
344+
endColumn: selection.endColumn
345+
})
346+
}
347+
348+
const pos = range.end
349+
const lineCnt = textDocument.getLineContent(pos.line)
350+
if (isEmptyText(lineCnt)) return insert(content, range)
351+
352+
const lineStartPos = { line: pos.line, column: 1 }
353+
const lineCntBeforePos = textDocument.getValueInRange({ start: lineStartPos, end: pos })
354+
const isPosAtLineStart = isEmptyText(lineCntBeforePos)
355+
if (isPosAtLineStart) {
356+
if (!content.endsWith('\n')) content = content + '\n'
357+
return insert(content, range)
358+
}
359+
360+
const lineEndPos = { line: pos.line, column: lineCnt.length + 1 }
361+
this.editor.setPosition(toMonacoPosition(lineEndPos))
362+
content = '\n' + content.replace(/\n$/, '')
363+
return insert(content, { start: lineEndPos, end: lineEndPos })
364+
}
365+
366+
async insertInlineText(text: string, range: Range = this.getSelectionRange()) {
367+
return this.insertInlineContent('text', text, range)
368+
}
369+
370+
async insertInlineSnippet(snippet: string, range: Range = this.getSelectionRange()) {
371+
return this.insertInlineContent('snippet', snippet, range)
372+
}
373+
374+
async insertBlockText(text: string, range: Range = this.getSelectionRange()) {
375+
return this.insertBlockContent('text', text, range)
376+
}
377+
378+
async insertBlockSnippet(snippet: string, range: Range = this.getSelectionRange()) {
379+
return this.insertBlockContent('snippet', snippet, range)
380+
}
381+
285382
private cursorPositionRef = shallowRef<Position | null>(null)
286383
/** Cursor position (in current active text document) */
287384
get cursorPosition() {
@@ -478,3 +575,7 @@ export class CodeEditorUI extends Disposable implements ICodeEditorUI {
478575
super.dispose()
479576
}
480577
}
578+
579+
function isEmptyText(s: string) {
580+
return /^\s*$/.test(s)
581+
}

spx-gui/src/components/editor/code-editor/ui/markdown/CodeBlock.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const code = useSlotText()
1919
const handleInsert = useMessageHandle(
2020
() =>
2121
editorCtx.project.history.doAction({ name: { en: 'Insert code', zh: '插入代码' } }, () =>
22-
codeEditorUICtx.ui.insertText(code.value)
22+
codeEditorUICtx.ui.insertBlockText(code.value)
2323
),
2424
{ en: 'Failed to insert code', zh: '插入代码失败' }
2525
).fn

0 commit comments

Comments
 (0)