Don't block EDT while counting information for Resolve and Bytecode window
This commit is contained in:
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2012 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.jet.plugin.internal;
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.Editor;
|
||||||
|
import com.intellij.openapi.editor.ex.EditorEx;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
|
import com.intellij.psi.PsiFile;
|
||||||
|
import com.intellij.psi.PsiManager;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.jet.lang.psi.JetFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nikolay Krasko
|
||||||
|
*/
|
||||||
|
public class Location {
|
||||||
|
@Nullable
|
||||||
|
final Editor editor;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
final JetFile jetFile;
|
||||||
|
|
||||||
|
final long modificationStamp;
|
||||||
|
|
||||||
|
final int startOffset;
|
||||||
|
final int endOffset;
|
||||||
|
|
||||||
|
private Location(@Nullable Editor editor, Project project) {
|
||||||
|
this.editor = editor;
|
||||||
|
|
||||||
|
if (editor != null) {
|
||||||
|
modificationStamp = editor.getDocument().getModificationStamp();
|
||||||
|
startOffset = editor.getSelectionModel().getSelectionStart();
|
||||||
|
endOffset = editor.getSelectionModel().getSelectionEnd();
|
||||||
|
|
||||||
|
VirtualFile vFile = ((EditorEx) editor).getVirtualFile();
|
||||||
|
if (vFile == null) {
|
||||||
|
jetFile = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PsiFile psiFile = PsiManager.getInstance(project).findFile(vFile);
|
||||||
|
jetFile = psiFile instanceof JetFile ? (JetFile) psiFile : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
modificationStamp = 0;
|
||||||
|
startOffset = 0;
|
||||||
|
endOffset = 0;
|
||||||
|
jetFile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location fromEditor(Editor editor, Project project) {
|
||||||
|
return new Location(editor, project);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public JetFile getJetFile() {
|
||||||
|
return jetFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Editor getEditor() {
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartOffset() {
|
||||||
|
return startOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEndOffset() {
|
||||||
|
return endOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof Location)) return false;
|
||||||
|
|
||||||
|
Location location = (Location)o;
|
||||||
|
|
||||||
|
if (modificationStamp != location.modificationStamp) return false;
|
||||||
|
if (endOffset != location.endOffset) return false;
|
||||||
|
if (startOffset != location.startOffset) return false;
|
||||||
|
if (editor != null ? !editor.equals(location.editor) : location.editor != null) return false;
|
||||||
|
if (jetFile != null ? !jetFile.equals(location.jetFile) : location.jetFile != null) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = editor != null ? editor.hashCode() : 0;
|
||||||
|
result = 31 * result + (jetFile != null ? jetFile.hashCode() : 0);
|
||||||
|
result = 31 * result + (int)(modificationStamp ^ (modificationStamp >>> 32));
|
||||||
|
result = 31 * result + startOffset;
|
||||||
|
result = 31 * result + endOffset;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,22 +27,21 @@ import com.intellij.openapi.editor.Document;
|
|||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor;
|
||||||
import com.intellij.openapi.editor.EditorFactory;
|
import com.intellij.openapi.editor.EditorFactory;
|
||||||
import com.intellij.openapi.editor.ScrollType;
|
import com.intellij.openapi.editor.ScrollType;
|
||||||
import com.intellij.openapi.editor.ex.EditorEx;
|
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||||
|
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.openapi.util.Comparing;
|
|
||||||
import com.intellij.openapi.util.Pair;
|
import com.intellij.openapi.util.Pair;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
|
||||||
import com.intellij.psi.PsiFile;
|
|
||||||
import com.intellij.psi.PsiManager;
|
|
||||||
import com.intellij.util.Alarm;
|
import com.intellij.util.Alarm;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.jet.analyzer.AnalyzeExhaust;
|
import org.jetbrains.jet.analyzer.AnalyzeExhaust;
|
||||||
import org.jetbrains.jet.codegen.ClassBuilderFactories;
|
import org.jetbrains.jet.codegen.ClassBuilderFactories;
|
||||||
import org.jetbrains.jet.codegen.ClassFileFactory;
|
import org.jetbrains.jet.codegen.ClassFileFactory;
|
||||||
import org.jetbrains.jet.codegen.CompilationErrorHandler;
|
import org.jetbrains.jet.codegen.CompilationErrorHandler;
|
||||||
import org.jetbrains.jet.codegen.GenerationState;
|
import org.jetbrains.jet.codegen.GenerationState;
|
||||||
import org.jetbrains.jet.lang.psi.JetFile;
|
import org.jetbrains.jet.lang.psi.JetFile;
|
||||||
|
import org.jetbrains.jet.plugin.internal.Location;
|
||||||
import org.jetbrains.jet.plugin.project.WholeProjectAnalyzerFacade;
|
import org.jetbrains.jet.plugin.project.WholeProjectAnalyzerFacade;
|
||||||
|
import org.jetbrains.jet.plugin.util.LongRunningReadTask;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -54,67 +53,89 @@ import java.util.List;
|
|||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
public class BytecodeToolwindow extends JPanel implements Disposable {
|
public class BytecodeToolwindow extends JPanel implements Disposable {
|
||||||
private static final int UPDATE_DELAY = 500;
|
private static final int UPDATE_DELAY = 1000;
|
||||||
private static final String DEFAULT_TEXT = "/*\n" +
|
private static final String DEFAULT_TEXT = "/*\n" +
|
||||||
"Generated bytecode for Kotlin source file.\n" +
|
"Generated bytecode for Kotlin source file.\n" +
|
||||||
"No Kotlin source file is opened.\n" +
|
"No Kotlin source file is opened.\n" +
|
||||||
"*/";
|
"*/";
|
||||||
|
|
||||||
private final Editor myEditor;
|
public class UpdateBytecodeToolWindowTask extends LongRunningReadTask<Location, String> {
|
||||||
private final Alarm myUpdateAlarm;
|
@Override
|
||||||
private Location myCurrentLocation;
|
protected Location prepareRequestInfo() {
|
||||||
private final Project myProject;
|
Location location = Location.fromEditor(FileEditorManager.getInstance(myProject).getSelectedTextEditor(), myProject);
|
||||||
|
if (location.getEditor() == null || location.getJetFile() == null) {
|
||||||
|
return null;
|
||||||
public BytecodeToolwindow(Project project) {
|
|
||||||
super(new BorderLayout());
|
|
||||||
myProject = project;
|
|
||||||
myEditor =
|
|
||||||
EditorFactory.getInstance().createEditor(EditorFactory.getInstance().createDocument(""), project, JavaFileType.INSTANCE, true);
|
|
||||||
add(myEditor.getComponent());
|
|
||||||
myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, this);
|
|
||||||
myUpdateAlarm.addRequest(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
myUpdateAlarm.addRequest(this, UPDATE_DELAY);
|
|
||||||
Location location = Location.fromEditor(FileEditorManager.getInstance(myProject).getSelectedTextEditor());
|
|
||||||
if (!Comparing.equal(location, myCurrentLocation)) {
|
|
||||||
updateBytecode(location, myCurrentLocation);
|
|
||||||
myCurrentLocation = location;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, UPDATE_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBytecode(Location location, Location oldLocation) {
|
return location;
|
||||||
Editor editor = location.editor;
|
}
|
||||||
|
|
||||||
if (editor == null) {
|
@NotNull
|
||||||
|
@Override
|
||||||
|
protected Location cloneRequestInfo(@NotNull Location location) {
|
||||||
|
Location newLocation = super.cloneRequestInfo(location);
|
||||||
|
assert location.equals(newLocation) : "cloneRequestInfo should generate same location object";
|
||||||
|
return newLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void hideResultOnInvalidLocation() {
|
||||||
setText(DEFAULT_TEXT);
|
setText(DEFAULT_TEXT);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
VirtualFile vFile = ((EditorEx)editor).getVirtualFile();
|
@NotNull
|
||||||
if (vFile == null) {
|
@Override
|
||||||
setText(DEFAULT_TEXT);
|
protected String processRequest(@NotNull Location location) {
|
||||||
|
JetFile jetFile = location.getJetFile();
|
||||||
|
assert jetFile != null;
|
||||||
|
|
||||||
|
GenerationState state;
|
||||||
|
try {
|
||||||
|
AnalyzeExhaust binding = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile(jetFile);
|
||||||
|
if (binding.isError()) {
|
||||||
|
return printStackTraceToString(binding.getError());
|
||||||
|
}
|
||||||
|
state = new GenerationState(jetFile.getProject(), ClassBuilderFactories.TEXT, binding, Collections.singletonList(jetFile));
|
||||||
|
state.compileCorrectFiles(CompilationErrorHandler.THROW_EXCEPTION);
|
||||||
|
}
|
||||||
|
catch (ProcessCanceledException e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return printStackTraceToString(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder answer = new StringBuilder();
|
||||||
|
|
||||||
|
final ClassFileFactory factory = state.getFactory();
|
||||||
|
for (String filename : factory.files()) {
|
||||||
|
answer.append("// ================ ");
|
||||||
|
answer.append(filename);
|
||||||
|
answer.append(" =================\n");
|
||||||
|
answer.append(factory.asText(filename)).append("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResultReady(@NotNull Location requestInfo, final String resultText) {
|
||||||
|
Editor editor = requestInfo.getEditor();
|
||||||
|
assert editor != null;
|
||||||
|
|
||||||
|
if (resultText == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile);
|
setText(resultText);
|
||||||
if (!(psiFile instanceof JetFile)) {
|
|
||||||
setText(DEFAULT_TEXT);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldLocation == null ||
|
int fileStartOffset = requestInfo.getStartOffset();
|
||||||
!Comparing.equal(oldLocation.editor, location.editor) ||
|
int fileEndOffset = requestInfo.getEndOffset();
|
||||||
oldLocation.modificationStamp != location.modificationStamp) {
|
|
||||||
setText(generateToText((JetFile)psiFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
Document document = editor.getDocument();
|
Document document = editor.getDocument();
|
||||||
int startLine = document.getLineNumber(location.startOffset);
|
int startLine = document.getLineNumber(fileStartOffset);
|
||||||
int endLine = document.getLineNumber(location.endOffset);
|
int endLine = document.getLineNumber(fileEndOffset);
|
||||||
if (endLine > startLine && location.endOffset > 0 && document.getCharsSequence().charAt(location.endOffset - 1) == '\n') {
|
if (endLine > startLine && fileEndOffset > 0 && document.getCharsSequence().charAt(fileEndOffset - 1) == '\n') {
|
||||||
endLine--;
|
endLine--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +156,37 @@ public class BytecodeToolwindow extends JPanel implements Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Editor myEditor;
|
||||||
|
private final Alarm myUpdateAlarm;
|
||||||
|
private final Project myProject;
|
||||||
|
|
||||||
|
private UpdateBytecodeToolWindowTask currentTask = null;
|
||||||
|
|
||||||
|
public BytecodeToolwindow(Project project) {
|
||||||
|
super(new BorderLayout());
|
||||||
|
myProject = project;
|
||||||
|
myEditor = EditorFactory.getInstance().createEditor(
|
||||||
|
EditorFactory.getInstance().createDocument(""), project, JavaFileType.INSTANCE, true);
|
||||||
|
add(myEditor.getComponent());
|
||||||
|
|
||||||
|
myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, this);
|
||||||
|
myUpdateAlarm.addRequest(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
myUpdateAlarm.addRequest(this, UPDATE_DELAY);
|
||||||
|
UpdateBytecodeToolWindowTask task = new UpdateBytecodeToolWindowTask();
|
||||||
|
task.init();
|
||||||
|
|
||||||
|
if (task.shouldStart(currentTask)) {
|
||||||
|
currentTask = task;
|
||||||
|
currentTask.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, UPDATE_DELAY);
|
||||||
|
|
||||||
|
setText(DEFAULT_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
private static Pair<Integer, Integer> mapLines(String text, int startLine, int endLine) {
|
private static Pair<Integer, Integer> mapLines(String text, int startLine, int endLine) {
|
||||||
int byteCodeLine = 0;
|
int byteCodeLine = 0;
|
||||||
int byteCodeStartLine = -1;
|
int byteCodeStartLine = -1;
|
||||||
@@ -192,109 +244,29 @@ public class BytecodeToolwindow extends JPanel implements Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setText(final String text) {
|
private static String printStackTraceToString(Throwable e) {
|
||||||
|
StringWriter out = new StringWriter(1024);
|
||||||
|
PrintWriter printWriter = new PrintWriter(out);
|
||||||
|
try {
|
||||||
|
e.printStackTrace(printWriter);
|
||||||
|
return out.toString().replace("\r", "");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
printWriter.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setText(@NotNull final String resultText) {
|
||||||
new WriteCommandAction(myProject) {
|
new WriteCommandAction(myProject) {
|
||||||
@Override
|
@Override
|
||||||
protected void run(Result result) throws Throwable {
|
protected void run(Result result) throws Throwable {
|
||||||
myEditor.getDocument().setText(text);
|
myEditor.getDocument().setText(resultText);
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String generateToText(JetFile file) {
|
|
||||||
GenerationState state;
|
|
||||||
try {
|
|
||||||
AnalyzeExhaust binding = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile(file);
|
|
||||||
// AnalyzingUtils.throwExceptionOnErrors(binding);
|
|
||||||
if (binding.isError()) {
|
|
||||||
return printStackTraceToString(binding.getError());
|
|
||||||
}
|
|
||||||
state = new GenerationState(file.getProject(), ClassBuilderFactories.TEXT, binding, Collections.singletonList(file));
|
|
||||||
state.compileCorrectFiles(CompilationErrorHandler.THROW_EXCEPTION);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
return printStackTraceToString(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
StringBuilder answer = new StringBuilder();
|
|
||||||
|
|
||||||
final ClassFileFactory factory = state.getFactory();
|
|
||||||
for (String filename : factory.files()) {
|
|
||||||
answer.append("// ================ ");
|
|
||||||
answer.append(filename);
|
|
||||||
answer.append(" =================\n");
|
|
||||||
answer.append(factory.asText(filename)).append("\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return answer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String printStackTraceToString(Throwable e) {StringWriter out = new StringWriter(1024);
|
|
||||||
e.printStackTrace(new PrintWriter(out));
|
|
||||||
return out.toString().replace("\r", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
EditorFactory.getInstance().releaseEditor(myEditor);
|
EditorFactory.getInstance().releaseEditor(myEditor);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public static class Location {
|
|
||||||
final Editor editor;
|
|
||||||
final long modificationStamp;
|
|
||||||
final int startOffset;
|
|
||||||
final int endOffset;
|
|
||||||
|
|
||||||
private Location(Editor editor) {
|
|
||||||
this.editor = editor;
|
|
||||||
modificationStamp = editor != null ? editor.getDocument().getModificationStamp() : 0;
|
|
||||||
startOffset = editor != null ? editor.getSelectionModel().getSelectionStart() : 0;
|
|
||||||
endOffset = editor != null ? editor.getSelectionModel().getSelectionEnd() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Location fromEditor(Editor editor) {
|
|
||||||
return new Location(editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Editor getEditor() {
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getModificationStamp() {
|
|
||||||
return modificationStamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStartOffset() {
|
|
||||||
return startOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getEndOffset() {
|
|
||||||
return endOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (!(o instanceof Location)) return false;
|
|
||||||
|
|
||||||
Location location = (Location)o;
|
|
||||||
|
|
||||||
if (endOffset != location.endOffset) return false;
|
|
||||||
if (modificationStamp != location.modificationStamp) return false;
|
|
||||||
if (startOffset != location.startOffset) return false;
|
|
||||||
if (editor != null ? !editor.equals(location.editor) : location.editor != null) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = editor != null ? editor.hashCode() : 0;
|
|
||||||
result = 31 * result + (int)(modificationStamp ^ (modificationStamp >>> 32));
|
|
||||||
result = 31 * result + startOffset;
|
|
||||||
result = 31 * result + endOffset;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -24,17 +24,14 @@ import com.intellij.openapi.application.Result;
|
|||||||
import com.intellij.openapi.command.WriteCommandAction;
|
import com.intellij.openapi.command.WriteCommandAction;
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor;
|
||||||
import com.intellij.openapi.editor.EditorFactory;
|
import com.intellij.openapi.editor.EditorFactory;
|
||||||
import com.intellij.openapi.editor.ex.EditorEx;
|
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.openapi.util.Comparing;
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.psi.PsiFile;
|
import com.intellij.psi.PsiFile;
|
||||||
import com.intellij.psi.PsiManager;
|
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
import com.intellij.psi.util.PsiUtilCore;
|
import com.intellij.psi.util.PsiUtilCore;
|
||||||
import com.intellij.util.Alarm;
|
import com.intellij.util.Alarm;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.jet.lang.descriptors.*;
|
import org.jetbrains.jet.lang.descriptors.*;
|
||||||
import org.jetbrains.jet.lang.psi.JetElement;
|
import org.jetbrains.jet.lang.psi.JetElement;
|
||||||
@@ -47,10 +44,10 @@ import org.jetbrains.jet.lang.resolve.calls.inference.BoundsOwner;
|
|||||||
import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor;
|
import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor;
|
||||||
import org.jetbrains.jet.lang.types.JetType;
|
import org.jetbrains.jet.lang.types.JetType;
|
||||||
import org.jetbrains.jet.plugin.JetFileType;
|
import org.jetbrains.jet.plugin.JetFileType;
|
||||||
import org.jetbrains.jet.plugin.internal.codewindow.BytecodeToolwindow;
|
import org.jetbrains.jet.plugin.internal.Location;
|
||||||
import org.jetbrains.jet.plugin.project.WholeProjectAnalyzerFacade;
|
import org.jetbrains.jet.plugin.project.WholeProjectAnalyzerFacade;
|
||||||
|
import org.jetbrains.jet.plugin.util.LongRunningReadTask;
|
||||||
import org.jetbrains.jet.util.slicedmap.ReadOnlySlice;
|
import org.jetbrains.jet.util.slicedmap.ReadOnlySlice;
|
||||||
import org.jetbrains.jet.util.slicedmap.WritableSlice;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -67,110 +64,121 @@ public class ResolveToolwindow extends JPanel implements Disposable {
|
|||||||
|
|
||||||
public static final String BAR = "\n\n===\n\n";
|
public static final String BAR = "\n\n===\n\n";
|
||||||
|
|
||||||
private static final int UPDATE_DELAY = 500;
|
private static final int UPDATE_DELAY = 1000;
|
||||||
private static final String DEFAULT_TEXT = "/*\n" +
|
private static final String DEFAULT_TEXT = "/*\n" +
|
||||||
"Information about symbols resolved by\nKotlin compiler.\n" +
|
"Information about symbols resolved by\nKotlin compiler.\n" +
|
||||||
"No Kotlin source file is opened.\n" +
|
"No Kotlin source file is opened.\n" +
|
||||||
"*/";
|
"*/";
|
||||||
private final Editor myEditor;
|
|
||||||
private final Alarm myUpdateAlarm;
|
|
||||||
private BytecodeToolwindow.Location myCurrentLocation;
|
|
||||||
private final Project myProject;
|
|
||||||
|
|
||||||
|
private static final String NO_REFERENCE_TEXT = "/*\n" +
|
||||||
|
"Information about symbols resolved by\nKotlin compiler.\n" +
|
||||||
|
"Invalid place for getting reference information.\n" +
|
||||||
|
"*/";
|
||||||
|
|
||||||
|
private class UpdateResolveToolWindowTask extends LongRunningReadTask<Location, String> {
|
||||||
|
@Override
|
||||||
|
protected Location prepareRequestInfo() {
|
||||||
|
Location location = Location.fromEditor(FileEditorManager.getInstance(myProject).getSelectedTextEditor(), myProject);
|
||||||
|
if (location.getEditor() == null || location.getJetFile() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void hideResultOnInvalidLocation() {
|
||||||
|
setText(DEFAULT_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
protected String processRequest(@NotNull Location requestInfo) {
|
||||||
|
JetFile jetFile = requestInfo.getJetFile();
|
||||||
|
assert jetFile != null;
|
||||||
|
|
||||||
|
int startOffset = requestInfo.getStartOffset();
|
||||||
|
int endOffset = requestInfo.getEndOffset();
|
||||||
|
|
||||||
|
BindingContext bindingContext = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile(jetFile).getBindingContext();
|
||||||
|
|
||||||
|
PsiElement elementAtOffset;
|
||||||
|
if (startOffset == endOffset) {
|
||||||
|
elementAtOffset = PsiUtilCore.getElementAtOffset(jetFile, startOffset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PsiElement start = PsiUtilCore.getElementAtOffset(jetFile, startOffset);
|
||||||
|
PsiElement end = PsiUtilCore.getElementAtOffset(jetFile, endOffset - 1);
|
||||||
|
elementAtOffset = PsiTreeUtil.findCommonParent(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsiElement elementWithDebugInfo = findData(bindingContext, elementAtOffset, RESOLUTION_DEBUG_INFO);
|
||||||
|
if (elementWithDebugInfo != null) {
|
||||||
|
return renderDebugInfo(elementWithDebugInfo, bindingContext.get(RESOLUTION_DEBUG_INFO, elementWithDebugInfo), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
PsiElement elementWithResolvedCall = findData(bindingContext, elementAtOffset, (ReadOnlySlice) RESOLVED_CALL);
|
||||||
|
if (elementWithResolvedCall instanceof JetElement) {
|
||||||
|
return renderDebugInfo(elementWithResolvedCall, null,
|
||||||
|
bindingContext.get(RESOLVED_CALL, (JetElement) elementWithResolvedCall));
|
||||||
|
}
|
||||||
|
|
||||||
|
JetExpression parentExpression = (elementAtOffset instanceof JetExpression) ?
|
||||||
|
(JetExpression) elementAtOffset :
|
||||||
|
PsiTreeUtil.getParentOfType(elementAtOffset, JetExpression.class);
|
||||||
|
|
||||||
|
if (parentExpression != null) {
|
||||||
|
JetType type = bindingContext.get(EXPRESSION_TYPE, parentExpression);
|
||||||
|
String text = parentExpression + "|" + parentExpression.getText() + "| : " + type;
|
||||||
|
if (parentExpression instanceof JetReferenceExpression) {
|
||||||
|
JetReferenceExpression referenceExpression = (JetReferenceExpression) parentExpression;
|
||||||
|
DeclarationDescriptor target = bindingContext.get(REFERENCE_TARGET, referenceExpression);
|
||||||
|
text += "\nReference target: \n" + target;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_REFERENCE_TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResultReady(@NotNull Location requestInfo, @Nullable final String resultText) {
|
||||||
|
if (resultText != null) {
|
||||||
|
setText(resultText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Alarm myUpdateAlarm;
|
||||||
|
private final Editor myEditor;
|
||||||
|
private UpdateResolveToolWindowTask currentTask = null;
|
||||||
|
|
||||||
|
private final Project myProject;
|
||||||
|
|
||||||
public ResolveToolwindow(Project project) {
|
public ResolveToolwindow(Project project) {
|
||||||
super(new BorderLayout());
|
super(new BorderLayout());
|
||||||
myProject = project;
|
myProject = project;
|
||||||
myEditor = EditorFactory.getInstance().createEditor(EditorFactory.getInstance().createDocument(""), project, JetFileType.INSTANCE, true);
|
myEditor = EditorFactory.getInstance()
|
||||||
|
.createEditor(EditorFactory.getInstance().createDocument(""), project, JetFileType.INSTANCE, true);
|
||||||
add(myEditor.getComponent());
|
add(myEditor.getComponent());
|
||||||
myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, this);
|
myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, this);
|
||||||
myUpdateAlarm.addRequest(new Runnable() {
|
myUpdateAlarm.addRequest(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
myUpdateAlarm.addRequest(this, UPDATE_DELAY);
|
myUpdateAlarm.addRequest(this, UPDATE_DELAY);
|
||||||
BytecodeToolwindow.Location location = BytecodeToolwindow.Location.fromEditor(FileEditorManager.getInstance(myProject).getSelectedTextEditor());
|
UpdateResolveToolWindowTask task = new UpdateResolveToolWindowTask();
|
||||||
if (!Comparing.equal(location, myCurrentLocation)) {
|
task.init();
|
||||||
render(location, myCurrentLocation);
|
|
||||||
myCurrentLocation = location;
|
if (task.shouldStart(currentTask)) {
|
||||||
|
currentTask = task;
|
||||||
|
currentTask.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, UPDATE_DELAY);
|
}, UPDATE_DELAY);
|
||||||
}
|
|
||||||
|
|
||||||
private void render(BytecodeToolwindow.Location location, BytecodeToolwindow.Location oldLocation) {
|
setText(DEFAULT_TEXT);
|
||||||
Editor editor = location.getEditor();
|
|
||||||
if (editor == null) {
|
|
||||||
setText(DEFAULT_TEXT);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
VirtualFile vFile = ((EditorEx) editor).getVirtualFile();
|
|
||||||
if (vFile == null) {
|
|
||||||
setText(DEFAULT_TEXT);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile);
|
|
||||||
if (!(psiFile instanceof JetFile)) {
|
|
||||||
setText(DEFAULT_TEXT);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int startOffset = location.getStartOffset();
|
|
||||||
int endOffset = location.getEndOffset();
|
|
||||||
if (oldLocation == null || !Comparing.equal(oldLocation.getEditor(), location.getEditor())
|
|
||||||
|| oldLocation.getStartOffset() != startOffset
|
|
||||||
|| oldLocation.getEndOffset() != endOffset) {
|
|
||||||
|
|
||||||
BindingContext bindingContext = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile((JetFile) psiFile)
|
|
||||||
.getBindingContext();
|
|
||||||
|
|
||||||
|
|
||||||
PsiElement elementAtOffset;
|
|
||||||
if (startOffset == endOffset) {
|
|
||||||
elementAtOffset = PsiUtilCore.getElementAtOffset(psiFile, startOffset);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
PsiElement start = PsiUtilCore.getElementAtOffset(psiFile, startOffset);
|
|
||||||
PsiElement end = PsiUtilCore.getElementAtOffset(psiFile, endOffset - 1);
|
|
||||||
elementAtOffset = PsiTreeUtil.findCommonParent(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
PsiElement currentElement = elementAtOffset;
|
|
||||||
|
|
||||||
boolean callFound = false;
|
|
||||||
|
|
||||||
PsiElement elementWithDebugInfo = findData(bindingContext, currentElement, RESOLUTION_DEBUG_INFO);
|
|
||||||
if (elementWithDebugInfo != null) {
|
|
||||||
callFound = true;
|
|
||||||
setText(renderDebugInfo(elementWithDebugInfo, bindingContext.get(RESOLUTION_DEBUG_INFO, elementWithDebugInfo), null));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
PsiElement elementWithResolvedCall = findData(bindingContext, currentElement, (WritableSlice) RESOLVED_CALL);
|
|
||||||
if (elementWithResolvedCall instanceof JetElement) {
|
|
||||||
callFound = true;
|
|
||||||
setText(renderDebugInfo(elementWithResolvedCall, null, bindingContext.get(RESOLVED_CALL, (JetElement) elementWithResolvedCall)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!callFound) {
|
|
||||||
|
|
||||||
JetExpression parentExpression = (elementAtOffset instanceof JetExpression) ?
|
|
||||||
(JetExpression) elementAtOffset :
|
|
||||||
PsiTreeUtil.getParentOfType(elementAtOffset, JetExpression.class);
|
|
||||||
if (parentExpression != null) {
|
|
||||||
JetType type = bindingContext.get(EXPRESSION_TYPE, parentExpression);
|
|
||||||
String text = parentExpression + "|" + parentExpression.getText() + "| : " + type;
|
|
||||||
if (parentExpression instanceof JetReferenceExpression) {
|
|
||||||
JetReferenceExpression referenceExpression = (JetReferenceExpression) parentExpression;
|
|
||||||
DeclarationDescriptor target = bindingContext.get(REFERENCE_TARGET, referenceExpression);
|
|
||||||
text += "\nReference target: \n" + target;
|
|
||||||
}
|
|
||||||
setText(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -182,18 +190,22 @@ public class ResolveToolwindow extends JPanel implements Disposable {
|
|||||||
if (data != null && data != NO_DEBUG_INFO) {
|
if (data != null && data != NO_DEBUG_INFO) {
|
||||||
return currentElement;
|
return currentElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentElement = currentElement.getParent();
|
currentElement = currentElement.getParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String renderDebugInfo(PsiElement currentElement,
|
@NotNull
|
||||||
|
private static String renderDebugInfo(
|
||||||
|
PsiElement currentElement,
|
||||||
@Nullable ResolutionDebugInfo.Data debugInfo,
|
@Nullable ResolutionDebugInfo.Data debugInfo,
|
||||||
@Nullable ResolvedCall<? extends CallableDescriptor> call) {
|
@Nullable ResolvedCall<? extends CallableDescriptor> call
|
||||||
|
) {
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
if (debugInfo != null) {
|
if (debugInfo != null) {
|
||||||
List<? extends ResolutionTask<? extends CallableDescriptor, ?>> resolutionTasks = debugInfo.get(TASKS);
|
List<? extends ResolutionTask<? extends CallableDescriptor, ?>> resolutionTasks = debugInfo.get(TASKS);
|
||||||
for (ResolutionTask<? extends CallableDescriptor, ?> resolutionTask : resolutionTasks) {
|
for (ResolutionTask<? extends CallableDescriptor, ?> resolutionTask : resolutionTasks) {
|
||||||
@@ -215,9 +227,11 @@ public class ResolveToolwindow extends JPanel implements Disposable {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void renderResolutionLogForCall(Data debugInfo,
|
private static void renderResolutionLogForCall(
|
||||||
|
Data debugInfo,
|
||||||
ResolvedCallWithTrace<? extends CallableDescriptor> resolvedCall,
|
ResolvedCallWithTrace<? extends CallableDescriptor> resolvedCall,
|
||||||
StringBuilder result) {
|
StringBuilder result
|
||||||
|
) {
|
||||||
result.append("Trying to call ").append(resolvedCall.getCandidateDescriptor()).append("\n");
|
result.append("Trying to call ").append(resolvedCall.getCandidateDescriptor()).append("\n");
|
||||||
StringBuilder errors = debugInfo.getByKey(ERRORS, resolvedCall);
|
StringBuilder errors = debugInfo.getByKey(ERRORS, resolvedCall);
|
||||||
if (errors != null) {
|
if (errors != null) {
|
||||||
@@ -254,7 +268,6 @@ public class ResolveToolwindow extends JPanel implements Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String renderCall(StringBuilder builder, ResolvedCall<? extends CallableDescriptor> resolvedCall) {
|
private static String renderCall(StringBuilder builder, ResolvedCall<? extends CallableDescriptor> resolvedCall) {
|
||||||
|
|
||||||
CallableDescriptor resultingDescriptor = resolvedCall.getResultingDescriptor();
|
CallableDescriptor resultingDescriptor = resolvedCall.getResultingDescriptor();
|
||||||
ReceiverDescriptor receiverArgument = resolvedCall.getReceiverArgument();
|
ReceiverDescriptor receiverArgument = resolvedCall.getReceiverArgument();
|
||||||
ReceiverDescriptor thisObject = resolvedCall.getThisObject();
|
ReceiverDescriptor thisObject = resolvedCall.getThisObject();
|
||||||
@@ -329,17 +342,17 @@ public class ResolveToolwindow extends JPanel implements Disposable {
|
|||||||
if (receiverArgument.exists()) {
|
if (receiverArgument.exists()) {
|
||||||
builder.append("/").append(receiverArgument);
|
builder.append("/").append(receiverArgument);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thisObject.exists()) {
|
if (thisObject.exists()) {
|
||||||
builder.append("/this=").append(thisObject);
|
builder.append("/this=").append(thisObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thisObject.exists() || receiverArgument.exists()) {
|
if (thisObject.exists() || receiverArgument.exists()) {
|
||||||
builder.append("/.");
|
builder.append("/.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setText(final String text) {
|
private void setText(@NotNull final String text) {
|
||||||
new WriteCommandAction(myProject) {
|
new WriteCommandAction(myProject) {
|
||||||
@Override
|
@Override
|
||||||
protected void run(Result result) throws Throwable {
|
protected void run(Result result) throws Throwable {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public abstract class LongRunningReadTask<RequestInfo, ResultData> {
|
|||||||
INITIALIZED,
|
INITIALIZED,
|
||||||
STARTED,
|
STARTED,
|
||||||
FINISHED,
|
FINISHED,
|
||||||
FINISHED_AND_PROCESSED
|
FINISHED_WITH_ACTUAL_DATA
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProgressIndicator progressIndicator = null;
|
private ProgressIndicator progressIndicator = null;
|
||||||
@@ -45,7 +45,7 @@ public abstract class LongRunningReadTask<RequestInfo, ResultData> {
|
|||||||
protected LongRunningReadTask() {}
|
protected LongRunningReadTask() {}
|
||||||
|
|
||||||
/** Should be executed in GUI thread */
|
/** Should be executed in GUI thread */
|
||||||
public boolean isShouldStartWithCancel(@Nullable LongRunningReadTask<RequestInfo, ResultData> previousTask) {
|
public boolean shouldStart(@Nullable LongRunningReadTask<RequestInfo, ResultData> previousTask) {
|
||||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||||
|
|
||||||
if (currentState != State.INITIALIZED) {
|
if (currentState != State.INITIALIZED) {
|
||||||
@@ -60,20 +60,19 @@ public abstract class LongRunningReadTask<RequestInfo, ResultData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (requestInfo == null) {
|
if (requestInfo == null) {
|
||||||
if (previousTask != null && (previousTask.currentState == State.FINISHED_AND_PROCESSED || previousTask.currentState == State.FINISHED)) {
|
if (previousTask != null && (previousTask.currentState == State.FINISHED_WITH_ACTUAL_DATA || previousTask.currentState == State.FINISHED)) {
|
||||||
previousTask.hideResultOnInvalidLocation();
|
previousTask.hideResultOnInvalidLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (previousTask != null) {
|
if (previousTask != null) {
|
||||||
if (previousTask.currentState == State.STARTED) {
|
if (previousTask.currentState == State.STARTED) {
|
||||||
// Start new task only if previous isn't working on similar request
|
// Start new task only if previous isn't working on similar request
|
||||||
return !requestInfo.equals(previousTask.requestInfo);
|
return !requestInfo.equals(previousTask.requestInfo);
|
||||||
}
|
}
|
||||||
else if (previousTask.currentState == State.FINISHED_AND_PROCESSED) {
|
else if (previousTask.currentState == State.FINISHED_WITH_ACTUAL_DATA) {
|
||||||
if (requestInfo.equals(previousTask.requestInfo)) {
|
if (requestInfo.equals(previousTask.requestInfo)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -144,7 +143,7 @@ public abstract class LongRunningReadTask<RequestInfo, ResultData> {
|
|||||||
if (resultData != null) {
|
if (resultData != null) {
|
||||||
RequestInfo actualInfo = prepareRequestInfo();
|
RequestInfo actualInfo = prepareRequestInfo();
|
||||||
if (requestInfo.equals(actualInfo)) {
|
if (requestInfo.equals(actualInfo)) {
|
||||||
currentState = State.FINISHED_AND_PROCESSED;
|
currentState = State.FINISHED_WITH_ACTUAL_DATA;
|
||||||
onResultReady(actualInfo, resultData);
|
onResultReady(actualInfo, resultData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,6 +171,8 @@ public abstract class LongRunningReadTask<RequestInfo, ResultData> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Executed in GUI Thread.
|
* Executed in GUI Thread.
|
||||||
|
*
|
||||||
|
* @return null if current request is invalid and task shouldn't be executed.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
protected abstract RequestInfo prepareRequestInfo();
|
protected abstract RequestInfo prepareRequestInfo();
|
||||||
|
|||||||
Reference in New Issue
Block a user