diff --git a/README.md b/README.md index f78ec640..0c63cdd8 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,12 @@ because the interpreter is language agnostic. For the same reasons, if your parser and/or lexer classes extend a custom implementation of the base parser/lexer classes, your custom code will *not* be run during live preview. +As of 1.17, this limitation is partially not true. The configuration window of a grammar has a new option +that allows using the generated parser code in the preview. In this case, the grammar must be compiled into +a Java class (just to be on Project's target classpath). Any changes made to such grammar are not immediately +reflected in the preview and the project must be recompiled instead. It is also possible to specify the name of the +compiler parser/lexer class. By default the name of the grammar is used (parser and lexer grammar). + ## History See [Releases](https://github.com/antlr/intellij-plugin-v4/releases) diff --git a/build.gradle b/build.gradle index 3761bdd6..89f394d2 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 00f10512..7b955ffd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -pluginVersion=1.19.2 +pluginVersion=1.19.2-akovari # 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 @@ -16,7 +16,7 @@ pluginVersion=1.19.2 #ideaVersion=IC-2021.2 -ideaVersion=IC-2021.3.3 +ideaVersion=IC-2022.2 # The version of ANTLR v4 that will be used to generate the parser antlr4Version=4.10.1 diff --git a/historical-contributors-agreement.txt b/historical-contributors-agreement.txt index bfb07c2d..e7f4fee6 100644 --- a/historical-contributors-agreement.txt +++ b/historical-contributors-agreement.txt @@ -64,4 +64,4 @@ YYYY/MM/DD, github id, Full name, email 2019/11/24, nopeslide, Uffke Drechsler, nopeslide@web.de 2020/12/05, roggenbrot, Sascha Dais, sdais@gmx.net 2021/11/04, OleksiiKovalov, Oleksii Kovalov, Oleksii.Kovalov@outlook.com - +2021/06/04, akovari, Adam Kovari, kovariadam@gmail.com diff --git a/src/main/java/org/antlr/intellij/plugin/ANTLRv4PluginController.java b/src/main/java/org/antlr/intellij/plugin/ANTLRv4PluginController.java index 1423c0e6..5d5dc8b1 100644 --- a/src/main/java/org/antlr/intellij/plugin/ANTLRv4PluginController.java +++ b/src/main/java/org/antlr/intellij/plugin/ANTLRv4PluginController.java @@ -26,8 +26,10 @@ import com.intellij.openapi.progress.util.BackgroundTaskUtil; import com.intellij.openapi.progress.util.ProgressWindow; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.OrderEnumerator; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileAdapter; import com.intellij.openapi.vfs.VirtualFileEvent; @@ -37,212 +39,248 @@ import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentFactory; +import com.intellij.util.lang.UrlClassLoader; import com.intellij.util.messages.MessageBusConnection; +import org.antlr.intellij.plugin.configdialogs.ANTLRv4GrammarProperties; +import org.antlr.intellij.plugin.configdialogs.ANTLRv4GrammarPropertiesStore; import org.antlr.intellij.plugin.parsing.ParsingUtils; import org.antlr.intellij.plugin.parsing.RunANTLROnGrammarFile; import org.antlr.intellij.plugin.preview.PreviewPanel; import org.antlr.intellij.plugin.preview.PreviewState; import org.antlr.intellij.plugin.profiler.ProfilerPanel; import org.antlr.v4.parse.ANTLRParser; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.tool.Grammar; import org.antlr.v4.tool.LexerGrammar; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.io.File; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** 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. +import java.lang.reflect.Constructor; +import java.net.MalformedURLException; +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. */ 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; - - 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(); - } - - 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"; - } - - // ------------------------------ - - 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); - } - } - } - ); - } + 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 is showing a grammar, because @@ -262,16 +300,15 @@ public void setStartRuleNameEvent(VirtualFile grammarFile, String startRuleName) } } - 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 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")+ @@ -288,152 +325,172 @@ public void currentEditorFileChangedEvent(VirtualFile oldFile, VirtualFile newFi 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) { - synchronized (previewState) { // build atomically - previewState.lg = (LexerGrammar)grammars[0]; - previewState.g = grammars[1]; - } - } - else { - synchronized (previewState) { // build atomically - previewState.lg = null; - previewState.g = null; - } - } - 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)); - } + // 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; + } + } + + 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) { final PreviewState previewState = getPreviewState(grammarFile); @@ -445,10 +502,10 @@ public void parseText(final VirtualFile grammarFile, String inputText) { // System.out.println("PARSE START "+Thread.currentThread().getName()); long start = System.nanoTime(); - previewState.parsingResult = ParsingUtils.parseText( - previewState.g, previewState.lg, previewState.startRuleName, - grammarFile, inputText, project - ); + previewState.parsingResult = ParsingUtils.parseText( + previewState.g, previewState.lg, previewState.lexerCtor, previewState.parserCtor, + previewState.startRuleName, grammarFile, inputText, project + ); // long parseTime_ns = System.nanoTime() - start; // double parseTimeMS = parseTime_ns/(1000.0*1000.0); @@ -461,13 +518,13 @@ public void parseText(final VirtualFile grammarFile, String inputText) { ); } - public void abortCurrentParsing() { - if ( parsingProgressIndicator!=null ) { - parsingProgressIndicator.cancel(); - parsingProgressIndicator = null; - previewPanel.onParsingCancelled(); - } - } + public void abortCurrentParsing() { + if (parsingProgressIndicator != null) { + parsingProgressIndicator.cancel(); + parsingProgressIndicator = null; + previewPanel.onParsingCancelled(); + } + } public void startParsing() { parsingProgressIndicator = null; @@ -479,113 +536,113 @@ 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); - } - } - } + 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 diff --git a/src/main/java/org/antlr/intellij/plugin/configdialogs/ANTLRv4GrammarProperties.java b/src/main/java/org/antlr/intellij/plugin/configdialogs/ANTLRv4GrammarProperties.java index 879411d7..f89cac57 100644 --- a/src/main/java/org/antlr/intellij/plugin/configdialogs/ANTLRv4GrammarProperties.java +++ b/src/main/java/org/antlr/intellij/plugin/configdialogs/ANTLRv4GrammarProperties.java @@ -78,6 +78,15 @@ public class ANTLRv4GrammarProperties implements Cloneable { @OptionTag(converter = CaseChangingStrategyConverter.class) CaseChangingStrategy caseChangingStrategy = CaseChangingStrategy.LEAVE_AS_IS; + @Property + boolean useGeneratedParserCodeCheckBox = false; + + @Property + String generatedParserClassName; + + @Property + String generatedLexerClassName; + public ANTLRv4GrammarProperties() { } @@ -92,6 +101,9 @@ public ANTLRv4GrammarProperties(ANTLRv4GrammarProperties source) { this.generateListener = source.generateListener; this.generateVisitor = source.generateVisitor; this.caseChangingStrategy = source.caseChangingStrategy; + this.useGeneratedParserCodeCheckBox = source.useGeneratedParserCodeCheckBox; + this.generatedParserClassName = source.generatedParserClassName; + this.generatedLexerClassName = source.generatedLexerClassName; } public boolean shouldAutoGenerateParser() { @@ -126,6 +138,18 @@ public boolean shouldGenerateParseTreeVisitor() { return generateVisitor; } + public boolean isUseGeneratedParserCodeCheckBox() { + return useGeneratedParserCodeCheckBox; + } + + public String getGeneratedParserClassName() { + return generatedParserClassName; + } + + public String getGeneratedLexerClassName() { + return generatedLexerClassName; + } + public CaseChangingStrategy getCaseChangingStrategy() { return caseChangingStrategy; } diff --git a/src/main/java/org/antlr/intellij/plugin/configdialogs/ConfigANTLRDialogPanel.form b/src/main/java/org/antlr/intellij/plugin/configdialogs/ConfigANTLRDialogPanel.form index 138c607b..c7f7a6a5 100644 --- a/src/main/java/org/antlr/intellij/plugin/configdialogs/ConfigANTLRDialogPanel.form +++ b/src/main/java/org/antlr/intellij/plugin/configdialogs/ConfigANTLRDialogPanel.form @@ -1,9 +1,9 @@

- + - + @@ -109,11 +109,6 @@ - - - - - @@ -128,6 +123,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/antlr/intellij/plugin/configdialogs/ConfigANTLRPerGrammar.java b/src/main/java/org/antlr/intellij/plugin/configdialogs/ConfigANTLRPerGrammar.java index 5271029a..ffe65ef8 100644 --- a/src/main/java/org/antlr/intellij/plugin/configdialogs/ConfigANTLRPerGrammar.java +++ b/src/main/java/org/antlr/intellij/plugin/configdialogs/ConfigANTLRPerGrammar.java @@ -32,6 +32,9 @@ public class ConfigANTLRPerGrammar extends DialogWrapper { protected JCheckBox autoGenerateParsersCheckBox; protected JTextField languageField; private JComboBox caseTransformation; + private JCheckBox useGeneratedParserCodeCheckBox; + private JTextField generatedParserClassName; + private JTextField generatedLexerClassName; private ConfigANTLRPerGrammar(final Project project) { super(project, false); @@ -65,6 +68,15 @@ private void initAntlrFields(Project project, String qualFileName) { libDirField.setTextFieldPreferredWidth(50); loadValues(project, qualFileName); + + useGeneratedParserCodeCheckBox.addActionListener(e -> toggleGeneratedClassNames()); + toggleGeneratedClassNames(); + } + + private void toggleGeneratedClassNames() { + boolean enabled = useGeneratedParserCodeCheckBox.isSelected(); + generatedParserClassName.setEnabled(enabled); + generatedLexerClassName.setEnabled(enabled); } public void loadValues(Project project, String qualFileName) { @@ -79,6 +91,9 @@ public void loadValues(Project project, String qualFileName) { caseTransformation.setSelectedItem(grammarProperties.getCaseChangingStrategy()); generateParseTreeListenerCheckBox.setSelected(grammarProperties.shouldGenerateParseTreeListener()); generateParseTreeVisitorCheckBox.setSelected(grammarProperties.shouldGenerateParseTreeVisitor()); + useGeneratedParserCodeCheckBox.setSelected(grammarProperties.isUseGeneratedParserCodeCheckBox()); + generatedParserClassName.setText(grammarProperties.generatedParserClassName); + generatedLexerClassName.setText(grammarProperties.generatedLexerClassName); } public void saveValues(Project project, String qualFileName) { @@ -93,6 +108,9 @@ public void saveValues(Project project, String qualFileName) { grammarProperties.caseChangingStrategy = getCaseChangingStrategy(); grammarProperties.generateListener = generateParseTreeListenerCheckBox.isSelected(); grammarProperties.generateVisitor = generateParseTreeVisitorCheckBox.isSelected(); + grammarProperties.useGeneratedParserCodeCheckBox = useGeneratedParserCodeCheckBox.isSelected(); + grammarProperties.generatedParserClassName = getGeneratedParserClassName(); + grammarProperties.generatedLexerClassName = getGeneratedLexerClassName(); } boolean isModified(ANTLRv4GrammarProperties originalProperties) { @@ -101,7 +119,10 @@ boolean isModified(ANTLRv4GrammarProperties originalProperties) { || !Objects.equals(originalProperties.getEncoding(), getFileEncodingText()) || !Objects.equals(originalProperties.getPackage(), getPackageFieldText()) || !Objects.equals(originalProperties.getLanguage(), getLanguageText()) - || !Objects.equals(originalProperties.caseChangingStrategy, getCaseChangingStrategy()); + || !Objects.equals(originalProperties.caseChangingStrategy, getCaseChangingStrategy()) + || !Objects.equals(originalProperties.isUseGeneratedParserCodeCheckBox(), isUseGeneratedParserCode()) + || !Objects.equals(originalProperties.generatedParserClassName, getGeneratedParserClassName()) + || !Objects.equals(originalProperties.generatedLexerClassName, getGeneratedLexerClassName()); } String getLanguageText() { @@ -124,6 +145,18 @@ String getOutputDirText() { return outputDirField.getText(); } + boolean isUseGeneratedParserCode() { + return useGeneratedParserCodeCheckBox.isSelected(); + } + + String getGeneratedParserClassName() { + return generatedParserClassName.getText(); + } + + String getGeneratedLexerClassName() { + return generatedLexerClassName.getText(); + } + private CaseChangingStrategy getCaseChangingStrategy() { return (CaseChangingStrategy) caseTransformation.getSelectedItem(); } @@ -139,9 +172,12 @@ public String toString() { return "ConfigANTLRPerGrammar{" + " generateParseTreeListenerCheckBox=" + generateParseTreeListenerCheckBox + ", generateParseTreeVisitorCheckBox=" + generateParseTreeVisitorCheckBox + + ", useGeneratedParserCodeCheckBox=" + useGeneratedParserCodeCheckBox + ", packageField=" + packageField + ", outputDirField=" + outputDirField + ", libDirField=" + libDirField + + ", generatedParserClassName=" + generatedParserClassName + + ", generatedLexerClassName=" + generatedLexerClassName + '}'; } 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 c28981b9..81b3dccf 100644 --- a/src/main/java/org/antlr/intellij/plugin/parsing/ParsingUtils.java +++ b/src/main/java/org/antlr/intellij/plugin/parsing/ParsingUtils.java @@ -29,13 +29,14 @@ 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.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.*; import static org.antlr.intellij.plugin.configdialogs.ANTLRv4GrammarPropertiesStore.getGrammarProperties; @@ -193,64 +194,83 @@ public static ParsingResult parseANTLRGrammar(String text) { return new ParsingResult(parser, t, listener); } - public static ParsingResult parseText(Grammar g, - LexerGrammar lg, - String startRuleName, + public static ParsingResult parseText(final Grammar g, + final LexerGrammar lg, + final Constructor lexerCtor, + final Constructor parserCtor, + final String startRuleName, final VirtualFile grammarFile, - String inputText, - Project project) { + final String inputText, + final Project project) { + if ( g==null || lg==null ) { + ANTLRv4PluginController.LOG.info("parseText can't parse: missing lexer or parser no Grammar object for " + + (grammarFile != null ? grammarFile.getName() : "")); + return null; + } + ANTLRv4GrammarProperties grammarProperties = getGrammarProperties(project, grammarFile); CharStream input = grammarProperties.getCaseChangingStrategy() .applyTo(CharStreams.fromString(inputText, grammarFile.getPath())); - LexerInterpreter lexEngine; - lexEngine = lg.createLexerInterpreter(input); - SyntaxErrorListener syntaxErrorListener = new SyntaxErrorListener(); - lexEngine.removeErrorListeners(); - lexEngine.addErrorListener(syntaxErrorListener); - CommonTokenStream tokens = new TokenStreamSubset(lexEngine); - return parseText(g, lg, startRuleName, grammarFile, syntaxErrorListener, tokens, 0); + + try { + CommonTokenStream tokens; + + LexerInterpreter lexEngine; + lexEngine = lg.createLexerInterpreter(input); + SyntaxErrorListener syntaxErrorListener = new SyntaxErrorListener(); + lexEngine.removeErrorListeners(); + lexEngine.addErrorListener(syntaxErrorListener); + + if (lexerCtor != null) { + Lexer lexer = lexerCtor.newInstance(input); + tokens = new TokenStreamSubset(lexer); + } else { + tokens = new TokenStreamSubset(lexEngine); + } + + Parser parser = null; + + if (parserCtor != null) { + parser = parserCtor.newInstance(tokens); + } + + return parseText(g, tokens, parser, syntaxErrorListener, startRuleName, 0); + } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) { + ANTLRv4PluginController.LOG.error(e); + return null; + } } public static ParsingResult parseText(Grammar g, - LexerGrammar lg, - String startRuleName, - final VirtualFile grammarFile, - SyntaxErrorListener syntaxErrorListener, TokenStream tokens, + Parser parser, + SyntaxErrorListener syntaxErrorListener, + String startRuleName, int startIndex) { - if ( g==null || lg==null ) { - ANTLRv4PluginController.LOG.info("parseText can't parse: missing lexer or parser no Grammar object for " + - (grammarFile != null ? grammarFile.getName() : "")); - return null; - } + tokens.seek(startIndex); - String grammarFileName = g.fileName; - if (!new File(grammarFileName).exists()) { - ANTLRv4PluginController.LOG.info("parseText grammar doesn't exist "+grammarFileName); - return null; - } + ParserInterpreter parserInterpreter; - if ( g==BAD_PARSER_GRAMMAR || lg==BAD_LEXER_GRAMMAR ) { - return null; + if (parser != null) { + parserInterpreter = new PreviewParser(g, parser.getATN(), tokens); + } else { + parserInterpreter = new PreviewParser(g, tokens); } - tokens.seek(startIndex); - - PreviewParser parser = new PreviewParser(g, tokens); - parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION); - parser.setProfile(true); + parserInterpreter.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION); + parserInterpreter.setProfile(true); - parser.removeErrorListeners(); - parser.addErrorListener(syntaxErrorListener); + parserInterpreter.removeErrorListeners(); + parserInterpreter.addErrorListener(syntaxErrorListener); - Rule start = g.getRule(startRuleName); - if ( start==null ) { + int startRuleIndex = parserInterpreter.getRuleIndex(startRuleName); + if ( startRuleIndex==-1 ) { return null; // can't find start rule } - ParseTree t = parser.parse(start.index); + ParseTree t = parserInterpreter.parse(startRuleIndex); if ( t!=null ) { - return new ParsingResult(parser, t, syntaxErrorListener); + return new ParsingResult(parserInterpreter, t, syntaxErrorListener); } return null; } diff --git a/src/main/java/org/antlr/intellij/plugin/preview/PreviewState.java b/src/main/java/org/antlr/intellij/plugin/preview/PreviewState.java index 7ff6757c..05204ce4 100644 --- a/src/main/java/org/antlr/intellij/plugin/preview/PreviewState.java +++ b/src/main/java/org/antlr/intellij/plugin/preview/PreviewState.java @@ -5,9 +5,13 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.antlr.intellij.plugin.parsing.ParsingResult; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Parser; import org.antlr.v4.tool.Grammar; import org.antlr.v4.tool.LexerGrammar; +import java.lang.reflect.Constructor; + /** Track everything associated with the state of the preview window. * For each grammar, we need to track an InputPanel (with <= 2 editor objects) * that we will flip to every time we come back to a specific grammar, @@ -33,6 +37,9 @@ public class PreviewState { public ParsingResult parsingResult; + public Constructor parserCtor; + public Constructor lexerCtor; + /** The current input editor (inputEditor or fileEditor) for this grammar * in InputPanel. This can be null when a PreviewState and InputPanel * are created out of sync. Depends on order IDE opens files vs