Skip to content

Commit 7f7ee5a

Browse files
committed
Store caret position for contextual queries when it changes to avoid using the Editor UI from non-UI thread in query context highlighter (#256)
1 parent d4c42f9 commit 7f7ee5a

File tree

3 files changed

+63
-23
lines changed

3 files changed

+63
-23
lines changed

resources/META-INF/plugin.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
<postStartupActivity implementation="com.intellij.lang.jsgraphql.endpoint.ide.startup.GraphQLStartupActivity" />
114114
<postStartupActivity implementation="com.intellij.lang.jsgraphql.ide.project.graphqlconfig.GraphQLConfigProjectStartupActivity" />
115115
<postStartupActivity implementation="com.intellij.lang.jsgraphql.ide.project.relay.GraphQLRelayModernEnableStartupActivity" />
116+
<postStartupActivity implementation="com.intellij.lang.jsgraphql.v1.ide.editor.JSGraphQLQueryContextCaretListener" />
116117

117118
<!-- Syntax and error highlighting -->
118119
<lang.syntaxHighlighterFactory language="GraphQL" implementationClass="com.intellij.lang.jsgraphql.ide.GraphQLSyntaxHighlighterFactory"/>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (c) 2019-present, Jim Kynde Meyer
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
package com.intellij.lang.jsgraphql.v1.ide.editor;
9+
10+
import com.intellij.lang.jsgraphql.psi.GraphQLFile;
11+
import com.intellij.openapi.application.ApplicationManager;
12+
import com.intellij.openapi.editor.EditorFactory;
13+
import com.intellij.openapi.editor.event.CaretEvent;
14+
import com.intellij.openapi.editor.event.CaretListener;
15+
import com.intellij.openapi.editor.event.EditorEventMulticaster;
16+
import com.intellij.openapi.project.DumbAware;
17+
import com.intellij.openapi.project.Project;
18+
import com.intellij.openapi.startup.StartupActivity;
19+
import com.intellij.openapi.util.Key;
20+
import com.intellij.psi.PsiDocumentManager;
21+
import com.intellij.psi.PsiFile;
22+
import org.jetbrains.annotations.NotNull;
23+
24+
/**
25+
* Updates the current caret position in GraphQL files to enable contextual queries and highlighting of included fragments
26+
*/
27+
public class JSGraphQLQueryContextCaretListener implements StartupActivity, DumbAware {
28+
29+
static final Key<Integer> CARET_OFFSET = Key.create("JSGraphQL.QueryContext.CaretOffset");
30+
31+
@Override
32+
public void runActivity(@NotNull Project project) {
33+
if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
34+
final EditorEventMulticaster eventMulticaster = EditorFactory.getInstance().getEventMulticaster();
35+
final PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(project);
36+
eventMulticaster.addCaretListener(new CaretListener() {
37+
@Override
38+
public void caretPositionChanged(CaretEvent e) {
39+
final PsiFile psiFile = psiDocumentManager.getPsiFile(e.getEditor().getDocument());
40+
if (psiFile instanceof GraphQLFile) {
41+
int offset = e.getEditor().logicalPositionToOffset(e.getNewPosition());
42+
psiFile.putUserData(CARET_OFFSET, offset);
43+
}
44+
}
45+
}, project);
46+
}
47+
}
48+
}

