4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
6
import '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' ;
9
9
import { EditorLayoutInfo , EditorOption } from 'vs/editor/common/config/editorOptions' ;
10
10
import { IRange , Range } from 'vs/editor/common/core/range' ;
11
11
import { localize } from 'vs/nls' ;
@@ -49,6 +49,7 @@ import { ExpansionState } from 'vs/workbench/contrib/inlineChat/browser/inlineCh
49
49
import { IdleValue } from 'vs/base/common/async' ;
50
50
import * as aria from 'vs/base/browser/ui/aria/aria' ;
51
51
import { IMenuWorkbenchButtonBarOptions , MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar' ;
52
+ import { KeyCode } from 'vs/base/common/keyCodes' ;
52
53
53
54
const defaultAriaLabel = localize ( 'aria-label' , "Inline Chat Input" ) ;
54
55
@@ -180,6 +181,8 @@ export class InlineChatWidget {
180
181
private _preferredExpansionState : ExpansionState | undefined ;
181
182
private _expansionState : ExpansionState = ExpansionState . NOT_CROPPED ;
182
183
184
+ private _slashCommandContentWidget : SlashCommandContentWidget ;
185
+
183
186
constructor (
184
187
private readonly parentEditor : ICodeEditor ,
185
188
@IModelService private readonly _modelService : IModelService ,
@@ -278,6 +281,11 @@ export class InlineChatWidget {
278
281
this . _store . add ( this . _inputModel . onDidChangeContent ( togglePlaceholder ) ) ;
279
282
togglePlaceholder ( ) ;
280
283
284
+ // slash command content widget
285
+
286
+ this . _slashCommandContentWidget = new SlashCommandContentWidget ( this . _inputEditor ) ;
287
+ this . _store . add ( this . _slashCommandContentWidget ) ;
288
+
281
289
// toolbars
282
290
283
291
const toolbar = this . _instantiationService . createInstance ( MenuWorkbenchToolBar , this . _elements . editorToolbar , MENU_INLINE_CHAT_WIDGET , {
@@ -654,6 +662,8 @@ export class InlineChatWidget {
654
662
const decorations = this . _inputEditor . createDecorationsCollection ( ) ;
655
663
656
664
const updateSlashDecorations = ( ) => {
665
+ this . _slashCommandContentWidget . hide ( ) ;
666
+
657
667
const newDecorations : IModelDeltaDecoration [ ] = [ ] ;
658
668
for ( const command of commands ) {
659
669
const withSlash = `/${ command . command } ` ;
@@ -664,9 +674,16 @@ export class InlineChatWidget {
664
674
options : {
665
675
description : 'inline-chat-slash-command' ,
666
676
inlineClassName : 'inline-chat-slash-command' ,
677
+ after : {
678
+ // Force some space between slash command and placeholder
679
+ content : ' '
680
+ }
667
681
}
668
682
} ) ;
669
683
684
+ this . _slashCommandContentWidget . setCommandText ( command . command ) ;
685
+ this . _slashCommandContentWidget . show ( ) ;
686
+
670
687
// inject detail when otherwise empty
671
688
if ( firstLine === `/${ command . command } ` ) {
672
689
newDecorations . push ( {
@@ -691,6 +708,57 @@ export class InlineChatWidget {
691
708
}
692
709
}
693
710
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
+
694
762
export class InlineChatZoneWidget extends ZoneWidget {
695
763
696
764
readonly widget : InlineChatWidget ;
0 commit comments