@@ -21,7 +21,7 @@ import { Codicon } from 'vs/base/common/codicons';
21
21
import { Emitter , Event } from 'vs/base/common/event' ;
22
22
import { FuzzyScore } from 'vs/base/common/filters' ;
23
23
import { IMarkdownString , MarkdownString } from 'vs/base/common/htmlContent' ;
24
- import { Disposable , DisposableStore , IDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
24
+ import { Disposable , DisposableStore , IDisposable , IReference , toDisposable } from 'vs/base/common/lifecycle' ;
25
25
import { ResourceMap } from 'vs/base/common/map' ;
26
26
import { marked } from 'vs/base/common/marked/marked' ;
27
27
import { FileAccess , Schemas , matchesSomeScheme } from 'vs/base/common/network' ;
@@ -30,18 +30,16 @@ import { basename } from 'vs/base/common/path';
30
30
import { equalsIgnoreCase } from 'vs/base/common/strings' ;
31
31
import { ThemeIcon } from 'vs/base/common/themables' ;
32
32
import { URI } from 'vs/base/common/uri' ;
33
- import { ICodeEditor } from 'vs/editor/browser/editorBrowser' ;
34
- import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService' ;
35
33
import { IMarkdownRenderResult , MarkdownRenderer } from 'vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer' ;
36
34
import { Range } from 'vs/editor/common/core/range' ;
35
+ import { IResolvedTextEditorModel , ITextModelService } from 'vs/editor/common/services/resolverService' ;
37
36
import { localize } from 'vs/nls' ;
38
37
import { IMenuEntryActionViewItemOptions , MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem' ;
39
38
import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar' ;
40
39
import { MenuId , MenuItemAction } from 'vs/platform/actions/common/actions' ;
41
40
import { ICommandService } from 'vs/platform/commands/common/commands' ;
42
41
import { IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
43
42
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey' ;
44
- import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor' ;
45
43
import { FileKind , FileType } from 'vs/platform/files/common/files' ;
46
44
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
47
45
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection' ;
@@ -56,9 +54,9 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil
56
54
import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView' ;
57
55
import { ChatTreeItem , IChatCodeBlockInfo , IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat' ;
58
56
import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups' ;
59
- import { ChatMarkdownDecorationsRenderer , annotateSpecialMarkdownContent , extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer' ;
57
+ import { ChatMarkdownDecorationsRenderer , IMarkdownVulnerability , annotateSpecialMarkdownContent , extractVulnerabilitiesFromText } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer' ;
60
58
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions' ;
61
- import { ChatCodeBlockContentProvider , ICodeBlockData , ICodeBlockPart , LocalFileCodeBlockPart , SimpleCodeBlockPart , localFileLanguageId , parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart' ;
59
+ import { ChatCodeBlockContentProvider , CodeBlockPart , ICodeBlockData , ICodeBlockPart , localFileLanguageId , parseLocalFileData } from 'vs/workbench/contrib/chat/browser/codeBlockPart' ;
62
60
import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents' ;
63
61
import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING , CONTEXT_REQUEST , CONTEXT_RESPONSE , CONTEXT_RESPONSE_FILTERED , CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys' ;
64
62
import { IChatProgressRenderableResponseContent } from 'vs/workbench/contrib/chat/common/chatModel' ;
@@ -105,6 +103,7 @@ export interface IChatListItemRendererOptions {
105
103
}
106
104
107
105
export class ChatListItemRenderer extends Disposable implements ITreeRenderer < ChatTreeItem , FuzzyScore , IChatListItemTemplate > {
106
+
108
107
static readonly ID = 'item' ;
109
108
110
109
private readonly codeBlocksByResponseId = new Map < string , IChatCodeBlockInfo [ ] > ( ) ;
@@ -133,47 +132,29 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
133
132
private _usedReferencesEnabled = false ;
134
133
135
134
constructor (
136
- private readonly editorOptions : ChatEditorOptions ,
135
+ editorOptions : ChatEditorOptions ,
137
136
private readonly rendererOptions : IChatListItemRendererOptions ,
138
137
private readonly delegate : IChatRendererDelegate ,
139
- overflowWidgetsDomNode : HTMLElement | undefined ,
138
+ private readonly codeBlockModelCollection : CodeBlockModelCollection ,
140
139
@IInstantiationService private readonly instantiationService : IInstantiationService ,
141
140
@IConfigurationService configService : IConfigurationService ,
142
141
@ILogService private readonly logService : ILogService ,
143
142
@IOpenerService private readonly openerService : IOpenerService ,
144
143
@IContextKeyService private readonly contextKeyService : IContextKeyService ,
145
- @ICodeEditorService codeEditorService : ICodeEditorService ,
146
144
@IThemeService private readonly themeService : IThemeService ,
147
145
@ICommandService private readonly commandService : ICommandService ,
146
+ @ITextModelService private readonly textModelService : ITextModelService ,
148
147
) {
149
148
super ( ) ;
149
+ this . _editorPool = this . _register ( this . instantiationService . createInstance ( EditorPool , editorOptions , delegate , undefined ) ) ;
150
+
150
151
this . renderer = this . _register ( this . instantiationService . createInstance ( MarkdownRenderer , { } ) ) ;
151
152
this . markdownDecorationsRenderer = this . instantiationService . createInstance ( ChatMarkdownDecorationsRenderer ) ;
152
- this . _editorPool = this . _register ( this . instantiationService . createInstance ( EditorPool , this . editorOptions , delegate , overflowWidgetsDomNode ) ) ;
153
153
this . _treePool = this . _register ( this . instantiationService . createInstance ( TreePool , this . _onDidChangeVisibility . event ) ) ;
154
154
this . _contentReferencesListPool = this . _register ( this . instantiationService . createInstance ( ContentReferencesListPool , this . _onDidChangeVisibility . event ) ) ;
155
155
156
156
this . _register ( this . instantiationService . createInstance ( ChatCodeBlockContentProvider ) ) ;
157
157
158
- this . _register ( codeEditorService . registerCodeEditorOpenHandler ( async ( input : ITextResourceEditorInput , _source : ICodeEditor | null , _sideBySide ?: boolean ) : Promise < ICodeEditor | null > => {
159
- if ( input . resource . scheme !== Schemas . vscodeChatCodeBlock ) {
160
- return null ;
161
- }
162
- const block = this . _editorPool . find ( input . resource ) ;
163
- if ( ! block ) {
164
- return null ;
165
- }
166
- if ( input . options ?. selection ) {
167
- block . editor . setSelection ( {
168
- startLineNumber : input . options . selection . startLineNumber ,
169
- startColumn : input . options . selection . startColumn ,
170
- endLineNumber : input . options . selection . startLineNumber ?? input . options . selection . endLineNumber ,
171
- endColumn : input . options . selection . startColumn ?? input . options . selection . endColumn
172
- } ) ;
173
- }
174
- return block . editor ;
175
- } ) ) ;
176
-
177
158
this . _usedReferencesEnabled = configService . getValue ( 'chat.experimental.usedReferences' ) ?? true ;
178
159
this . _register ( configService . onDidChangeConfiguration ( e => {
179
160
if ( e . affectsConfiguration ( 'chat.experimental.usedReferences' ) ) {
@@ -186,6 +167,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
186
167
return ChatListItemRenderer . ID ;
187
168
}
188
169
170
+ editorsInUse ( ) {
171
+ return this . _editorPool . inUse ( ) ;
172
+ }
173
+
189
174
private traceLayout ( method : string , message : string ) {
190
175
if ( forceVerboseLayoutTracing ) {
191
176
this . logService . info ( `ChatListItemRenderer#${ method } : ${ message } ` ) ;
@@ -858,7 +843,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
858
843
859
844
private renderMarkdown ( markdown : IMarkdownString , element : ChatTreeItem , templateData : IChatListItemTemplate , fillInIncompleteTokens = false ) : IMarkdownRenderResult {
860
845
const disposables = new DisposableStore ( ) ;
861
- let codeBlockIndex = 0 ;
862
846
863
847
markdown = new MarkdownString ( markdown . value , {
864
848
isTrusted : {
@@ -870,25 +854,33 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
870
854
// We release editors in order so that it's more likely that the same editor will be assigned if this element is re-rendered right away, like it often is during progressive rendering
871
855
const orderedDisposablesList : IDisposable [ ] = [ ] ;
872
856
const codeblocks : IChatCodeBlockInfo [ ] = [ ] ;
857
+ let codeBlockIndex = 0 ;
873
858
const result = this . renderer . render ( markdown , {
874
859
fillInIncompleteTokens,
875
860
codeBlockRendererSync : ( languageId , text ) => {
876
- let data : ICodeBlockData ;
861
+ const index = codeBlockIndex ++ ;
862
+ let textModel : Promise < IReference < IResolvedTextEditorModel > > ;
863
+ let range : Range | undefined ;
864
+ let vulns : readonly IMarkdownVulnerability [ ] | undefined ;
877
865
if ( equalsIgnoreCase ( languageId , localFileLanguageId ) ) {
878
866
try {
879
867
const parsedBody = parseLocalFileData ( text ) ;
880
- data = { type : 'localFile' , uri : parsedBody . uri , range : parsedBody . range && Range . lift ( parsedBody . range ) , codeBlockIndex : codeBlockIndex ++ , element, hideToolbar : false , parentContextKeyService : templateData . contextKeyService } ;
868
+ range = parsedBody . range && Range . lift ( parsedBody . range ) ;
869
+ textModel = this . textModelService . createModelReference ( parsedBody . uri ) ;
881
870
} catch ( e ) {
882
- console . error ( e ) ;
883
871
return $ ( 'div' ) ;
884
872
}
885
873
} else {
886
- const vulns = extractVulnerabilitiesFromText ( text ) ;
887
- const hideToolbar = isResponseVM ( element ) && element . errorDetails ?. responseIsFiltered ;
888
- data = { type : 'code' , languageId, text : vulns . newText , codeBlockIndex : codeBlockIndex ++ , element, hideToolbar, parentContextKeyService : templateData . contextKeyService , vulns : vulns . vulnerabilities } ;
874
+ // TODO: Creating the text models should be done in the model layer, not in the renderer
875
+ // The current approach means that only code blocks that have been rendered can be referenced
876
+ textModel = this . codeBlockModelCollection . getOrCreate ( element , index ) ;
877
+ const extractedVulns = extractVulnerabilitiesFromText ( text ) ;
878
+ vulns = extractedVulns . vulnerabilities ;
879
+ textModel . then ( ref => ref . object . textEditorModel . setValue ( extractedVulns . newText ) ) ;
889
880
}
890
881
891
- const ref = this . renderCodeBlock ( data ) ;
882
+ const hideToolbar = isResponseVM ( element ) && element . errorDetails ?. responseIsFiltered ;
883
+ const ref = this . renderCodeBlock ( { languageId, textModel, codeBlockIndex : index , element, range, hideToolbar, parentContextKeyService : templateData . contextKeyService , vulns } ) ;
892
884
893
885
// Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping)
894
886
// not during a renderElement OR a progressive render (when we will be firing this event anyway at the end of the render)
@@ -899,15 +891,18 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
899
891
900
892
if ( isResponseVM ( element ) ) {
901
893
const info : IChatCodeBlockInfo = {
902
- codeBlockIndex : data . codeBlockIndex ,
894
+ codeBlockIndex : index ,
903
895
element,
904
896
focus ( ) {
905
897
ref . object . focus ( ) ;
906
898
}
907
899
} ;
908
900
codeblocks . push ( info ) ;
909
- this . codeBlocksByEditorUri . set ( ref . object . uri , info ) ;
910
- disposables . add ( toDisposable ( ( ) => this . codeBlocksByEditorUri . delete ( ref . object . uri ) ) ) ;
901
+ if ( ref . object . uri ) {
902
+ const uri = ref . object . uri ;
903
+ this . codeBlocksByEditorUri . set ( uri , info ) ;
904
+ disposables . add ( toDisposable ( ( ) => this . codeBlocksByEditorUri . delete ( uri ) ) ) ;
905
+ }
911
906
}
912
907
orderedDisposablesList . push ( ref ) ;
913
908
return ref . object . element ;
@@ -933,7 +928,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
933
928
}
934
929
935
930
private renderCodeBlock ( data : ICodeBlockData ) : IDisposableReference < ICodeBlockPart > {
936
- const ref = this . _editorPool . get ( data ) ;
931
+ const ref = this . _editorPool . get ( ) ;
937
932
const editorInfo = ref . object ;
938
933
editorInfo . render ( data , this . _currentLayoutWidth ) ;
939
934
@@ -1068,54 +1063,78 @@ interface IDisposableReference<T> extends IDisposable {
1068
1063
isStale : ( ) => boolean ;
1069
1064
}
1070
1065
1071
- class EditorPool extends Disposable {
1066
+ export class EditorPool extends Disposable {
1072
1067
1073
- private readonly _simpleEditorPool : ResourcePool < SimpleCodeBlockPart > ;
1074
- private readonly _localFileEditorPool : ResourcePool < LocalFileCodeBlockPart > ;
1068
+ private readonly _pool : ResourcePool < CodeBlockPart > ;
1075
1069
1076
- public * inUse ( ) : Iterable < ICodeBlockPart > {
1077
- yield * this . _simpleEditorPool . inUse ;
1078
- yield * this . _localFileEditorPool . inUse ;
1070
+ public inUse ( ) : Iterable < ICodeBlockPart > {
1071
+ return this . _pool . inUse ;
1079
1072
}
1080
1073
1081
1074
constructor (
1082
- private readonly options : ChatEditorOptions ,
1075
+ options : ChatEditorOptions ,
1083
1076
delegate : IChatRendererDelegate ,
1084
1077
overflowWidgetsDomNode : HTMLElement | undefined ,
1085
- @IInstantiationService private readonly instantiationService : IInstantiationService ,
1078
+ @IInstantiationService instantiationService : IInstantiationService ,
1086
1079
) {
1087
1080
super ( ) ;
1088
- this . _simpleEditorPool = this . _register ( new ResourcePool ( ( ) => {
1089
- return this . instantiationService . createInstance ( SimpleCodeBlockPart , this . options , MenuId . ChatCodeBlock , delegate , overflowWidgetsDomNode ) ;
1090
- } ) ) ;
1091
- this . _localFileEditorPool = this . _register ( new ResourcePool ( ( ) => {
1092
- return this . instantiationService . createInstance ( LocalFileCodeBlockPart , this . options , MenuId . ChatCodeBlock , delegate , overflowWidgetsDomNode ) ;
1081
+ this . _pool = this . _register ( new ResourcePool ( ( ) => {
1082
+ return instantiationService . createInstance ( CodeBlockPart , options , MenuId . ChatCodeBlock , delegate , overflowWidgetsDomNode ) ;
1093
1083
} ) ) ;
1094
1084
}
1095
1085
1096
- get ( data : ICodeBlockData ) : IDisposableReference < ICodeBlockPart > {
1097
- return this . getFromPool ( data . type === 'localFile' ? this . _localFileEditorPool : this . _simpleEditorPool ) ;
1098
- }
1099
-
1100
- find ( resource : URI ) : SimpleCodeBlockPart | undefined {
1101
- return Array . from ( this . _simpleEditorPool . inUse ) . find ( part => part . uri ?. toString ( ) === resource . toString ( ) ) ;
1102
- }
1103
-
1104
- private getFromPool ( pool : ResourcePool < ICodeBlockPart > ) : IDisposableReference < ICodeBlockPart > {
1105
- const codeBlock = pool . get ( ) ;
1086
+ get ( ) : IDisposableReference < ICodeBlockPart > {
1087
+ const codeBlock = this . _pool . get ( ) ;
1106
1088
let stale = false ;
1107
1089
return {
1108
1090
object : codeBlock ,
1109
1091
isStale : ( ) => stale ,
1110
1092
dispose : ( ) => {
1111
1093
codeBlock . reset ( ) ;
1112
1094
stale = true ;
1113
- pool . release ( codeBlock ) ;
1095
+ this . _pool . release ( codeBlock ) ;
1114
1096
}
1115
1097
} ;
1116
1098
}
1117
1099
}
1118
1100
1101
+ export class CodeBlockModelCollection extends Disposable {
1102
+
1103
+ private readonly _models = new ResourceMap < Promise < IReference < IResolvedTextEditorModel > > > ( ) ;
1104
+
1105
+ constructor (
1106
+ @ITextModelService private readonly textModelService : ITextModelService ,
1107
+ ) {
1108
+ super ( ) ;
1109
+ }
1110
+
1111
+ public override dispose ( ) : void {
1112
+ super . dispose ( ) ;
1113
+ this . clear ( ) ;
1114
+ }
1115
+
1116
+ getOrCreate ( element : ChatTreeItem , codeBlockIndex : number ) : Promise < IReference < IResolvedTextEditorModel > > {
1117
+ const uri = this . getUri ( element , codeBlockIndex ) ;
1118
+ const existing = this . _models . get ( uri ) ;
1119
+ if ( existing ) {
1120
+ return existing ;
1121
+ }
1122
+
1123
+ const ref = this . textModelService . createModelReference ( uri ) ;
1124
+ this . _models . set ( uri , ref ) ;
1125
+ return ref ;
1126
+ }
1127
+
1128
+ clear ( ) : void {
1129
+ this . _models . forEach ( async model => ( await model ) . dispose ( ) ) ;
1130
+ this . _models . clear ( ) ;
1131
+ }
1132
+
1133
+ private getUri ( element : ChatTreeItem , index : number ) : URI {
1134
+ return URI . from ( { scheme : Schemas . vscodeChatCodeBlock , path : `/${ element . id } /${ index } ` } ) ;
1135
+ }
1136
+ }
1137
+
1119
1138
class TreePool extends Disposable {
1120
1139
private _pool : ResourcePool < WorkbenchCompressibleAsyncDataTree < IChatResponseProgressFileTreeData , IChatResponseProgressFileTreeData , void > > ;
1121
1140
0 commit comments