1
- /**
1
+ /*
2
2
* Copyright (c) 2015-present, Jim Kynde Meyer
3
3
* All rights reserved.
4
4
* <p>
8
8
package com .intellij .lang .jsgraphql .v1 .ide .editor ;
9
9
10
10
import com .google .common .collect .Lists ;
11
+ import com .google .common .collect .Maps ;
11
12
import com .google .common .collect .Sets ;
12
13
import com .intellij .codeInsight .daemon .DaemonCodeAnalyzer ;
13
14
import com .intellij .codeInsight .daemon .impl .HighlightInfo ;
21
22
import com .intellij .ide .util .PropertiesComponent ;
22
23
import com .intellij .lang .annotation .Annotation ;
23
24
import com .intellij .lang .annotation .HighlightSeverity ;
25
+ import com .intellij .lang .jsgraphql .ide .project .GraphQLPsiSearchHelper ;
24
26
import com .intellij .lang .jsgraphql .psi .*;
25
27
import com .intellij .notification .Notification ;
26
28
import com .intellij .notification .NotificationType ;
51
53
52
54
import javax .swing .*;
53
55
import java .awt .*;
54
- import java .util .Collection ;
55
- import java .util .Comparator ;
56
- import java .util .HashSet ;
57
- import java .util .Set ;
56
+ import java .util .*;
58
57
import java .util .function .Function ;
59
58
import java .util .stream .Collectors ;
60
59
import java .util .stream .Stream ;
@@ -104,6 +103,9 @@ public void visit(@NotNull PsiElement element) {
104
103
@ Override
105
104
public boolean analyze (final @ NotNull PsiFile file , boolean updateWholeFile , @ NotNull HighlightInfoHolder holder , @ NotNull Runnable action ) {
106
105
106
+ // run the default pass first (DefaultHighlightVisitor) which calls annotators etc.
107
+ action .run ();
108
+
107
109
final PsiElement operationAtCursor = getOperationAtCursor (file , null );
108
110
if (operationAtCursor != null && hasMultipleVisibleTopLevelElement (file )) {
109
111
@@ -112,12 +114,17 @@ public boolean analyze(final @NotNull PsiFile file, boolean updateWholeFile, @No
112
114
113
115
final Color borderColor = EditorColorsManager .getInstance ().getGlobalScheme ().getColor (EditorColors .TEARLINE_COLOR );
114
116
final TextAttributes textAttributes = new TextAttributes (null , null , borderColor , EffectType .ROUNDED_BOX , Font .PLAIN );
115
- final HashSet < GraphQLFragmentDefinition > foundFragments = Sets . newHashSet ();
117
+ final Map < String , GraphQLFragmentDefinition > foundFragments = Maps . newHashMap ();
116
118
findFragmentsInsideOperation (operationAtCursor , foundFragments , null );
117
119
for (PsiElement psiElement : file .getChildren ()) {
118
120
boolean showAsUsed = false ;
119
121
if (psiElement instanceof GraphQLFragmentDefinition ) {
120
- showAsUsed = foundFragments .contains (psiElement );
122
+ GraphQLFragmentDefinition definition = (GraphQLFragmentDefinition ) psiElement ;
123
+ if (definition .getOriginalElement () instanceof GraphQLFragmentDefinition ) {
124
+ // use the original PSI to compare since a separate editor tab has its own version of the PSI
125
+ definition = (GraphQLFragmentDefinition ) definition .getOriginalElement ();
126
+ }
127
+ showAsUsed = foundFragments .containsKey (getFragmentKey (definition ));
121
128
} else if (psiElement == operationAtCursor ) {
122
129
showAsUsed = true ;
123
130
}
@@ -188,8 +195,14 @@ public void caretPositionChanged(CaretEvent e) {
188
195
return true ;
189
196
}
190
197
198
+ private static String getFragmentKey (GraphQLFragmentDefinition definition ) {
199
+ if (definition == null ) {
200
+ return "" ;
201
+ }
202
+ return GraphQLPsiSearchHelper .getFileName (definition .getContainingFile ()) + ":" + definition .getName ();
203
+ }
204
+
191
205
private static void removeHighlights (Editor editor , Project project ) {
192
- // TODO JKM This also clears error highlights from the other GraphQL annotators that are internal in v2!!
193
206
HighlightManagerImpl highlightManager = (HighlightManagerImpl ) HighlightManager .getInstance (project );
194
207
for (RangeHighlighter rangeHighlighter : highlightManager .getHighlighters (editor )) {
195
208
highlightManager .removeSegmentHighlighter (editor , rangeHighlighter );
@@ -328,11 +341,11 @@ public static JSGraphQLQueryContext getQueryContextBufferAndHighlightUnused(fina
328
341
329
342
// no selection -- see if the caret is inside an operation
330
343
331
- final PsiElement operationAtCursor = getOperationAtCursor (psiFile , null );
344
+ final GraphQLOperationDefinition operationAtCursor = getOperationAtCursor (psiFile , null );
332
345
if (operationAtCursor != null ) {
333
- final HashSet < GraphQLFragmentDefinition > foundFragments = Sets . newHashSet ();
346
+ final Map < String , GraphQLFragmentDefinition > foundFragments = Maps . newHashMap ();
334
347
findFragmentsInsideOperation (operationAtCursor , foundFragments , null );
335
- Set <PsiElement > queryElements = Sets .newHashSet (foundFragments );
348
+ Set <PsiElement > queryElements = Sets .newHashSet (foundFragments . values () );
336
349
queryElements .add (operationAtCursor );
337
350
final StringBuilder query = new StringBuilder (editorLength );
338
351
final TextAttributes unusedTextAttributes = getUnusedTextAttributes ();
@@ -343,8 +356,16 @@ public static JSGraphQLQueryContext getQueryContextBufferAndHighlightUnused(fina
343
356
}
344
357
} else {
345
358
final TextRange textRange = psiElement .getTextRange ();
346
- if (queryElements .contains (psiElement )) {
359
+ String fragmentKey = "" ;
360
+ if (psiElement instanceof GraphQLFragmentDefinition ) {
361
+ fragmentKey = getFragmentKey ((GraphQLFragmentDefinition ) psiElement );
362
+ }
363
+ if (queryElements .contains (psiElement ) || foundFragments .containsKey (fragmentKey )) {
347
364
queryElements .remove (psiElement );
365
+ GraphQLFragmentDefinition fragmentDefinition = foundFragments .get (fragmentKey );
366
+ if (fragmentDefinition != null ) {
367
+ queryElements .remove (fragmentDefinition );
368
+ }
348
369
query .append (editorBuffer .subSequence (textRange .getStartOffset (), textRange .getEndOffset ()));
349
370
} else {
350
371
if (!queryElements .isEmpty ()) {
@@ -365,12 +386,20 @@ public static JSGraphQLQueryContext getQueryContextBufferAndHighlightUnused(fina
365
386
}
366
387
}
367
388
}
368
- final GraphQLIdentifier operationName = PsiTreeUtil .getChildOfType (operationAtCursor , GraphQLIdentifier .class );
369
- if (operationName != null ) {
370
- showQueryContextHint (editor , "Executed " + getOperationKind (operationName ) + " \" " + operationName .getText () + "\" " );
371
- } else if (operationAtCursor instanceof GraphQLSelectionSetOperationDefinition ) {
372
- // anonymous query
373
- showQueryContextHint (editor , "Executed anonymous query" );
389
+
390
+ // include fragments from other PsiFiles
391
+ for (PsiElement queryElement : queryElements ) {
392
+ query .append ("\n \n # ---- fragment automatically included from \" " );
393
+ query .append (GraphQLPsiSearchHelper .getFileName (queryElement .getContainingFile ())).append ("\" ----\n " );
394
+ query .append (queryElement .getText ());
395
+ }
396
+
397
+ if (operationAtCursor .getNameIdentifier () != null ) {
398
+ // named operation
399
+ showQueryContextHint (editor , "Executed " + getOperationKind (operationAtCursor ) + " \" " + operationAtCursor .getNameIdentifier ().getText () + "\" " );
400
+ } else {
401
+ // anonymous operation
402
+ showQueryContextHint (editor , "Executed anonymous " + getOperationKind (operationAtCursor ));
374
403
}
375
404
return new JSGraphQLQueryContext (query .toString (), null );
376
405
}
@@ -410,9 +439,18 @@ private static TextAttributes getUnusedTextAttributes() {
410
439
}
411
440
412
441
/**
413
- * Determines the kind of operation keyword that preceeds an operation name
442
+ * Determines the kind of operation keyword that defines an operation, e.g. "query" or "mutation"
414
443
*/
415
- private static String getOperationKind (GraphQLIdentifier operationName ) {
444
+ private static String getOperationKind (GraphQLOperationDefinition operation ) {
445
+ if (operation instanceof GraphQLSelectionSetOperationDefinition ) {
446
+ return "query" ;
447
+ }
448
+ if (operation instanceof GraphQLTypedOperationDefinition ) {
449
+ final GraphQLOperationType operationType = ((GraphQLTypedOperationDefinition ) operation ).getOperationType ();
450
+ if (operationType != null ) {
451
+ return operationType .getText ();
452
+ }
453
+ }
416
454
return "operation" ;
417
455
}
418
456
@@ -434,10 +472,6 @@ private static void showQueryContextHint(Editor editor, String hintText) {
434
472
* e.g. when outside any operation or inside a fragment definition
435
473
*/
436
474
private static GraphQLOperationDefinition getOperationAtCursor (PsiFile psiFile , CaretEvent caretEvent ) {
437
- if (true ) {
438
- // TODO refactor to v2 PSI
439
- return null ;
440
- }
441
475
final FileEditor fileEditor = FileEditorManager .getInstance (psiFile .getProject ()).getSelectedEditor (psiFile .getVirtualFile ());
442
476
if (fileEditor instanceof TextEditor ) {
443
477
final Editor editor = ((TextEditor ) fileEditor ).getEditor ();
@@ -466,21 +500,7 @@ private static GraphQLOperationDefinition getOperationAtCursor(PsiFile psiFile,
466
500
* Returns the operation candidate if it's an operation, or <code>null</code>
467
501
*/
468
502
private static GraphQLOperationDefinition asOperationOrNull (PsiElement operationCandidate ) {
469
- // if (operationCandidate instanceof JSGraphQLPsiElement) {
470
- // if (operationCandidate instanceof GraphQLFragmentDefinition) {
471
- // // fragments aren't operations
472
- // return null;
473
- // }
474
- // if (operationCandidate instanceof JSGraphQLSelectionSetPsiElement) {
475
- // if (!((JSGraphQLSelectionSetPsiElement) operationCandidate).isAnonymousQuery()) {
476
- // // selection set is not an anonymous query, so not considered an operation
477
- // return null;
478
- // }
479
- // }
480
- // // named queries, mutations, subscriptions
481
- // return operationCandidate;
482
- // }
483
- return null ;
503
+ return PsiTreeUtil .getParentOfType (operationCandidate , GraphQLOperationDefinition .class , false );
484
504
}
485
505
486
506
/**
@@ -497,17 +517,23 @@ private static void placeCaretInsideFirstOperation(Editor editor, PsiFile psiFil
497
517
}
498
518
GraphQLOperationDefinition operationOrNull = asOperationOrNull (psiElement );
499
519
if (operationOrNull != null ) {
520
+ PsiElement navigationTarget = operationOrNull ;
500
521
final Project project = editor .getProject ();
501
522
if (project != null ) {
502
523
// try to find the name of the operation
503
- // final JSGraphQLNamedTypePsiElement name = PsiTreeUtil.getChildOfType(operationOrNull, JSGraphQLNamedTypePsiElement.class);
504
- // if (name != null && name.isDefinition()) {
505
- // operationOrNull = name;
506
- // }
524
+ if (operationOrNull instanceof GraphQLSelectionSetOperationDefinition ) {
525
+ // unnamed query
526
+ final GraphQLSelectionSet selectionSet = ((GraphQLSelectionSetOperationDefinition ) operationOrNull ).getSelectionSet ();
527
+ if (selectionSet != null ) {
528
+ navigationTarget = selectionSet ;
529
+ }
530
+ } else if (operationOrNull .getNameIdentifier () != null ) {
531
+ navigationTarget = operationOrNull .getNameIdentifier ();
532
+ }
507
533
final FileEditorManager fileEditorManager = FileEditorManager .getInstance (project );
508
534
fileEditorManager .openFile (psiFile .getVirtualFile (), true , true );
509
535
editor .getSelectionModel ().removeSelection ();
510
- editor .getCaretModel ().moveToOffset (operationOrNull .getTextOffset ());
536
+ editor .getCaretModel ().moveToOffset (navigationTarget .getTextOffset ());
511
537
editor .getScrollingModel ().scrollToCaret (ScrollType .CENTER );
512
538
}
513
539
return ;
@@ -520,48 +546,50 @@ private static void placeCaretInsideFirstOperation(Editor editor, PsiFile psiFil
520
546
* Locates the fragments used from inside an operation, and the fragments, if any, that are used from within those fragments
521
547
*
522
548
* @param operationOrFragment the operation to find used fragments for
523
- * @param foundFragments a set to add the found fragments to
549
+ * @param foundFragments a map fragments map keyed by filename:fragment-name to add the found fragments to
524
550
* @param findMore optional function to stop once a specific fragment has been found
525
551
*/
526
- private static void findFragmentsInsideOperation (PsiElement operationOrFragment , Set < GraphQLFragmentDefinition > foundFragments ,
552
+ private static void findFragmentsInsideOperation (PsiElement operationOrFragment , Map < String , GraphQLFragmentDefinition > foundFragments ,
527
553
Function <GraphQLFragmentDefinition , Boolean > findMore ) {
528
554
529
- // operationOrFragment.accept(new PsiRecursiveElementVisitor() {
530
- //
531
- // private boolean done = false;
532
- //
533
- // @Override
534
- // public void visitElement(PsiElement element) {
535
- // if (done) {
536
- // return;
537
- // }
538
- // if (element instanceof JSGraphQLNamedTypePsiElement) {
539
- // if (((JSGraphQLNamedTypePsiElement) element).isDefinition()) {
540
- // final PsiReference reference = element.getReference();
541
- // if (reference != null) {
542
- // final PsiElement declaration = reference.resolve();
543
- // if (declaration instanceof JSGraphQLNamedTypePsiElement) {
544
- // JSGraphQLNamedTypePsiElement namedType = (JSGraphQLNamedTypePsiElement) declaration;
545
- // if (namedType.getParent() instanceof GraphQLFragmentDefinition) {
546
- // final GraphQLFragmentDefinition fragment = (GraphQLFragmentDefinition) namedType.getParent();
547
- // if (!foundFragments.contains(fragment)) {
548
- // foundFragments.add(fragment);
549
- // if (findMore != null && !findMore.apply(fragment)) {
550
- // // we're done
551
- // done = true;
552
- // return;
553
- // }
554
- // // also look for fragments inside this fragment
555
- // findFragmentsInsideOperation(fragment, foundFragments, findMore);
556
- // }
557
- // }
558
- // }
559
- // }
560
- // }
561
- // }
562
- // super.visitElement(element);
563
- // }
564
- // });
555
+ operationOrFragment .accept (new PsiRecursiveElementVisitor () {
556
+
557
+ private boolean done = false ;
558
+
559
+ @ Override
560
+ public void visitElement (PsiElement element ) {
561
+ if (done ) {
562
+ return ;
563
+ }
564
+ if (element instanceof GraphQLFragmentSpread ) {
565
+ final GraphQLIdentifier fragmentSpreadName = ((GraphQLFragmentSpread ) element ).getNameIdentifier ();
566
+ final PsiReference reference = fragmentSpreadName != null ? fragmentSpreadName .getReference () : null ;
567
+ if (reference != null ) {
568
+ PsiElement fragmentDefinitionRef = reference .resolve ();
569
+ if (fragmentDefinitionRef instanceof GraphQLIdentifier ) {
570
+ if (fragmentDefinitionRef .getOriginalElement () instanceof GraphQLIdentifier ) {
571
+ fragmentDefinitionRef = fragmentDefinitionRef .getOriginalElement ();
572
+ }
573
+ final GraphQLFragmentDefinition fragment = PsiTreeUtil .getParentOfType (fragmentDefinitionRef , GraphQLFragmentDefinition .class );
574
+ final String fragmentKey = getFragmentKey (fragment );
575
+ if (fragment != null && !foundFragments .containsKey (fragmentKey )) {
576
+ foundFragments .put (fragmentKey , fragment );
577
+ if (findMore != null && !findMore .apply (fragment )) {
578
+ // we're done
579
+ done = true ;
580
+ return ;
581
+ }
582
+ // also look for fragments inside this fragment
583
+ findFragmentsInsideOperation (fragment , foundFragments , findMore );
584
+ }
585
+
586
+ }
587
+ }
588
+
589
+ }
590
+ super .visitElement (element );
591
+ }
592
+ });
565
593
}
566
594
567
595
}
0 commit comments