3
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
- import { coalesce } from '../../../../../base/common/arrays.js' ;
6
+ import { coalesce , groupBy } from '../../../../../base/common/arrays.js' ;
7
+ import { assertNever } from '../../../../../base/common/assert.js' ;
7
8
import { CancellationToken } from '../../../../../base/common/cancellation.js' ;
8
9
import { Codicon } from '../../../../../base/common/codicons.js' ;
9
10
import { isCancellationError } from '../../../../../base/common/errors.js' ;
@@ -35,7 +36,7 @@ import { IWorkspaceContextService } from '../../../../../platform/workspace/comm
35
36
import { getExcludes , IFileQuery , ISearchComplete , ISearchConfiguration , ISearchService , QueryType } from '../../../../services/search/common/search.js' ;
36
37
import { ISymbolQuickPickItem } from '../../../search/browser/symbolsQuickAccess.js' ;
37
38
import { IDiagnosticVariableEntryFilterData } from '../../common/chatModel.js' ;
38
- import { IChatRequestVariableValue , IDynamicVariable } from '../../common/chatVariables.js' ;
39
+ import { IChatRequestProblemsVariable , IChatRequestVariableValue , IDynamicVariable } from '../../common/chatVariables.js' ;
39
40
import { IChatWidget } from '../chat.js' ;
40
41
import { ChatWidget , IChatWidgetContrib } from '../chatWidget.js' ;
41
42
import { ChatFileReference } from './chatDynamicVariables/chatFileReference.js' ;
@@ -650,62 +651,84 @@ export class AddDynamicVariableAction extends Action2 {
650
651
}
651
652
registerAction2 ( AddDynamicVariableAction ) ;
652
653
653
- export async function createMarkersQuickPick ( accessor : ServicesAccessor , onBackgroundAccept : ( item : IDiagnosticVariableEntryFilterData [ ] ) => void ) : Promise < IDiagnosticVariableEntryFilterData | undefined > {
654
+ export async function createMarkersQuickPick ( accessor : ServicesAccessor , level : 'problem' | 'file' , onBackgroundAccept ? : ( item : IDiagnosticVariableEntryFilterData [ ] ) => void ) : Promise < IDiagnosticVariableEntryFilterData | undefined > {
654
655
const markers = accessor . get ( IMarkerService ) . read ( ) ;
655
656
if ( ! markers . length ) {
656
657
return ;
657
658
}
658
659
659
660
const uriIdentityService = accessor . get ( IUriIdentityService ) ;
660
661
const labelService = accessor . get ( ILabelService ) ;
661
- markers . sort ( ( a , b ) => uriIdentityService . extUri . compare ( a . resource , b . resource ) || b . severity - a . severity ) ;
662
+ const grouped = groupBy ( markers , ( a , b ) => uriIdentityService . extUri . compare ( a . resource , b . resource ) ) ;
662
663
663
664
const severities = new Set < MarkerSeverity > ( ) ;
664
665
type MarkerPickItem = IQuickPickItem & { resource ?: URI ; entry : IDiagnosticVariableEntryFilterData } ;
665
666
const items : ( MarkerPickItem | IQuickPickSeparator ) [ ] = [ ] ;
666
- for ( const marker of markers ) {
667
- if ( ! uriIdentityService . extUri . isEqual ( marker . resource , ( items . at ( - 1 ) as MarkerPickItem ) ?. resource ) ) {
668
- items . push ( { type : 'separator' , label : labelService . getUriLabel ( marker . resource , { relative : true } ) } ) ;
667
+
668
+ let pickCount = 0 ;
669
+ for ( const group of grouped ) {
670
+ const resource = group [ 0 ] . resource ;
671
+ if ( level === 'problem' ) {
672
+ items . push ( { type : 'separator' , label : labelService . getUriLabel ( resource , { relative : true } ) } ) ;
673
+ for ( const marker of group ) {
674
+ pickCount ++ ;
675
+ severities . add ( marker . severity ) ;
676
+ items . push ( {
677
+ type : 'item' ,
678
+ resource : marker . resource ,
679
+ label : marker . message ,
680
+ description : localize ( 'markers.panel.at.ln.col.number' , "[Ln {0}, Col {1}]" , '' + marker . startLineNumber , '' + marker . startColumn ) ,
681
+ entry : IDiagnosticVariableEntryFilterData . fromMarker ( marker ) ,
682
+ } ) ;
683
+ }
684
+ } else if ( level === 'file' ) {
685
+ const entry = { filterUri : resource } ;
686
+ pickCount ++ ;
687
+ items . push ( {
688
+ type : 'item' ,
689
+ resource,
690
+ label : IDiagnosticVariableEntryFilterData . label ( entry ) ,
691
+ description : group [ 0 ] . message + ( group . length > 1 ? localize ( 'problemsMore' , '+ {0} more' , group . length - 1 ) : '' ) ,
692
+ entry,
693
+ } ) ;
694
+ for ( const marker of group ) {
695
+ severities . add ( marker . severity ) ;
696
+ }
697
+ } else {
698
+ assertNever ( level ) ;
669
699
}
700
+ }
670
701
671
- severities . add ( marker . severity ) ;
672
- items . push ( {
673
- type : 'item' ,
674
- resource : marker . resource ,
675
- label : marker . message ,
676
- description : localize ( 'markers.panel.at.ln.col.number' , "[Ln {0}, Col {1}]" , '' + marker . startLineNumber , '' + marker . startColumn ) ,
677
- entry : IDiagnosticVariableEntryFilterData . fromMarker ( marker ) ,
678
- } ) ;
702
+ if ( pickCount < 2 ) { // single error in a URI
703
+ return items . find ( ( i ) : i is MarkerPickItem => i . type === 'item' ) ?. entry ;
679
704
}
680
705
681
- if ( items . length === 2 ) { // single error in a URI
682
- return ( items [ 1 ] as MarkerPickItem ) . entry ;
706
+ if ( level === 'file' ) {
707
+ items . unshift ( { type : 'separator' , label : localize ( 'markers.panel.files' , 'Files' ) } ) ;
683
708
}
684
709
685
- if ( items . length > 2 ) {
686
- if ( severities . has ( MarkerSeverity . Error ) ) {
687
- items . unshift ( { type : 'item' , label : localize ( 'markers.panel.allErrors' , 'All Errors' ) , entry : { filterSeverity : MarkerSeverity . Error } } ) ;
688
- }
689
- if ( severities . has ( MarkerSeverity . Warning ) ) {
690
- items . unshift ( { type : 'item' , label : localize ( 'markers.panel.allWarnings' , 'All Warnings' ) , entry : { filterSeverity : MarkerSeverity . Warning } } ) ;
691
- }
692
- if ( severities . has ( MarkerSeverity . Info ) ) {
693
- items . unshift ( { type : 'item' , label : localize ( 'markers.panel.allInfos' , 'All Infos' ) , entry : { filterSeverity : MarkerSeverity . Info } } ) ;
694
- }
710
+ if ( severities . has ( MarkerSeverity . Error ) ) {
711
+ items . unshift ( { type : 'item' , label : localize ( 'markers.panel.allErrors' , 'All Errors' ) , entry : { filterSeverity : MarkerSeverity . Error } } ) ;
712
+ }
713
+ if ( severities . has ( MarkerSeverity . Warning ) ) {
714
+ items . unshift ( { type : 'item' , label : localize ( 'markers.panel.allWarnings' , 'All Warnings' ) , entry : { filterSeverity : MarkerSeverity . Warning } } ) ;
715
+ }
716
+ if ( severities . has ( MarkerSeverity . Info ) ) {
717
+ items . unshift ( { type : 'item' , label : localize ( 'markers.panel.allInfos' , 'All Infos' ) , entry : { filterSeverity : MarkerSeverity . Info } } ) ;
695
718
}
696
719
697
720
698
721
const quickInputService = accessor . get ( IQuickInputService ) ;
699
722
const quickPick = quickInputService . createQuickPick < MarkerPickItem > ( { useSeparators : true } ) ;
700
- quickPick . canAcceptInBackground = true ;
723
+ quickPick . canAcceptInBackground = ! onBackgroundAccept ;
701
724
quickPick . placeholder = localize ( 'pickAProblem' , 'Pick a problem to attach...' ) ;
702
725
quickPick . items = items ;
703
726
704
727
return new Promise < IDiagnosticVariableEntryFilterData | undefined > ( resolve => {
705
728
quickPick . onDidHide ( ( ) => resolve ( undefined ) ) ;
706
729
quickPick . onDidAccept ( ev => {
707
730
if ( ev . inBackground ) {
708
- onBackgroundAccept ( quickPick . selectedItems . map ( i => i . entry ) ) ;
731
+ onBackgroundAccept ?. ( quickPick . selectedItems . map ( i => i . entry ) ) ;
709
732
} else {
710
733
resolve ( quickPick . selectedItems [ 0 ] ?. entry ) ;
711
734
quickPick . dispose ( ) ;
@@ -715,3 +738,53 @@ export async function createMarkersQuickPick(accessor: ServicesAccessor, onBackg
715
738
} ) . finally ( ( ) => quickPick . dispose ( ) ) ;
716
739
}
717
740
741
+ export class SelectAndInsertProblemAction extends Action2 {
742
+ static readonly Name = 'problems' ;
743
+ static readonly ID = 'workbench.action.chat.selectAndInsertProblems' ;
744
+
745
+ constructor ( ) {
746
+ super ( {
747
+ id : SelectAndInsertProblemAction . ID ,
748
+ title : '' // not displayed
749
+ } ) ;
750
+ }
751
+
752
+ async run ( accessor : ServicesAccessor , ...args : any [ ] ) {
753
+ const logService = accessor . get ( ILogService ) ;
754
+ const context = args [ 0 ] ;
755
+ if ( ! isSelectAndInsertActionContext ( context ) ) {
756
+ return ;
757
+ }
758
+
759
+ const doCleanup = ( ) => {
760
+ // Failed, remove the dangling `problem`
761
+ context . widget . inputEditor . executeEdits ( 'chatInsertProblems' , [ { range : context . range , text : `` } ] ) ;
762
+ } ;
763
+
764
+ const pick = await createMarkersQuickPick ( accessor , 'file' ) ;
765
+ if ( ! pick ) {
766
+ doCleanup ( ) ;
767
+ return ;
768
+ }
769
+
770
+ const editor = context . widget . inputEditor ;
771
+ const originalRange = context . range ;
772
+ const insertText = `#${ SelectAndInsertProblemAction . Name } :${ pick . filterUri ? basename ( pick . filterUri ) : MarkerSeverity . toString ( pick . filterSeverity ! ) } ` ;
773
+
774
+ const varRange = new Range ( originalRange . startLineNumber , originalRange . startColumn , originalRange . endLineNumber , originalRange . startColumn + insertText . length ) ;
775
+ const success = editor . executeEdits ( 'chatInsertProblems' , [ { range : varRange , text : insertText + ' ' } ] ) ;
776
+ if ( ! success ) {
777
+ logService . trace ( `SelectAndInsertProblemsAction: failed to insert "${ insertText } "` ) ;
778
+ doCleanup ( ) ;
779
+ return ;
780
+ }
781
+
782
+ context . widget . getContrib < ChatDynamicVariableModel > ( ChatDynamicVariableModel . ID ) ?. addReference ( {
783
+ id : 'vscode.problems' ,
784
+ prefix : SelectAndInsertProblemAction . Name ,
785
+ range : varRange ,
786
+ data : { id : 'vscode.problems' , filter : pick } satisfies IChatRequestProblemsVariable ,
787
+ } ) ;
788
+ }
789
+ }
790
+ registerAction2 ( SelectAndInsertProblemAction ) ;
0 commit comments