44 *--------------------------------------------------------------------------------------------*/
55
66import 'vs/css!./inlineChat' ;
7- import { DisposableStore , MutableDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
8- import { IActiveCodeEditor , ICodeEditor , IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser' ;
7+ import { Disposable , DisposableStore , MutableDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
8+ import { ContentWidgetPositionPreference , IActiveCodeEditor , ICodeEditor , IContentWidget , IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser' ;
99import { EditorLayoutInfo , EditorOption } from 'vs/editor/common/config/editorOptions' ;
1010import { IRange , Range } from 'vs/editor/common/core/range' ;
1111import { localize } from 'vs/nls' ;
@@ -49,6 +49,7 @@ import { ExpansionState } from 'vs/workbench/contrib/inlineChat/browser/inlineCh
4949import { IdleValue } from 'vs/base/common/async' ;
5050import * as aria from 'vs/base/browser/ui/aria/aria' ;
5151import { IMenuWorkbenchButtonBarOptions , MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar' ;
52+ import { KeyCode } from 'vs/base/common/keyCodes' ;
5253
5354const defaultAriaLabel = localize ( 'aria-label' , "Inline Chat Input" ) ;
5455
@@ -180,6 +181,8 @@ export class InlineChatWidget {
180181 private _preferredExpansionState : ExpansionState | undefined ;
181182 private _expansionState : ExpansionState = ExpansionState . NOT_CROPPED ;
182183
184+ private _slashCommandContentWidget : SlashCommandContentWidget ;
185+
183186 constructor (
184187 private readonly parentEditor : ICodeEditor ,
185188 @IModelService private readonly _modelService : IModelService ,
@@ -278,6 +281,11 @@ export class InlineChatWidget {
278281 this . _store . add ( this . _inputModel . onDidChangeContent ( togglePlaceholder ) ) ;
279282 togglePlaceholder ( ) ;
280283
284+ // slash command content widget
285+
286+ this . _slashCommandContentWidget = new SlashCommandContentWidget ( this . _inputEditor ) ;
287+ this . _store . add ( this . _slashCommandContentWidget ) ;
288+
281289 // toolbars
282290
283291 const toolbar = this . _instantiationService . createInstance ( MenuWorkbenchToolBar , this . _elements . editorToolbar , MENU_INLINE_CHAT_WIDGET , {
@@ -654,6 +662,8 @@ export class InlineChatWidget {
654662 const decorations = this . _inputEditor . createDecorationsCollection ( ) ;
655663
656664 const updateSlashDecorations = ( ) => {
665+ this . _slashCommandContentWidget . hide ( ) ;
666+
657667 const newDecorations : IModelDeltaDecoration [ ] = [ ] ;
658668 for ( const command of commands ) {
659669 const withSlash = `/${ command . command } ` ;
@@ -664,9 +674,16 @@ export class InlineChatWidget {
664674 options : {
665675 description : 'inline-chat-slash-command' ,
666676 inlineClassName : 'inline-chat-slash-command' ,
677+ after : {
678+ // Force some space between slash command and placeholder
679+ content : ' '
680+ }
667681 }
668682 } ) ;
669683
684+ this . _slashCommandContentWidget . setCommandText ( command . command ) ;
685+ this . _slashCommandContentWidget . show ( ) ;
686+
670687 // inject detail when otherwise empty
671688 if ( firstLine === `/${ command . command } ` ) {
672689 newDecorations . push ( {
@@ -691,6 +708,57 @@ export class InlineChatWidget {
691708 }
692709}
693710
711+ class SlashCommandContentWidget extends Disposable implements IContentWidget {
712+ private _domNode = document . createElement ( 'div' ) ;
713+ private _lastSlashCommandText : string | undefined ;
714+
715+ constructor ( private _editor : ICodeEditor ) {
716+ super ( ) ;
717+
718+ this . _domNode . toggleAttribute ( 'hidden' , true ) ;
719+ this . _domNode . classList . add ( 'inline-chat-slash-command-content-widget' ) ;
720+
721+ // If backspace at a slash command boundary, remove the slash command
722+ this . _register ( this . _editor . onKeyUp ( ( e ) => {
723+ if ( e . keyCode !== KeyCode . Backspace ) {
724+ return ;
725+ }
726+
727+ const firstLine = this . _editor . getModel ( ) ?. getLineContent ( 1 ) ;
728+ const selection = this . _editor . getSelection ( ) ;
729+ const withSlash = `/${ this . _lastSlashCommandText } ` ;
730+ if ( ! firstLine ?. startsWith ( withSlash ) || ! selection ?. isEmpty ( ) || selection ?. startLineNumber !== 1 || selection ?. startColumn !== withSlash . length + 1 ) {
731+ return ;
732+ }
733+
734+ // Allow to undo the backspace
735+ this . _editor . executeEdits ( 'inline-chat-slash-command' , [ {
736+ range : new Range ( 1 , 1 , 1 , selection . startColumn ) ,
737+ text : null
738+ } ] ) ;
739+ } ) ) ;
740+ }
741+
742+ show ( ) {
743+ this . _domNode . toggleAttribute ( 'hidden' , false ) ;
744+ this . _editor . addContentWidget ( this ) ;
745+ }
746+
747+ hide ( ) {
748+ this . _domNode . toggleAttribute ( 'hidden' , true ) ;
749+ this . _editor . removeContentWidget ( this ) ;
750+ }
751+
752+ setCommandText ( slashCommand : string ) {
753+ this . _domNode . innerText = `${ slashCommand } ` ;
754+ this . _lastSlashCommandText = slashCommand ;
755+ }
756+
757+ getId ( ) { return 'inline-chat-slash-command-content-widget' ; }
758+ getDomNode ( ) { return this . _domNode ; }
759+ getPosition ( ) { return { position : { lineNumber : 1 , column : 1 } , preference : [ ContentWidgetPositionPreference . EXACT ] } ; }
760+ }
761+
694762export class InlineChatZoneWidget extends ZoneWidget {
695763
696764 readonly widget : InlineChatWidget ;
0 commit comments