@@ -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+ }
0 commit comments