diff --git a/build.gradle b/build.gradle index ad5b2424..f9b5cdbc 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,8 @@ apply plugin: 'org.jetbrains.intellij' apply plugin: 'antlr' compileJava { - sourceCompatibility = '1.8' - targetCompatibility = '1.8' + sourceCompatibility = '11' + targetCompatibility = '11' } intellij { diff --git a/gradle.properties b/gradle.properties index 02019b9a..7b408f85 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -pluginVersion=1.17 +pluginVersion=1.18 # e.g. IC-2016.3.3, IU-2018.2.5 etc # For a list of possible values, refer to the section 'com.jetbrains.intellij.idea' at @@ -12,7 +12,7 @@ pluginVersion=1.17 #ideaVersion=IC-2020.2.2 -ideaVersion=IC-2021.2 +ideaVersion=IC-2021.3 # The version of ANTLR v4 that will be used to generate the parser antlr4Version=4.9.3 diff --git a/src/main/java/org/antlr/intellij/plugin/ANTLRv4PluginController.java b/src/main/java/org/antlr/intellij/plugin/ANTLRv4PluginController.java index 278d48b6..462f23ea 100644 --- a/src/main/java/org/antlr/intellij/plugin/ANTLRv4PluginController.java +++ b/src/main/java/org/antlr/intellij/plugin/ANTLRv4PluginController.java @@ -1,6 +1,5 @@ package org.antlr.intellij.plugin; -import com.intellij.CommonBundle; import com.intellij.execution.filters.TextConsoleBuilder; import com.intellij.execution.filters.TextConsoleBuilderFactory; import com.intellij.execution.ui.ConsoleView; @@ -17,7 +16,10 @@ import com.intellij.openapi.editor.event.EditorMouseAdapter; import com.intellij.openapi.editor.event.EditorMouseEvent; import com.intellij.openapi.extensions.PluginId; -import com.intellij.openapi.fileEditor.*; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; @@ -25,7 +27,6 @@ import com.intellij.openapi.progress.util.ProgressWindow; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.OrderEnumerator; -import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; @@ -63,599 +64,601 @@ import java.net.URL; import java.util.*; -/** This object is the controller for the ANTLR plug-in. It receives - * events and can send them on to its contained components. For example, - * saving the grammar editor or flipping to a new grammar sends an event - * to this object, which forwards on update events to the preview tool window. - * - * The main components are related to the console tool window forever output and - * the main panel of the preview tool window. - * - * This controller also manages the cache of grammar/editor combinations - * needed for the preview window. Updates must be made atomically so that - * the grammars and editors are consistently associated with the same window. +/** + * This object is the controller for the ANTLR plug-in. It receives + * events and can send them on to its contained components. For example, + * saving the grammar editor or flipping to a new grammar sends an event + * to this object, which forwards on update events to the preview tool window. + *

+ * The main components are related to the console tool window forever output and + * the main panel of the preview tool window. + *

+ * This controller also manages the cache of grammar/editor combinations + * needed for the preview window. Updates must be made atomically so that + * the grammars and editors are consistently associated with the same window. */ public class ANTLRv4PluginController implements ProjectComponent { - public static final String PLUGIN_ID = "org.antlr.intellij.plugin"; - - public static final Key EDITOR_MOUSE_LISTENER_KEY = Key.create("EDITOR_MOUSE_LISTENER_KEY"); - public static final Logger LOG = Logger.getInstance("ANTLRv4PluginController"); - - public static final String PREVIEW_WINDOW_ID = "ANTLR Preview"; - public static final String CONSOLE_WINDOW_ID = "Tool Output"; - - public boolean projectIsClosed = false; - - public Project project; - public ConsoleView console; - private ToolWindow consoleWindow; - - public Map grammarToPreviewState = - Collections.synchronizedMap(new HashMap<>()); - private ToolWindow previewWindow; // same for all grammar editor - public PreviewPanel previewPanel; // same for all grammar editor - - public MyVirtualFileAdapter myVirtualFileAdapter = new MyVirtualFileAdapter(); - public MyFileEditorManagerAdapter myFileEditorManagerAdapter = new MyFileEditorManagerAdapter(); - - private ProgressIndicator parsingProgressIndicator; - private UrlClassLoader projectClassLoader; - - private Class tokenStreamClass; - private Class charStreamClass; - - public ANTLRv4PluginController(Project project) { - this.project = project; - } - - public static ANTLRv4PluginController getInstance(Project project) { - if ( project==null ) { - LOG.error("getInstance: project is null"); - return null; - } - ANTLRv4PluginController pc = project.getComponent(ANTLRv4PluginController.class); - if ( pc==null ) { - LOG.error("getInstance: getComponent() for "+project.getName()+" returns null"); - } - return pc; - } - - @Override - public void initComponent() { - } - - @Override - public void projectOpened() { - IdeaPluginDescriptor plugin = PluginManager.getPlugin(PluginId.getId(PLUGIN_ID)); - String version = "unknown"; - if ( plugin!=null ) { - version = plugin.getVersion(); - } - LOG.info("ANTLR 4 Plugin version "+version+", Java version "+ SystemInfo.JAVA_VERSION); - // make sure the tool windows are created early - createToolWindows(); - installListeners(); - initClassLoader(); - } - - public void createToolWindows() { - LOG.info("createToolWindows "+project.getName()); - ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); - - previewPanel = new PreviewPanel(project); - - ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); - - toolWindowManager.invokeLater(() -> { - Content content = contentFactory.createContent(previewPanel, "", false); - content.setCloseable(false); - - previewWindow = toolWindowManager.registerToolWindow(PREVIEW_WINDOW_ID, true, ToolWindowAnchor.BOTTOM); - previewWindow.getContentManager().addContent(content); - previewWindow.setIcon(Icons.getToolWindow()); - }); - - TextConsoleBuilderFactory factory = TextConsoleBuilderFactory.getInstance(); - TextConsoleBuilder consoleBuilder = factory.createBuilder(project); - this.console = consoleBuilder.getConsole(); - - toolWindowManager.invokeLater(() -> { - JComponent consoleComponent = console.getComponent(); - Content content = contentFactory.createContent(consoleComponent, "", false); - content.setCloseable(false); - - consoleWindow = toolWindowManager.registerToolWindow(CONSOLE_WINDOW_ID, true, ToolWindowAnchor.BOTTOM); - consoleWindow.getContentManager().addContent(content); - consoleWindow.setIcon(Icons.getToolWindow()); - }); - } - - @Override - public void projectClosed() { - LOG.info("projectClosed " + project.getName()); - //synchronized ( shutdownLock ) { // They should be called from EDT only so no lock - projectIsClosed = true; - uninstallListeners(); - - console.dispose(); - - for (PreviewState it : grammarToPreviewState.values()) { - previewPanel.inputPanel.releaseEditor(it); - } - - previewPanel = null; - previewWindow = null; - consoleWindow = null; - project = null; - grammarToPreviewState = null; - } - - // seems that intellij can kill and reload a project w/o user knowing. - // a ptr was left around that pointed at a disposed project. led to - // problem in switchGrammar. Probably was a listener still attached and trigger - // editor listeners released in editorReleased() events. - public void uninstallListeners() { - VirtualFileManager.getInstance().removeVirtualFileListener(myVirtualFileAdapter); - - if ( !project.isDisposed() ) { - MessageBusConnection msgBus = project.getMessageBus().connect(project); - msgBus.disconnect(); - } - } - - @Override - public void disposeComponent() { - } - - @NotNull - @Override - public String getComponentName() { - return "antlr.ProjectComponent"; - } - - private void initClassLoader() { - List compiledClassUrls = OrderEnumerator.orderEntries(project).runtimeOnly().classes().usingCache().getPathsList().getPathList(); - final List urls = new ArrayList<>(); - - for (String path : compiledClassUrls) { + public static final String PLUGIN_ID = "org.antlr.intellij.plugin"; + + public static final Key EDITOR_MOUSE_LISTENER_KEY = Key.create("EDITOR_MOUSE_LISTENER_KEY"); + public static final Logger LOG = Logger.getInstance("ANTLRv4PluginController"); + + public static final String PREVIEW_WINDOW_ID = "ANTLR Preview"; + public static final String CONSOLE_WINDOW_ID = "Tool Output"; + + public boolean projectIsClosed = false; + + public Project project; + public ConsoleView console; + private ToolWindow consoleWindow; + + public Map grammarToPreviewState = + Collections.synchronizedMap(new HashMap<>()); + private ToolWindow previewWindow; // same for all grammar editor + public PreviewPanel previewPanel; // same for all grammar editor + + public MyVirtualFileAdapter myVirtualFileAdapter = new MyVirtualFileAdapter(); + public MyFileEditorManagerAdapter myFileEditorManagerAdapter = new MyFileEditorManagerAdapter(); + + private ProgressIndicator parsingProgressIndicator; + private UrlClassLoader projectClassLoader; + + private Class tokenStreamClass; + private Class charStreamClass; + + public ANTLRv4PluginController(Project project) { + this.project = project; + } + + public static ANTLRv4PluginController getInstance(Project project) { + if (project == null) { + LOG.error("getInstance: project is null"); + return null; + } + ANTLRv4PluginController pc = project.getComponent(ANTLRv4PluginController.class); + if (pc == null) { + LOG.error("getInstance: getComponent() for " + project.getName() + " returns null"); + } + return pc; + } + + @Override + public void initComponent() { + } + + @Override + public void projectOpened() { + IdeaPluginDescriptor plugin = PluginManager.getPlugin(PluginId.getId(PLUGIN_ID)); + String version = "unknown"; + if (plugin != null) { + version = plugin.getVersion(); + } + LOG.info("ANTLR 4 Plugin version " + version + ", Java version " + SystemInfo.JAVA_VERSION); + // make sure the tool windows are created early + createToolWindows(); + installListeners(); + initClassLoader(); + } + + public void createToolWindows() { + LOG.info("createToolWindows " + project.getName()); + ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); + + previewPanel = new PreviewPanel(project); + + ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); + + toolWindowManager.invokeLater(() -> { + Content content = contentFactory.createContent(previewPanel, "", false); + content.setCloseable(false); + + previewWindow = toolWindowManager.registerToolWindow(PREVIEW_WINDOW_ID, true, ToolWindowAnchor.BOTTOM); + previewWindow.getContentManager().addContent(content); + previewWindow.setIcon(Icons.getToolWindow()); + }); + + TextConsoleBuilderFactory factory = TextConsoleBuilderFactory.getInstance(); + TextConsoleBuilder consoleBuilder = factory.createBuilder(project); + this.console = consoleBuilder.getConsole(); + + toolWindowManager.invokeLater(() -> { + JComponent consoleComponent = console.getComponent(); + Content content = contentFactory.createContent(consoleComponent, "", false); + content.setCloseable(false); + + consoleWindow = toolWindowManager.registerToolWindow(CONSOLE_WINDOW_ID, true, ToolWindowAnchor.BOTTOM); + consoleWindow.getContentManager().addContent(content); + consoleWindow.setIcon(Icons.getToolWindow()); + }); + } + + @Override + public void projectClosed() { + LOG.info("projectClosed " + project.getName()); + //synchronized ( shutdownLock ) { // They should be called from EDT only so no lock + projectIsClosed = true; + uninstallListeners(); + + console.dispose(); + + for (PreviewState it : grammarToPreviewState.values()) { + previewPanel.inputPanel.releaseEditor(it); + } + + previewPanel = null; + previewWindow = null; + consoleWindow = null; + project = null; + grammarToPreviewState = null; + } + + // seems that intellij can kill and reload a project w/o user knowing. + // a ptr was left around that pointed at a disposed project. led to + // problem in switchGrammar. Probably was a listener still attached and trigger + // editor listeners released in editorReleased() events. + public void uninstallListeners() { + VirtualFileManager.getInstance().removeVirtualFileListener(myVirtualFileAdapter); + + if (!project.isDisposed()) { + MessageBusConnection msgBus = project.getMessageBus().connect(project); + msgBus.disconnect(); + } + } + + @Override + public void disposeComponent() { + } + + @NotNull + @Override + public String getComponentName() { + return "antlr.ProjectComponent"; + } + + private void initClassLoader() { + List compiledClassUrls = OrderEnumerator.orderEntries(project).runtimeOnly().classes().usingCache().getPathsList().getPathList(); + final List urls = new ArrayList<>(); + + for (String path : compiledClassUrls) { + try { + urls.add(new File(FileUtil.toSystemIndependentName(path)).toURI().toURL()); + } catch (MalformedURLException e1) { + LOG.error(e1); + } + } + + this.projectClassLoader = UrlClassLoader.build().parent(TokenStream.class.getClassLoader()).urls(urls).get(); + + try { + this.tokenStreamClass = Class.forName(TokenStream.class.getName(), true, this.projectClassLoader); + this.charStreamClass = Class.forName(CharStream.class.getName(), true, this.projectClassLoader); + } catch (ClassNotFoundException e) { + LOG.error(e); + } + } + + // ------------------------------ + + public void installListeners() { + LOG.info("installListeners " + project.getName()); + // Listen for .g4 file saves + VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileAdapter); + + // Listen for editor window changes + MessageBusConnection msgBus = project.getMessageBus().connect(project); + msgBus.subscribe( + FileEditorManagerListener.FILE_EDITOR_MANAGER, + myFileEditorManagerAdapter + ); + + EditorFactory factory = EditorFactory.getInstance(); + factory.addEditorFactoryListener( + new EditorFactoryAdapter() { + @Override + public void editorCreated(@NotNull EditorFactoryEvent event) { + final Editor editor = event.getEditor(); + final Document doc = editor.getDocument(); + VirtualFile vfile = FileDocumentManager.getInstance().getFile(doc); + if (vfile != null && vfile.getName().endsWith(".g4")) { + GrammarEditorMouseAdapter listener = new GrammarEditorMouseAdapter(); + editor.putUserData(EDITOR_MOUSE_LISTENER_KEY, listener); + editor.addEditorMouseListener(listener); + } + } + + @Override + public void editorReleased(@NotNull EditorFactoryEvent event) { + Editor editor = event.getEditor(); + if (editor.getProject() != null && editor.getProject() != project) { + return; + } + GrammarEditorMouseAdapter listener = editor.getUserData(EDITOR_MOUSE_LISTENER_KEY); + if (listener != null) { + editor.removeEditorMouseListener(listener); + editor.putUserData(EDITOR_MOUSE_LISTENER_KEY, null); + } + } + } + ); + } + + /** + * The test ANTLR rule action triggers this event. This can occur + * only occur when the current editor the showing a grammar, because + * that is the only time that the action is enabled. We will see + * a file changed event when the project loads the first grammar file. + */ + public void setStartRuleNameEvent(VirtualFile grammarFile, String startRuleName) { + LOG.info("setStartRuleNameEvent " + startRuleName + " " + project.getName()); + PreviewState previewState = getPreviewState(grammarFile); + previewState.startRuleName = startRuleName; + if (previewPanel != null) { + previewPanel.getInputPanel().setStartRuleName(grammarFile, startRuleName); // notify the view + previewPanel.updateParseTreeFromDoc(grammarFile); + } else { + LOG.error("setStartRuleNameEvent called before preview panel created"); + } + } + + public void grammarFileSavedEvent(VirtualFile grammarFile) { + LOG.info("grammarFileSavedEvent " + grammarFile.getPath() + " " + project.getName()); + updateGrammarObjectsFromFile(grammarFile, true); // force reload + if (previewPanel != null) { + previewPanel.grammarFileSaved(grammarFile); + } else { + LOG.error("grammarFileSavedEvent called before preview panel created"); + } + } + + public void currentEditorFileChangedEvent(VirtualFile oldFile, VirtualFile newFile) { + LOG.info("currentEditorFileChangedEvent " + (oldFile != null ? oldFile.getPath() : "none") + + " -> " + (newFile != null ? newFile.getPath() : "none") + " " + project.getName()); + if (newFile == null) { // all files must be closed I guess + return; + } + if (newFile.getName().endsWith(".g")) { + LOG.info("currentEditorFileChangedEvent ANTLR 4 cannot handle .g files, only .g4"); + hidePreview(); + return; + } + if (!newFile.getName().endsWith(".g4")) { + hidePreview(); + return; + } + + // When switching from a lexer grammar, update its objects in case the grammar was modified. + // The updated objects might be needed later by another dependant grammar. + if (oldFile != null && oldFile.getName().endsWith(".g4")) { + updateGrammarObjectsFromFile(oldFile, true); + } + + PreviewState previewState = getPreviewState(newFile); + if (previewState.g == null && previewState.lg == null) { // only load grammars if none is there + updateGrammarObjectsFromFile(newFile, false); + } + if (previewPanel != null) { + previewPanel.grammarFileChanged(newFile); + } + } + + public void mouseEnteredGrammarEditorEvent(VirtualFile vfile, EditorMouseEvent e) { + if (previewPanel != null) { + ProfilerPanel profilerPanel = previewPanel.getProfilerPanel(); + if (profilerPanel != null) { + profilerPanel.mouseEnteredGrammarEditorEvent(vfile, e); + } + } + } + + public void editorFileClosedEvent(VirtualFile vfile) { + // hopefully called only from swing EDT + String grammarFileName = vfile.getPath(); + LOG.info("editorFileClosedEvent " + grammarFileName + " " + project.getName()); + if (!vfile.getName().endsWith(".g4")) { + hidePreview(); + return; + } + + // Dispose of state, editor, and such for this file + PreviewState previewState = grammarToPreviewState.get(grammarFileName); + if (previewState == null) { // project closing must have done already + return; + } + + previewState.g = null; // wack old ref to the Grammar for text in editor + previewState.lg = null; + + previewPanel.closeGrammar(vfile); + + grammarToPreviewState.remove(grammarFileName); + + // close tool window + hidePreview(); + } + + private void hidePreview() { + if (previewPanel != null) { + previewPanel.setEnabled(false); + } + if (previewWindow != null) { + previewWindow.hide(null); + } + } + + /** + * Make sure to run after updating grammars in previewState + */ + public void runANTLRTool(final VirtualFile grammarFile) { + String title = "ANTLR Code Generation"; + boolean canBeCancelled = true; + boolean forceGeneration = false; + Task gen = + new RunANTLROnGrammarFile(grammarFile, + project, + title, + canBeCancelled, + forceGeneration); + ProgressManager.getInstance().run(gen); + } + + /** + * Look for state information concerning this grammar file and update + * the Grammar objects. This does not necessarily update the grammar file + * in the current editor window. Either we are already looking at + * this grammar or we will have seen a grammar file changed event. + * (I hope!) + */ + private void updateGrammarObjectsFromFile(VirtualFile grammarFile, boolean generateTokensFile) { + updateGrammarObjectsFromFile_(grammarFile); + + // if grammarFileName is a separate lexer, we need to look for + // its matching parser, if any, that is loaded in an editor + // (don't go looking on disk). + PreviewState s = getAssociatedParserIfLexer(grammarFile.getPath()); + if (s != null) { + if (generateTokensFile) { + // Run the tool to regenerate the .tokens file, which will be + // needed in the parser grammar + runANTLRTool(grammarFile); + } + + // try to load lexer again and associate with this parser grammar. + // must update parser too as tokens have changed + updateGrammarObjectsFromFile_(s.grammarFile); + } + } + + private String updateGrammarObjectsFromFile_(VirtualFile grammarFile) { + String grammarFileName = grammarFile.getPath(); + PreviewState previewState = getPreviewState(grammarFile); + Grammar[] grammars = ParsingUtils.loadGrammars(grammarFile, project); + if (grammars != null) { + LexerGrammar lg = (LexerGrammar) grammars[0]; + Grammar g = grammars[1]; + + Constructor parserCtor = null; + Constructor lexerCtor = null; + + ANTLRv4GrammarProperties grammarProperties = ANTLRv4GrammarPropertiesStore.getGrammarProperties(project, grammarFile); + if (grammarProperties.isUseGeneratedParserCodeCheckBox()) { + String parserClassName = grammarProperties.getGeneratedParserClassName() != null ? grammarProperties.getGeneratedParserClassName() : grammarFile.getNameWithoutExtension(); + String lexerClassName = grammarProperties.getGeneratedLexerClassName() != null ? grammarProperties.getGeneratedLexerClassName() : lg.name; + try { - urls.add(new File(FileUtil.toSystemIndependentName(path)).toURI().toURL()); - } catch (MalformedURLException e1) { - LOG.error(e1); + Class parserClass = (Class) Class.forName(parserClassName, true, this.projectClassLoader); + Class lexerClass = (Class) Class.forName(lexerClassName, true, this.projectClassLoader); + + parserCtor = parserClass.getDeclaredConstructor(tokenStreamClass); + lexerCtor = lexerClass.getDeclaredConstructor(charStreamClass); + } catch (ClassNotFoundException | NoSuchMethodException e) { + LOG.warn(e); } - } - - this.projectClassLoader = UrlClassLoader.build().parent(TokenStream.class.getClassLoader()).urls(urls).get(); - - try { - this.tokenStreamClass = Class.forName(TokenStream.class.getName(), true, this.projectClassLoader); - this.charStreamClass = Class.forName(CharStream.class.getName(), true, this.projectClassLoader); - } catch (ClassNotFoundException e) { - LOG.error(e); - } - } - - // ------------------------------ - - public void installListeners() { - LOG.info("installListeners "+project.getName()); - // Listen for .g4 file saves - VirtualFileManager.getInstance().addVirtualFileListener(myVirtualFileAdapter); - - // Listen for editor window changes - MessageBusConnection msgBus = project.getMessageBus().connect(project); - msgBus.subscribe( - FileEditorManagerListener.FILE_EDITOR_MANAGER, - myFileEditorManagerAdapter - ); - - EditorFactory factory = EditorFactory.getInstance(); - factory.addEditorFactoryListener( - new EditorFactoryAdapter() { - @Override - public void editorCreated(@NotNull EditorFactoryEvent event) { - final Editor editor = event.getEditor(); - final Document doc = editor.getDocument(); - VirtualFile vfile = FileDocumentManager.getInstance().getFile(doc); - if ( vfile!=null && vfile.getName().endsWith(".g4") ) { - GrammarEditorMouseAdapter listener = new GrammarEditorMouseAdapter(); - editor.putUserData(EDITOR_MOUSE_LISTENER_KEY, listener); - editor.addEditorMouseListener(listener); - } - } - - @Override - public void editorReleased(@NotNull EditorFactoryEvent event) { - Editor editor = event.getEditor(); - if (editor.getProject() != null && editor.getProject() != project) { - return; - } - GrammarEditorMouseAdapter listener = editor.getUserData(EDITOR_MOUSE_LISTENER_KEY); - if (listener != null) { - editor.removeEditorMouseListener(listener); - editor.putUserData(EDITOR_MOUSE_LISTENER_KEY, null); - } - } - } - ); - } - - /** The test ANTLR rule action triggers this event. This can occur - * only occur when the current editor the showing a grammar, because - * that is the only time that the action is enabled. We will see - * a file changed event when the project loads the first grammar file. - */ - public void setStartRuleNameEvent(VirtualFile grammarFile, String startRuleName) { - LOG.info("setStartRuleNameEvent " + startRuleName+" "+project.getName()); - PreviewState previewState = getPreviewState(grammarFile); - previewState.startRuleName = startRuleName; - if ( previewPanel!=null ) { - previewPanel.getInputPanel().setStartRuleName(grammarFile, startRuleName); // notify the view - previewPanel.updateParseTreeFromDoc(grammarFile); - } - else { - LOG.error("setStartRuleNameEvent called before preview panel created"); - } - } - - public void grammarFileSavedEvent(VirtualFile grammarFile) { - LOG.info("grammarFileSavedEvent "+grammarFile.getPath()+" "+project.getName()); - updateGrammarObjectsFromFile(grammarFile, true); // force reload - if ( previewPanel!=null ) { - previewPanel.grammarFileSaved(grammarFile); - } - else { - LOG.error("grammarFileSavedEvent called before preview panel created"); - } - } - - public void currentEditorFileChangedEvent(VirtualFile oldFile, VirtualFile newFile) { - LOG.info("currentEditorFileChangedEvent "+(oldFile!=null?oldFile.getPath():"none")+ - " -> "+(newFile!=null?newFile.getPath():"none")+" "+project.getName()); - if ( newFile==null ) { // all files must be closed I guess - return; - } - if ( newFile.getName().endsWith(".g") ) { - LOG.info("currentEditorFileChangedEvent ANTLR 4 cannot handle .g files, only .g4"); - hidePreview(); - return; - } - if ( !newFile.getName().endsWith(".g4") ) { - hidePreview(); - return; - } - - // When switching from a lexer grammar, update its objects in case the grammar was modified. - // The updated objects might be needed later by another dependant grammar. - if ( oldFile != null && oldFile.getName().endsWith(".g4")) { - updateGrammarObjectsFromFile(oldFile, true); - } - - PreviewState previewState = getPreviewState(newFile); - if ( previewState.g==null && previewState.lg==null ) { // only load grammars if none is there - updateGrammarObjectsFromFile(newFile, false); - } - if ( previewPanel!=null ) { - previewPanel.grammarFileChanged(newFile); - } - } - - public void mouseEnteredGrammarEditorEvent(VirtualFile vfile, EditorMouseEvent e) { - if ( previewPanel!=null ) { - ProfilerPanel profilerPanel = previewPanel.getProfilerPanel(); - if ( profilerPanel!=null ) { - profilerPanel.mouseEnteredGrammarEditorEvent(vfile, e); - } - } - } - - public void editorFileClosedEvent(VirtualFile vfile) { - // hopefully called only from swing EDT - String grammarFileName = vfile.getPath(); - LOG.info("editorFileClosedEvent "+ grammarFileName+" "+project.getName()); - if ( !vfile.getName().endsWith(".g4") ) { - hidePreview(); - return; - } - - // Dispose of state, editor, and such for this file - PreviewState previewState = grammarToPreviewState.get(grammarFileName); - if ( previewState==null ) { // project closing must have done already - return; - } - - previewState.g = null; // wack old ref to the Grammar for text in editor - previewState.lg = null; - - previewPanel.closeGrammar(vfile); - - grammarToPreviewState.remove(grammarFileName); - - // close tool window - hidePreview(); - } - - private void hidePreview() { - if (previewPanel != null) { - previewPanel.setEnabled(false); - } - if (previewWindow != null) { - previewWindow.hide(null); - } - } - - /** Make sure to run after updating grammars in previewState */ - public void runANTLRTool(final VirtualFile grammarFile) { - String title = "ANTLR Code Generation"; - boolean canBeCancelled = true; - boolean forceGeneration = false; - Task gen = - new RunANTLROnGrammarFile(grammarFile, - project, - title, - canBeCancelled, - forceGeneration); - ProgressManager.getInstance().run(gen); - } - - /** Look for state information concerning this grammar file and update - * the Grammar objects. This does not necessarily update the grammar file - * in the current editor window. Either we are already looking at - * this grammar or we will have seen a grammar file changed event. - * (I hope!) - */ - private void updateGrammarObjectsFromFile(VirtualFile grammarFile, boolean generateTokensFile) { - updateGrammarObjectsFromFile_(grammarFile); - - // if grammarFileName is a separate lexer, we need to look for - // its matching parser, if any, that is loaded in an editor - // (don't go looking on disk). - PreviewState s = getAssociatedParserIfLexer(grammarFile.getPath()); - if ( s!=null ) { - if (generateTokensFile) { - // Run the tool to regenerate the .tokens file, which will be - // needed in the parser grammar - runANTLRTool(grammarFile); - } - - // try to load lexer again and associate with this parser grammar. - // must update parser too as tokens have changed - updateGrammarObjectsFromFile_(s.grammarFile); - } - } - - private String updateGrammarObjectsFromFile_(VirtualFile grammarFile) { - String grammarFileName = grammarFile.getPath(); - PreviewState previewState = getPreviewState(grammarFile); - Grammar[] grammars = ParsingUtils.loadGrammars(grammarFile, project); - if (grammars != null) { - LexerGrammar lg = (LexerGrammar) grammars[0]; - Grammar g = grammars[1]; - - Constructor parserCtor = null; - Constructor lexerCtor = null; - - ANTLRv4GrammarProperties grammarProperties = ANTLRv4GrammarPropertiesStore.getGrammarProperties(project, grammarFile); - if (grammarProperties.isUseGeneratedParserCodeCheckBox()) { - String parserClassName = grammarProperties.getGeneratedParserClassName() != null ? grammarProperties.getGeneratedParserClassName() : grammarFile.getNameWithoutExtension(); - String lexerClassName = grammarProperties.getGeneratedLexerClassName() != null ? grammarProperties.getGeneratedLexerClassName() : lg.name; - - try { - Class parserClass = (Class) Class.forName(parserClassName, true, this.projectClassLoader); - Class lexerClass = (Class) Class.forName(lexerClassName, true, this.projectClassLoader); - - parserCtor = parserClass.getDeclaredConstructor(tokenStreamClass); - lexerCtor = lexerClass.getDeclaredConstructor(charStreamClass); - } catch (ClassNotFoundException | NoSuchMethodException e) { - LOG.warn(e); - } + } + + synchronized (previewState) { + previewState.lg = lg; + previewState.g = g; + previewState.lexerCtor = lexerCtor; + previewState.parserCtor = parserCtor; + } + } + return grammarFileName; + } + + // TODO there could be multiple grammars importing/tokenVocab'ing this lexer grammar + public PreviewState getAssociatedParserIfLexer(String grammarFileName) { + for (PreviewState s : grammarToPreviewState.values()) { + if (s != null && s.lg != null && + (sameFile(grammarFileName, s.lg.fileName) || s.lg == ParsingUtils.BAD_LEXER_GRAMMAR)) { + // s has a lexer with same filename, see if there is a parser grammar + // (not a combined grammar) + if (s.g != null && s.g.getType() == ANTLRParser.PARSER) { + return s; } + } - synchronized (previewState) { - previewState.lg = lg; - previewState.g = g; - previewState.lexerCtor = lexerCtor; - previewState.parserCtor = parserCtor; - } - } - return grammarFileName; - } - - // TODO there could be multiple grammars importing/tokenVocab'ing this lexer grammar - public PreviewState getAssociatedParserIfLexer(String grammarFileName) { - for (PreviewState s : grammarToPreviewState.values()) { - if ( s!=null && s.lg!=null && - (sameFile(grammarFileName, s.lg.fileName)||s.lg==ParsingUtils.BAD_LEXER_GRAMMAR) ) - { - // s has a lexer with same filename, see if there is a parser grammar - // (not a combined grammar) - if ( s.g!=null && s.g.getType()==ANTLRParser.PARSER ) { - return s; - } - } - - if ( s!=null && s.g!=null && s.g.importedGrammars!=null ) { - for ( Grammar importedGrammar : s.g.importedGrammars ) { - if (grammarFileName.equals(importedGrammar.fileName)) { - return s; - } - } - } - } - return null; - } - - private boolean sameFile(String pathOne, String pathTwo) { - // use new File() to support both / and \ in paths - return new File(pathOne).equals(new File(pathTwo)); - } - - public void parseText(final VirtualFile grammarFile, String inputText) { - // Wipes out the console and also any error annotations - previewPanel.inputPanel.clearParseErrors(); - - final PreviewState previewState = getPreviewState(grammarFile); - - abortCurrentParsing(); - - // Parse text in a background thread to avoid freezing the UI if the grammar is badly written - // an takes ages to interpret the input. - parsingProgressIndicator = BackgroundTaskUtil.executeAndTryWait( - (indicator) -> { - long start = System.nanoTime(); - - previewState.parsingResult = ParsingUtils.parseText( - previewState.g, previewState.lg, previewState.lexerCtor, previewState.parserCtor, - previewState.startRuleName, grammarFile, inputText, project - ); - - return () -> previewPanel.onParsingCompleted(previewState, System.nanoTime() - start); - }, - () -> previewPanel.notifySlowParsing(), - ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS, - false - ); - } - - public void abortCurrentParsing() { - if ( parsingProgressIndicator!=null ) { - parsingProgressIndicator.cancel(); - parsingProgressIndicator = null; - previewPanel.onParsingCancelled(); - } - } - - public PreviewPanel getPreviewPanel() { - return previewPanel; - } - - public ConsoleView getConsole() { - return console; - } - - public ToolWindow getConsoleWindow() { - return consoleWindow; - } - - public static void showConsoleWindow(final Project project) { - ApplicationManager.getApplication().invokeLater( - () -> ANTLRv4PluginController.getInstance(project).getConsoleWindow().show(null) - ); - } - - public ToolWindow getPreviewWindow() { - return previewWindow; - } - - public @NotNull PreviewState getPreviewState(VirtualFile grammarFile) { - // make sure only one thread tries to add a preview state object for a given file - String grammarFileName = grammarFile.getPath(); - // Have we seen this grammar before? - PreviewState stateForCurrentGrammar = grammarToPreviewState.get(grammarFileName); - if ( stateForCurrentGrammar!=null ) { - return stateForCurrentGrammar; // seen this before - } - - // not seen, must create state - stateForCurrentGrammar = new PreviewState(project, grammarFile); - grammarToPreviewState.put(grammarFileName, stateForCurrentGrammar); - - return stateForCurrentGrammar; - } - - public Editor getEditor(VirtualFile vfile) { - final FileDocumentManager fdm = FileDocumentManager.getInstance(); - final Document doc = fdm.getDocument(vfile); - if (doc == null) return null; - - EditorFactory factory = EditorFactory.getInstance(); - final Editor[] editors = factory.getEditors(doc, previewPanel.project); - if ( editors.length==0 ) { - // no editor found for this file. likely an out-of-sequence issue - // where Intellij is opening a project and doesn't fire events - // in order we'd expect. - return null; - } - return editors[0]; // hope just one - } - - - /** Get the state information associated with the grammar in the current - * editor window. If there is no grammar in the editor window, return null. - * If there is a grammar, return any existing preview state else - * create a new one in store in the map. - * - * Too dangerous; turning off but might be useful later. - public @org.jetbrains.annotations.Nullable PreviewState getPreviewState() { - VirtualFile currentGrammarFile = getCurrentGrammarFile(); - if ( currentGrammarFile==null ) { - return null; - } - String currentGrammarFileName = currentGrammarFile.getPath(); - if ( currentGrammarFileName==null ) { - return null; // we are not looking at a grammar file - } - return getPreviewState(currentGrammarFile); - } - */ - - // These "get current editor file" routines should only be used - // when you are sure the user is in control and is viewing the - // right file (i.e., don't use these during project loading etc...) - - public static VirtualFile getCurrentEditorFile(Project project) { - FileEditorManager fmgr = FileEditorManager.getInstance(project); - // "If more than one file is selected (split), the file with most recent focused editor is returned first." from IDE doc on method - VirtualFile[] files = fmgr.getSelectedFiles(); - if ( files.length == 0 ) { - return null; - } - return files[0]; - } - - public VirtualFile getCurrentGrammarFile() { - return getCurrentGrammarFile(project); - } - - public static VirtualFile getCurrentGrammarFile(Project project) { - VirtualFile f = getCurrentEditorFile(project); - if ( f==null ) { - return null; - } - if ( f.getName().endsWith(".g4") ) return f; - return null; - } - - private class GrammarEditorMouseAdapter extends EditorMouseAdapter { - @Override - public void mouseClicked(EditorMouseEvent e) { - Document doc = e.getEditor().getDocument(); - VirtualFile vfile = FileDocumentManager.getInstance().getFile(doc); - if ( vfile!=null && vfile.getName().endsWith(".g4") ) { - mouseEnteredGrammarEditorEvent(vfile, e); - } - } - } - - private class MyVirtualFileAdapter extends VirtualFileAdapter { - @Override - public void contentsChanged(VirtualFileEvent event) { - final VirtualFile vfile = event.getFile(); - if ( !vfile.getName().endsWith(".g4") ) return; - if ( !projectIsClosed && !ApplicationManager.getApplication().isUnitTestMode()) grammarFileSavedEvent(vfile); - } - } - - private class MyFileEditorManagerAdapter implements FileEditorManagerListener { - @Override - public void selectionChanged(FileEditorManagerEvent event) { - if ( !projectIsClosed ) currentEditorFileChangedEvent(event.getOldFile(), event.getNewFile()); - } - - @Override - public void fileClosed(FileEditorManager source, VirtualFile file) { - if ( !projectIsClosed ) editorFileClosedEvent(file); - } - } + if (s != null && s.g != null && s.g.importedGrammars != null) { + for (Grammar importedGrammar : s.g.importedGrammars) { + if (grammarFileName.equals(importedGrammar.fileName)) { + return s; + } + } + } + } + return null; + } + + private boolean sameFile(String pathOne, String pathTwo) { + // use new File() to support both / and \ in paths + return new File(pathOne).equals(new File(pathTwo)); + } + + public void parseText(final VirtualFile grammarFile, String inputText) { + // Wipes out the console and also any error annotations + previewPanel.inputPanel.clearParseErrors(); + + final PreviewState previewState = getPreviewState(grammarFile); + + abortCurrentParsing(); + + // Parse text in a background thread to avoid freezing the UI if the grammar is badly written + // an takes ages to interpret the input. + parsingProgressIndicator = BackgroundTaskUtil.executeAndTryWait( + (indicator) -> { + long start = System.nanoTime(); + + previewState.parsingResult = ParsingUtils.parseText( + previewState.g, previewState.lg, previewState.lexerCtor, previewState.parserCtor, + previewState.startRuleName, grammarFile, inputText, project + ); + + return () -> previewPanel.onParsingCompleted(previewState, System.nanoTime() - start); + }, + () -> previewPanel.notifySlowParsing(), + ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS, + false + ); + } + + public void abortCurrentParsing() { + if (parsingProgressIndicator != null) { + parsingProgressIndicator.cancel(); + parsingProgressIndicator = null; + previewPanel.onParsingCancelled(); + } + } + + public PreviewPanel getPreviewPanel() { + return previewPanel; + } + + public ConsoleView getConsole() { + return console; + } + + public ToolWindow getConsoleWindow() { + return consoleWindow; + } + + public static void showConsoleWindow(final Project project) { + ApplicationManager.getApplication().invokeLater( + () -> ANTLRv4PluginController.getInstance(project).getConsoleWindow().show(null) + ); + } + + public ToolWindow getPreviewWindow() { + return previewWindow; + } + + public @NotNull PreviewState getPreviewState(VirtualFile grammarFile) { + // make sure only one thread tries to add a preview state object for a given file + String grammarFileName = grammarFile.getPath(); + // Have we seen this grammar before? + PreviewState stateForCurrentGrammar = grammarToPreviewState.get(grammarFileName); + if (stateForCurrentGrammar != null) { + return stateForCurrentGrammar; // seen this before + } + + // not seen, must create state + stateForCurrentGrammar = new PreviewState(project, grammarFile); + grammarToPreviewState.put(grammarFileName, stateForCurrentGrammar); + + return stateForCurrentGrammar; + } + + public Editor getEditor(VirtualFile vfile) { + final FileDocumentManager fdm = FileDocumentManager.getInstance(); + final Document doc = fdm.getDocument(vfile); + if (doc == null) return null; + + EditorFactory factory = EditorFactory.getInstance(); + final Editor[] editors = factory.getEditors(doc, previewPanel.project); + if (editors.length == 0) { + // no editor found for this file. likely an out-of-sequence issue + // where Intellij is opening a project and doesn't fire events + // in order we'd expect. + return null; + } + return editors[0]; // hope just one + } + + + /** + * Get the state information associated with the grammar in the current + * editor window. If there is no grammar in the editor window, return null. + * If there is a grammar, return any existing preview state else + * create a new one in store in the map. + *

+ * Too dangerous; turning off but might be useful later. + * public @org.jetbrains.annotations.Nullable PreviewState getPreviewState() { + * VirtualFile currentGrammarFile = getCurrentGrammarFile(); + * if ( currentGrammarFile==null ) { + * return null; + * } + * String currentGrammarFileName = currentGrammarFile.getPath(); + * if ( currentGrammarFileName==null ) { + * return null; // we are not looking at a grammar file + * } + * return getPreviewState(currentGrammarFile); + * } + */ + + // These "get current editor file" routines should only be used + // when you are sure the user is in control and is viewing the + // right file (i.e., don't use these during project loading etc...) + public static VirtualFile getCurrentEditorFile(Project project) { + FileEditorManager fmgr = FileEditorManager.getInstance(project); + // "If more than one file is selected (split), the file with most recent focused editor is returned first." from IDE doc on method + VirtualFile[] files = fmgr.getSelectedFiles(); + if (files.length == 0) { + return null; + } + return files[0]; + } + + public VirtualFile getCurrentGrammarFile() { + return getCurrentGrammarFile(project); + } + + public static VirtualFile getCurrentGrammarFile(Project project) { + VirtualFile f = getCurrentEditorFile(project); + if (f == null) { + return null; + } + if (f.getName().endsWith(".g4")) return f; + return null; + } + + private class GrammarEditorMouseAdapter extends EditorMouseAdapter { + @Override + public void mouseClicked(EditorMouseEvent e) { + Document doc = e.getEditor().getDocument(); + VirtualFile vfile = FileDocumentManager.getInstance().getFile(doc); + if (vfile != null && vfile.getName().endsWith(".g4")) { + mouseEnteredGrammarEditorEvent(vfile, e); + } + } + } + + private class MyVirtualFileAdapter extends VirtualFileAdapter { + @Override + public void contentsChanged(VirtualFileEvent event) { + final VirtualFile vfile = event.getFile(); + if (!vfile.getName().endsWith(".g4")) return; + if (!projectIsClosed && !ApplicationManager.getApplication().isUnitTestMode()) grammarFileSavedEvent(vfile); + } + } + + private class MyFileEditorManagerAdapter implements FileEditorManagerListener { + @Override + public void selectionChanged(FileEditorManagerEvent event) { + if (!projectIsClosed) currentEditorFileChangedEvent(event.getOldFile(), event.getNewFile()); + } + + @Override + public void fileClosed(FileEditorManager source, VirtualFile file) { + if (!projectIsClosed) editorFileClosedEvent(file); + } + } } diff --git a/src/main/java/org/antlr/intellij/plugin/parsing/ParsingUtils.java b/src/main/java/org/antlr/intellij/plugin/parsing/ParsingUtils.java index ba46784e..d4fc9baa 100644 --- a/src/main/java/org/antlr/intellij/plugin/parsing/ParsingUtils.java +++ b/src/main/java/org/antlr/intellij/plugin/parsing/ParsingUtils.java @@ -1,21 +1,13 @@ package org.antlr.intellij.plugin.parsing; -import com.intellij.CommonBundle; import com.intellij.execution.ui.ConsoleView; import com.intellij.execution.ui.ConsoleViewContentType; -import com.intellij.openapi.compiler.CompilerManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.project.ProjectUtil; -import com.intellij.openapi.roots.OrderEnumerator; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.lang.UrlClassLoader; import org.antlr.intellij.adaptor.parser.SyntaxErrorListener; import org.antlr.intellij.plugin.ANTLRv4PluginController; import org.antlr.intellij.plugin.PluginIgnoreMissingTokensFileErrorManager; @@ -37,9 +29,7 @@ import org.antlr.v4.tool.ErrorType; import org.antlr.v4.tool.Grammar; import org.antlr.v4.tool.LexerGrammar; -import org.antlr.v4.tool.Rule; import org.antlr.v4.tool.ast.GrammarRootAST; -import org.codehaus.groovy.antlr.AntlrParserPlugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -47,8 +37,6 @@ import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; -import java.net.URL; import java.util.*; import static org.antlr.intellij.plugin.configdialogs.ANTLRv4GrammarPropertiesStore.getGrammarProperties;