/* * Copyright 2011-present Greg Shrago * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.intellij.grammar.livePreview; import com.intellij.lang.*; import com.intellij.lexer.Lexer; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; import com.intellij.openapi.fileEditor.impl.EditorWindow; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.NotNullLazyKey; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.TokenType; import com.intellij.testFramework.LightVirtualFile; import com.intellij.util.Alarm; import com.intellij.util.FileContentUtil; import com.intellij.util.PairProcessor; import com.intellij.util.SingleAlarm; import com.intellij.util.containers.ContainerUtil; import org.intellij.grammar.parser.GeneratedParserUtilBase; import org.intellij.grammar.psi.BnfExpression; import org.intellij.grammar.psi.BnfFile; import org.intellij.grammar.psi.BnfRule; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.Collection; import java.util.Map; /** * @author gregsh */ public class LivePreviewHelper { public static void showFor(BnfFile bnfFile) { PsiFile psiFile = parseFile(bnfFile, ""); VirtualFile virtualFile = psiFile == null? null : psiFile.getVirtualFile(); if (virtualFile == null) return; Project project = bnfFile.getProject(); installUpdateListener(project); FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(project); EditorWindow curWindow = fileEditorManager.getCurrentWindow(); curWindow.split(SwingConstants.HORIZONTAL, false, virtualFile, true); fileEditorManager.openFile(virtualFile, true); } @Nullable public static PsiFile parseFile(BnfFile bnfFile, String text) { Language language = getLanguageFor(bnfFile); if (language == null) return null; String fileName = bnfFile.getName() + ".preview"; LightVirtualFile virtualFile = new LightVirtualFile(fileName, language, text); final Project project = bnfFile.getProject(); return PsiManager.getInstance(project).findFile(virtualFile); } @Nullable public static Language getLanguageFor(BnfFile psiFile) { LivePreviewLanguage existing = LivePreviewLanguage.findInstance(psiFile); if (existing != null) return existing; LivePreviewLanguage language = LivePreviewLanguage.newInstance(psiFile); registerLanguageExtensions(language); return language; } public static void registerLanguageExtensions(LivePreviewLanguage language) { LanguageStructureViewBuilder.INSTANCE.addExplicitExtension(language, new LivePreviewStructureViewFactory()); LanguageParserDefinitions.INSTANCE.addExplicitExtension(language, new LivePreviewParserDefinition(language)); //SyntaxHighlighterFactory.LANGUAGE_FACTORY.addExplicitExtension(language, new LivePreviewSyntaxHighlighterFactory(language)); } public static void unregisterLanguageExtensions(LivePreviewLanguage language) { LanguageStructureViewBuilder.INSTANCE.removeExplicitExtension(language, LanguageStructureViewBuilder.INSTANCE.forLanguage(language)); LanguageParserDefinitions.INSTANCE.removeExplicitExtension(language, LanguageParserDefinitions.INSTANCE.forLanguage(language)); } private static final NotNullLazyKey<SingleAlarm, Project> LIVE_PREVIEW_ALARM = NotNullLazyKey.create("LIVE_PREVIEW_ALARM", project -> new SingleAlarm(() -> reparseAllLivePreviews(project), 300, Alarm.ThreadToUse.SWING_THREAD, project)); private static void installUpdateListener(final Project project) { EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new DocumentAdapter() { FileDocumentManager fileManager = FileDocumentManager.getInstance(); PsiManager psiManager = PsiManager.getInstance(project); @Override public void documentChanged(DocumentEvent e) { Document document = e.getDocument(); VirtualFile file = fileManager.getFile(document); PsiFile psiFile = file == null ? null : psiManager.findFile(file); if (psiFile instanceof BnfFile) { LIVE_PREVIEW_ALARM.getValue(project).cancelAndRequest(); } } }, project); //project.getMessageBus().connect(project).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() { // @Override // public void fileOpened(FileEditorManager source, VirtualFile file) { // // // // add structure component // // FileEditor fileEditor = source.getSelectedEditor(file); // if (!(fileEditor instanceof TextEditor)) return; // StructureViewBuilder builder = // StructureViewBuilder.PROVIDER.getStructureViewBuilder(file.getFileType(), file, project); // if (builder == null) return; // StructureView structureView = builder.createStructureView(fileEditor, project); // // Editor editor = ((TextEditor)fileEditor).getEditor(); // editor.getComponent().getParent().getParent().add(structureView.getComponent(), BorderLayout.EAST); // Disposer.register(fileEditor, structureView); // } //}); } private static void reparseAllLivePreviews(@NotNull Project project) { if (!project.isOpen()) return; PsiDocumentManager.getInstance(project).commitAllDocuments(); Collection<VirtualFile> files = ContainerUtil.newLinkedHashSet(); FileEditorManager fileEditorManager = FileEditorManager.getInstance(project); PsiManager psiManager = PsiManager.getInstance(project); for (VirtualFile file : fileEditorManager.getOpenFiles()) { PsiFile psiFile = psiManager.findFile(file); Language language = psiFile == null? null : psiFile.getLanguage(); if (!(language instanceof LivePreviewLanguage)) continue; files.add(file); } FileContentUtil.reparseFiles(project, files, false); } public static boolean collectExpressionsAtOffset(Project project, Editor previewEditor, LivePreviewLanguage language, final PairProcessor<BnfExpression, Boolean> processor) { Lexer lexer = new LivePreviewLexer(project, language); final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(language); final PsiBuilder builder = PsiBuilderFactory.getInstance().createBuilder(parserDefinition, lexer, previewEditor.getDocument().getText()); final int caretOffset = previewEditor.getCaretModel().getOffset(); final PsiParser parser = new LivePreviewParser(project, language) { @Override protected boolean generateNodeCall(PsiBuilder builder, int level, BnfRule rule, @Nullable BnfExpression node, String nextName, Map<String, GeneratedParserUtilBase.Parser> externalArguments) { int tokenStartOffset = builder.getCurrentOffset(); int initialOffset = builder.rawLookup(-1) == TokenType.WHITE_SPACE ? builder.rawTokenTypeStart(-1) : builder.getCurrentOffset(); String tokenText = builder.getTokenText(); int tokenEndOffset = tokenText == null? tokenStartOffset : tokenStartOffset + tokenText.length(); boolean result = super.generateNodeCall(builder, level, rule, node, nextName, externalArguments); builder.getCurrentOffset(); // advance to the next token first int finalOffset = builder.rawLookup(-1) == TokenType.WHITE_SPACE ? builder.rawTokenTypeStart(-1) : builder.getCurrentOffset(); if (node != null) { if (result && initialOffset <= caretOffset && finalOffset > caretOffset || !result && initialOffset <= caretOffset && tokenEndOffset > caretOffset) { boolean inWhitespace = isTokenExpression(node) && initialOffset <= caretOffset && tokenStartOffset > caretOffset; if (!processor.process(node, result && !inWhitespace)) { throw new ProcessCanceledException(); } } } return result; } }; try { parser.parse(parserDefinition.getFileNodeType(), builder); return true; } catch (ProcessCanceledException e) { return false; } } }