1- /**
1+ /*
22 * Copyright (c) 2015-present, Jim Kynde Meyer
33 * All rights reserved.
44 * <p>
88package com .intellij .lang .jsgraphql .v1 .ide .editor ;
99
1010import com .google .common .collect .Lists ;
11+ import com .google .common .collect .Maps ;
1112import com .google .common .collect .Sets ;
1213import com .intellij .codeInsight .daemon .DaemonCodeAnalyzer ;
1314import com .intellij .codeInsight .daemon .impl .HighlightInfo ;
2122import com .intellij .ide .util .PropertiesComponent ;
2223import com .intellij .lang .annotation .Annotation ;
2324import com .intellij .lang .annotation .HighlightSeverity ;
25+ import com .intellij .lang .jsgraphql .ide .project .GraphQLPsiSearchHelper ;
2426import com .intellij .lang .jsgraphql .psi .*;
2527import com .intellij .notification .Notification ;
2628import com .intellij .notification .NotificationType ;
5153
5254import javax .swing .*;
5355import 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 .*;
5857import java .util .function .Function ;
5958import java .util .stream .Collectors ;
6059import java .util .stream .Stream ;
@@ -104,6 +103,9 @@ public void visit(@NotNull PsiElement element) {
104103 @ Override
105104 public boolean analyze (final @ NotNull PsiFile file , boolean updateWholeFile , @ NotNull HighlightInfoHolder holder , @ NotNull Runnable action ) {
106105
106+ // run the default pass first (DefaultHighlightVisitor) which calls annotators etc.
107+ action .run ();
108+
107109 final PsiElement operationAtCursor = getOperationAtCursor (file , null );
108110 if (operationAtCursor != null && hasMultipleVisibleTopLevelElement (file )) {
109111
@@ -112,12 +114,17 @@ public boolean analyze(final @NotNull PsiFile file, boolean updateWholeFile, @No
112114
113115 final Color borderColor = EditorColorsManager .getInstance ().getGlobalScheme ().getColor (EditorColors .TEARLINE_COLOR );
114116 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 ();
116118 findFragmentsInsideOperation (operationAtCursor , foundFragments , null );
117119 for (PsiElement psiElement : file .getChildren ()) {
118120 boolean showAsUsed = false ;
119121 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 ));
121128 } else if (psiElement == operationAtCursor ) {
122129 showAsUsed = true ;
123130 }
@@ -188,8 +195,14 @@ public void caretPositionChanged(CaretEvent e) {
188195 return true ;
189196 }
190197
198+ private static String getFragmentKey (GraphQLFragmentDefinition definition ) {
199+ if (definition == null ) {
200+ return "" ;
201+ }
202+ return GraphQLPsiSearchHelper .getFileName (definition .getContainingFile ()) + ":" + definition .getName ();
203+ }
204+
191205 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!!
193206 HighlightManagerImpl highlightManager = (HighlightManagerImpl ) HighlightManager .getInstance (project );
194207 for (RangeHighlighter rangeHighlighter : highlightManager .getHighlighters (editor )) {
195208 highlightManager .removeSegmentHighlighter (editor , rangeHighlighter );
@@ -328,11 +341,11 @@ public static JSGraphQLQueryContext getQueryContextBufferAndHighlightUnused(fina
328341
329342 // no selection -- see if the caret is inside an operation
330343
331- final PsiElement operationAtCursor = getOperationAtCursor (psiFile , null );
344+ final GraphQLOperationDefinition operationAtCursor = getOperationAtCursor (psiFile , null );
332345 if (operationAtCursor != null ) {
333- final HashSet < GraphQLFragmentDefinition > foundFragments = Sets . newHashSet ();
346+ final Map < String , GraphQLFragmentDefinition > foundFragments = Maps . newHashMap ();
334347 findFragmentsInsideOperation (operationAtCursor , foundFragments , null );
335- Set <PsiElement > queryElements = Sets .newHashSet (foundFragments );
348+ Set <PsiElement > queryElements = Sets .newHashSet (foundFragments . values () );
336349 queryElements .add (operationAtCursor );
337350 final StringBuilder query = new StringBuilder (editorLength );
338351 final TextAttributes unusedTextAttributes = getUnusedTextAttributes ();
@@ -343,8 +356,16 @@ public static JSGraphQLQueryContext getQueryContextBufferAndHighlightUnused(fina
343356 }
344357 } else {
345358 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 )) {
347364 queryElements .remove (psiElement );
365+ GraphQLFragmentDefinition fragmentDefinition = foundFragments .get (fragmentKey );
366+ if (fragmentDefinition != null ) {
367+ queryElements .remove (fragmentDefinition );
368+ }
348369 query .append (editorBuffer .subSequence (textRange .getStartOffset (), textRange .getEndOffset ()));
349370 } else {
350371 if (!queryElements .isEmpty ()) {
@@ -365,12 +386,20 @@ public static JSGraphQLQueryContext getQueryContextBufferAndHighlightUnused(fina
365386 }
366387 }
367388 }
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 ));
374403 }
375404 return new JSGraphQLQueryContext (query .toString (), null );
376405 }
@@ -410,9 +439,18 @@ private static TextAttributes getUnusedTextAttributes() {
410439 }
411440
412441 /**
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"
414443 */
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+ }
416454 return "operation" ;
417455 }
418456
@@ -434,10 +472,6 @@ private static void showQueryContextHint(Editor editor, String hintText) {
434472 * e.g. when outside any operation or inside a fragment definition
435473 */
436474 private static GraphQLOperationDefinition getOperationAtCursor (PsiFile psiFile , CaretEvent caretEvent ) {
437- if (true ) {
438- // TODO refactor to v2 PSI
439- return null ;
440- }
441475 final FileEditor fileEditor = FileEditorManager .getInstance (psiFile .getProject ()).getSelectedEditor (psiFile .getVirtualFile ());
442476 if (fileEditor instanceof TextEditor ) {
443477 final Editor editor = ((TextEditor ) fileEditor ).getEditor ();
@@ -466,21 +500,7 @@ private static GraphQLOperationDefinition getOperationAtCursor(PsiFile psiFile,
466500 * Returns the operation candidate if it's an operation, or <code>null</code>
467501 */
468502 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 );
484504 }
485505
486506 /**
@@ -497,17 +517,23 @@ private static void placeCaretInsideFirstOperation(Editor editor, PsiFile psiFil
497517 }
498518 GraphQLOperationDefinition operationOrNull = asOperationOrNull (psiElement );
499519 if (operationOrNull != null ) {
520+ PsiElement navigationTarget = operationOrNull ;
500521 final Project project = editor .getProject ();
501522 if (project != null ) {
502523 // 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+ }
507533 final FileEditorManager fileEditorManager = FileEditorManager .getInstance (project );
508534 fileEditorManager .openFile (psiFile .getVirtualFile (), true , true );
509535 editor .getSelectionModel ().removeSelection ();
510- editor .getCaretModel ().moveToOffset (operationOrNull .getTextOffset ());
536+ editor .getCaretModel ().moveToOffset (navigationTarget .getTextOffset ());
511537 editor .getScrollingModel ().scrollToCaret (ScrollType .CENTER );
512538 }
513539 return ;
@@ -520,48 +546,50 @@ private static void placeCaretInsideFirstOperation(Editor editor, PsiFile psiFil
520546 * Locates the fragments used from inside an operation, and the fragments, if any, that are used from within those fragments
521547 *
522548 * @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
524550 * @param findMore optional function to stop once a specific fragment has been found
525551 */
526- private static void findFragmentsInsideOperation (PsiElement operationOrFragment , Set < GraphQLFragmentDefinition > foundFragments ,
552+ private static void findFragmentsInsideOperation (PsiElement operationOrFragment , Map < String , GraphQLFragmentDefinition > foundFragments ,
527553 Function <GraphQLFragmentDefinition , Boolean > findMore ) {
528554
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+ });
565593 }
566594
567595}
0 commit comments