src/main/com/intellij/lang/jsgraphql/v1/ide/editor/JSGraphQLQueryContextHighlightVisitor.java

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
import com.intellij.openapi.editor.ScrollType;
3333
import com.intellij.openapi.editor.colors.EditorColors;
3434
import com.intellij.openapi.editor.colors.EditorColorsManager;
35-
import com.intellij.openapi.editor.event.CaretAdapter;
3635
import com.intellij.openapi.editor.event.CaretEvent;
36+
import com.intellij.openapi.editor.event.CaretListener;
3737
import com.intellij.openapi.editor.markup.EffectType;
3838
import com.intellij.openapi.editor.markup.RangeHighlighter;
3939
import com.intellij.openapi.editor.markup.TextAttributes;
@@ -106,7 +106,7 @@ public boolean analyze(final @NotNull PsiFile file, boolean updateWholeFile, @No
106106
// run the default pass first (DefaultHighlightVisitor) which calls annotators etc.
107107
action.run();
108108

109-
final PsiElement operationAtCursor = getOperationAtCursor(file, null);
109+
final PsiElement operationAtCursor = getOperationAtCursor(file);
110110
if (operationAtCursor != null && hasMultipleVisibleTopLevelElement(file)) {
111111

112112
// store the range of the current operation for use in the caret listener
@@ -146,7 +146,7 @@ public boolean analyze(final @NotNull PsiFile file, boolean updateWholeFile, @No
146146
if (fileEditor instanceof TextEditor) {
147147
final Editor editor = ((TextEditor) fileEditor).getEditor();
148148
if (!Boolean.TRUE.equals(editor.getUserData(QUERY_HIGHLIGHT_LISTENER_ADDED))) {
149-
editor.getCaretModel().addCaretListener(new CaretAdapter() {
149+
editor.getCaretModel().addCaretListener(new CaretListener() {
150150
@Override
151151
public void caretPositionChanged(CaretEvent e) {
152152
// re-highlight when the operation changes
@@ -178,7 +178,7 @@ public void caretPositionChanged(CaretEvent e) {
178178

179179
if (!sameOperation) {
180180
// moved to somewhere outside the previous operation
181-
if (hadOperation || getOperationAtCursor(psiFile, e) != null) {
181+
if (hadOperation || getOperationAtCursor(psiFile) != null) {
182182
// perform a new highlighting pass
183183
DaemonCodeAnalyzer.getInstance(project).restart(psiFile);
184184
}
@@ -341,7 +341,7 @@ public static JSGraphQLQueryContext getQueryContextBufferAndHighlightUnused(fina
341341

342342
// no selection -- see if the caret is inside an operation
343343

344-
final GraphQLOperationDefinition operationAtCursor = getOperationAtCursor(psiFile, null);
344+
final GraphQLOperationDefinition operationAtCursor = getOperationAtCursor(psiFile);
345345
if (operationAtCursor != null) {
346346
final Map<String, GraphQLFragmentDefinition> foundFragments = Maps.newHashMap();
347347
findFragmentsInsideOperation(operationAtCursor, foundFragments, null);
@@ -471,27 +471,18 @@ private static void showQueryContextHint(Editor editor, String hintText) {
471471
* Gets the operation that wraps the current caret position, or <code>null</code> if none is found,
472472
* e.g. when outside any operation or inside a fragment definition
473473
*/
474-
private static GraphQLOperationDefinition getOperationAtCursor(PsiFile psiFile, CaretEvent caretEvent) {
475-
final FileEditor fileEditor = FileEditorManager.getInstance(psiFile.getProject()).getSelectedEditor(psiFile.getVirtualFile());
476-
if (fileEditor instanceof TextEditor) {
477-
final Editor editor = ((TextEditor) fileEditor).getEditor();
478-
final int currentOffset;
479-
if (caretEvent != null) {
480-
currentOffset = editor.logicalPositionToOffset(caretEvent.getNewPosition());
481-
} else {
482-
try {
483-
currentOffset = editor.getCaretModel().getOffset();
484-
} catch (Throwable e) {
485-
// a caret update is in progress, so can't determine the operation at this time,
486-
// but a new caretPositionChanged event will follow
487-
return null;
488-
}
489-
}
490-
PsiElement currentElement = psiFile.findElementAt(currentOffset);
474+
private static GraphQLOperationDefinition getOperationAtCursor(PsiFile psiFile) {
475+
476+
final Integer caretOffset = psiFile.getUserData(JSGraphQLQueryContextCaretListener.CARET_OFFSET);
477+
478+
if (caretOffset != null) {
479+
PsiElement currentElement = psiFile.findElementAt(caretOffset);
491480
while (currentElement != null && !(currentElement.getParent() instanceof PsiFile)) {
492481
currentElement = currentElement.getParent();
493482
}
494-
return asOperationOrNull(currentElement);
483+
if (currentElement != null) {
484+
return asOperationOrNull(currentElement);
485+
}
495486
}
496487
return null;
497488
}

0 commit comments

Comments
 (0)