From bb60183fe7b0a7b88a86c91702368e8ebe3d352d Mon Sep 17 00:00:00 2001 From: Ville Hakulinen Date: Wed, 20 Apr 2016 17:54:26 +0300 Subject: [PATCH] Quickfixes (#3) --- neovim-intellij-complete.iml | 3 +- src/NeovimIntellijComplete.java | 99 ++++++++++++++++++---- src/codeinspect/Inspect.java | 143 ++++++++++++++++++++++++++++++++ src/complete/EmbeditorUtil.java | 2 +- 4 files changed, 230 insertions(+), 17 deletions(-) create mode 100644 src/codeinspect/Inspect.java diff --git a/neovim-intellij-complete.iml b/neovim-intellij-complete.iml index f78988f..227d8f9 100644 --- a/neovim-intellij-complete.iml +++ b/neovim-intellij-complete.iml @@ -9,8 +9,7 @@ - - + \ No newline at end of file diff --git a/src/NeovimIntellijComplete.java b/src/NeovimIntellijComplete.java index 9b87359..bad43b0 100644 --- a/src/NeovimIntellijComplete.java +++ b/src/NeovimIntellijComplete.java @@ -1,31 +1,29 @@ -import com.google.common.net.HostAndPort; +import codeinspect.Inspect; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.intellij.codeInsight.CodeSmellInfo; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.DataKeys; -import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.CodeSmellDetector; +import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.file.PsiPackageImpl; import com.intellij.util.ui.UIUtil; -import com.neovim.*; +import com.neovim.Neovim; +import com.neovim.NeovimHandler; +import com.neovim.SocketNeovim; import com.neovim.msgpack.MessagePackRPC; -import complete.DeopleteHelper; -import complete.DeopleteItem; -import complete.EmbeditorRequestHandler; -import complete.Problem; +import complete.*; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class NeovimIntellijComplete extends AnAction { @@ -39,17 +37,60 @@ public static class Updater { private Neovim mNeovim; private EmbeditorRequestHandler mEmbeditorRequestHandler; + private Fix[] mCachedFixes = new Fix[0]; public Updater(Neovim nvim){ mNeovim = nvim; mEmbeditorRequestHandler = new EmbeditorRequestHandler(); } + /** + * Hack to have up to date files when doing quickfix stuff... + * @param path + */ + @NeovimHandler("IntellijOnWrite") + public void intellijOnWrite(String path) { + ApplicationManager.getApplication().invokeAndWait(() -> { + PsiFile f = EmbeditorUtil.findTargetFile(path); + if (f != null) { + VirtualFile vf = f.getVirtualFile(); + if (vf != null) + vf.refresh(false, true); + } + }, ModalityState.any()); + } + @NeovimHandler("TextChanged") public void changed(String args) { LOG.info("Text changed"); } + @NeovimHandler("IntellijFixProblem") + public void intellijFixProblem(String path, List lines, int fixId) { + final String fileContent = String.join("\n", lines) ; + for (Fix f : mCachedFixes) { + if (f.getFixId() == fixId) { + Inspect.doFix(path, fileContent, f.getAction()); + break; + } + } + + } + + @NeovimHandler("IntellijProblems") + public Fix[] intellijProblems(String path, List lines, final int row, final int col) { + final String fileContent = String.join("\n", lines) ; + List allFixes = Inspect.getFixes(path, fileContent, row, col); + List fixes = new ArrayList<>(); + for (int i = 0; i < allFixes.size(); i++) { + HighlightInfo.IntentionActionDescriptor d = allFixes.get(i); + if (d.getAction().getText().length() == 0) continue; + fixes.add(new Fix(d.getAction().getText(), i, d)); + } + mCachedFixes = fixes.toArray(new Fix[fixes.size()]); + return mCachedFixes; + } + @NeovimHandler("IntellijCodeSmell") public Problem[] intellijCodeSmell(final String path, final List lines) { final String fileContent = String.join("\n", lines); @@ -101,6 +142,34 @@ public DeopleteItem[] intellijComplete(final String path, final String bufferCon }); return dh.getItems(); } + + private class Fix { + @JsonProperty + private String description; + @JsonProperty + private int fixId; + + @JsonIgnore + private HighlightInfo.IntentionActionDescriptor action; + + public Fix(String description, int fixId, HighlightInfo.IntentionActionDescriptor action) { + this.description = description; + this.fixId = fixId; + this.action = action; + } + + public String getDescription() { + return description; + } + + public int getFixId() { + return fixId; + } + + public HighlightInfo.IntentionActionDescriptor getAction() { + return action; + } + } } public NeovimIntellijComplete() { @@ -128,6 +197,8 @@ public void actionPerformed(AnActionEvent e) { long cid = mNeovim.getChannelId().join(); mNeovim.commandOutput("let g:intellijID=" + cid); + // Refresh file on intellij on write so we can have uptodate stuff when doing codeanalyzis + mNeovim.commandOutput("au BufWritePost * call rpcnotify(g:intellijID, \"IntellijOnWrite\", expand(\"%:p\"))"); mNeovim.register(new Updater(mNeovim)); mNeovim.sendVimCommand("echo 'Intellij connected.'"); diff --git a/src/codeinspect/Inspect.java b/src/codeinspect/Inspect.java new file mode 100644 index 0000000..f75ae85 --- /dev/null +++ b/src/codeinspect/Inspect.java @@ -0,0 +1,143 @@ +package codeinspect; + +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx; +import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.impl.file.impl.FileManager; +import com.intellij.util.ui.UIUtil; +import complete.EmbeditorUtil; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by ville on 4/19/16. + */ +public class Inspect { + private final static Logger LOG = Logger.getInstance(Inspect.class); + + /** + * Runs code analyzis and returns all problems found under cursor (row and col). + * + * @param path + * @param fileContent + * @param row + * @param col + * @return + */ + public static List getFixes( + final String path, @Nullable final String fileContent, final int row, final int col) { + List fixes = new ArrayList<>(); + Pair> problems = getProblems(path, fileContent); + Document doc = problems.getFirst(); + for (HighlightInfo h : problems.getSecond()) { + if (h.quickFixActionRanges == null) continue; + for (Pair p : h.quickFixActionRanges) { + int offset = EmbeditorUtil.lineAndColumnToOffset(doc, row, col); + if (p.getSecond().contains(offset)) { + fixes.add(p.getFirst()); + } + } + } + return fixes; + } + + /** + * Runs code anlyzis on document found in path and returns all problems found with the document. + * @param path + * @param fileContent + * @return + */ + private static Pair> getProblems(final String path, @Nullable final String fileContent) { + final Ref psiFileRef = new Ref<>(); + final Ref editorRef = new Ref<>(); + final Ref docRef = new Ref<>(); + final Ref projectRef = new Ref<>(); + + UIUtil.invokeAndWaitIfNeeded((Runnable)() -> { + PsiFile targetPsiFile = EmbeditorUtil.findTargetFile(path); + VirtualFile targetVirtualFile = targetPsiFile.getVirtualFile(); + Project project = targetPsiFile.getProject(); + + PsiFile fileCopy = fileContent != null + ? EmbeditorUtil.createDummyPsiFile(project, fileContent, targetPsiFile) + : EmbeditorUtil.createDummyPsiFile(project, targetPsiFile.getText(), targetPsiFile); + + final Document document = fileCopy.getViewProvider().getDocument(); + + editorRef.set(EditorFactory.getInstance().createEditor(document, project, targetVirtualFile, false)); + psiFileRef.set(targetPsiFile); + docRef.set(document); + projectRef.set(project); + }); + Disposable context = Disposer.newDisposable(); + + Ref> highlightInfoList = new Ref<>(); + + ApplicationManager.getApplication().runReadAction(() -> { + final DaemonProgressIndicator progress = new DaemonProgressIndicator(); + Disposer.register(context, progress); + + ProgressManager.getInstance().runProcess(() -> { + + final DaemonCodeAnalyzerEx analyzer = + DaemonCodeAnalyzerEx.getInstanceEx(projectRef.get()); + //analyzer.restart(psiFileRef.get()); + + // analyze! + highlightInfoList.set(analyzer.runMainPasses( + psiFileRef.get(), docRef.get(), progress)); + }, progress); + }); + return Pair.create(docRef.get(), highlightInfoList.get()); + } + + /** + * Invokes action in intentionActionDescriptor on file found in path and writes the file to disk. + * + * @param path + * @param fileContent + * @param intentionActionDescriptor + * @return + */ + public static String doFix(String path, @Nullable String fileContent, HighlightInfo.IntentionActionDescriptor intentionActionDescriptor) { + UIUtil.invokeAndWaitIfNeeded((Runnable)() -> { + PsiFile psiFile = EmbeditorUtil.findTargetFile(path); + Project project = psiFile.getProject(); + + PsiFile fileCopy = fileContent != null + ? EmbeditorUtil.createDummyPsiFile(project, fileContent, psiFile) + : EmbeditorUtil.createDummyPsiFile(project, psiFile.getText(), psiFile); + + VirtualFile targetVirtualFile = psiFile.getVirtualFile(); + Document document = fileCopy.getViewProvider().getDocument(); + + Editor editor = EditorFactory.getInstance().createEditor(document, project, targetVirtualFile, false); + + intentionActionDescriptor.getAction().invoke(project, editor, fileCopy); + + FileDocumentManager.getInstance().saveDocument(psiFile.getViewProvider().getDocument()); + }); + return null; + } +} diff --git a/src/complete/EmbeditorUtil.java b/src/complete/EmbeditorUtil.java index d9aabc1..1faccec 100644 --- a/src/complete/EmbeditorUtil.java +++ b/src/complete/EmbeditorUtil.java @@ -200,7 +200,7 @@ public static PsiFile createDummyPsiFile(Project project, String contents, PsiFi return psiFile; } - private static int lineAndColumnToOffset(Document document, int line, int column) { + public static int lineAndColumnToOffset(Document document, int line, int column) { return document.getLineStartOffset(line) + column; }