private void removeAllContents(Project project, Content notRemove) { if (project.isDisposed()) { return; } final MessageView messageView = MessageView.SERVICE.getInstance(project); Content[] contents = messageView.getContentManager().getContents(); for (Content content : contents) { if (content.isPinned()) { continue; } if (content == notRemove) { continue; } boolean toRemove = CONTENT_ID_KEY.get(content) == myContentId; if (!toRemove) { final Object contentSessionId = SESSION_ID_KEY.get(content); toRemove = contentSessionId != null && contentSessionId != mySessionId; // the content was added by previous compilation } if (toRemove) { messageView.getContentManager().removeContent(content, true); } } }
/** @author max */ public class FileContextUtil { public static final Key<SmartPsiElementPointer> INJECTED_IN_ELEMENT = Key.create("injectedIn"); public static final Key<PsiFile> CONTAINING_FILE_KEY = Key.create("CONTAINING_FILE_KEY"); private FileContextUtil() {} @Nullable public static PsiElement getFileContext(PsiFile file) { SmartPsiElementPointer pointer = file.getUserData(INJECTED_IN_ELEMENT); return pointer == null ? null : pointer.getElement(); } @Nullable public static PsiFile getContextFile(@NotNull PsiElement element) { if (!element.isValid()) return null; PsiFile file = element.getContainingFile(); if (file == null) return null; PsiElement context = file.getContext(); if (context == null) { return file; } else { return getContextFile(context); } } }
@Override public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) { final InstrumentationClassFinder finder = CLASS_FINDER.get(context); if (finder != null) { CLASS_FINDER.set(context, null); finder.releaseResources(); } }
@NotNull public static StatisticsInfo getBaseStatisticsInfo( LookupElement item, @Nullable CompletionLocation location) { StatisticsInfo info = BASE_STATISTICS_INFO.get(item); if (info == null) { if (location == null) { return StatisticsInfo.EMPTY; } BASE_STATISTICS_INFO.set(item, info = calcBaseInfo(item, location)); } return info; }
private static boolean checkChunkRebuildNeeded( CompileContext context, GroovycOutputParser parser) { if (JavaBuilderUtil.isForcedRecompilationAllJavaModules(context) || !parser.shouldRetry()) { return false; } if (CHUNK_REBUILD_ORDERED.get(context) != null) { CHUNK_REBUILD_ORDERED.set(context, null); return false; } CHUNK_REBUILD_ORDERED.set(context, Boolean.TRUE); LOG.info("Order chunk rebuild"); return true; }
@Override public void compileAndRun( @NotNull final Runnable startRunnable, @NotNull final ExecutionEnvironment environment, @Nullable final RunProfileState state, @Nullable final Runnable onCancelRunnable) { long id = environment.getExecutionId(); if (id == 0) { id = environment.assignNewExecutionId(); } RunProfile profile = environment.getRunProfile(); if (!(profile instanceof RunConfiguration)) { startRunnable.run(); return; } final RunConfiguration runConfiguration = (RunConfiguration) profile; final List<BeforeRunTask> beforeRunTasks = RunManagerEx.getInstanceEx(myProject).getBeforeRunTasks(runConfiguration); if (beforeRunTasks.isEmpty()) { startRunnable.run(); } else { DataContext context = environment.getDataContext(); final DataContext projectContext = context != null ? context : SimpleDataContext.getProjectContext(myProject); final long finalId = id; final Long executionSessionId = new Long(id); ApplicationManager.getApplication() .executeOnPooledThread( () -> { for (BeforeRunTask task : beforeRunTasks) { if (myProject.isDisposed()) { return; } @SuppressWarnings("unchecked") BeforeRunTaskProvider<BeforeRunTask> provider = BeforeRunTaskProvider.getProvider(myProject, task.getProviderId()); if (provider == null) { LOG.warn( "Cannot find BeforeRunTaskProvider for id='" + task.getProviderId() + "'"); continue; } ExecutionEnvironment taskEnvironment = new ExecutionEnvironmentBuilder(environment).contentToReuse(null).build(); taskEnvironment.setExecutionId(finalId); EXECUTION_SESSION_ID_KEY.set(taskEnvironment, executionSessionId); if (!provider.executeTask( projectContext, runConfiguration, taskEnvironment, task)) { if (onCancelRunnable != null) { SwingUtilities.invokeLater(onCancelRunnable); } return; } } doRun(environment, startRunnable); }); } }
@Override public void buildStarted(CompileContext context) { final JpsProject project = context.getProjectDescriptor().getProject(); final JpsJavaCompilerConfiguration config = JpsJavaExtensionService.getInstance().getCompilerConfiguration(project); final String compilerId = config == null ? JavaCompilers.JAVAC_ID : config.getJavaCompilerId(); if (LOG.isDebugEnabled()) { LOG.debug("Java compiler ID: " + compilerId); } final boolean isJavac = JavaCompilers.JAVAC_ID.equalsIgnoreCase(compilerId) || JavaCompilers.JAVAC_API_ID.equalsIgnoreCase(compilerId); final boolean isEclipse = JavaCompilers.ECLIPSE_ID.equalsIgnoreCase(compilerId) || JavaCompilers.ECLIPSE_EMBEDDED_ID.equalsIgnoreCase(compilerId); IS_ENABLED.set(context, isJavac || isEclipse); String messageText = null; if (isJavac) { messageText = "Using javac " + System.getProperty("java.version") + " to compile java sources"; } else if (isEclipse) { messageText = "Using eclipse compiler to compile java sources"; } COMPILER_VERSION_INFO.set(context, new AtomicReference<String>(messageText)); }
@NotNull @Override public Navigatable createNavigatable(@NotNull Project project) { if (ALTERNATIVE_SOURCE_KEY.get(myFile) != null) { return new OpenFileDescriptor(project, getFile(), getLine(), 0); } return XSourcePositionImpl.doCreateOpenFileDescriptor(project, this); }
public abstract class RefJavaManager implements RefManagerExtension<RefJavaManager> { @NonNls public static final String CLASS = "class"; @NonNls public static final String METHOD = "method"; @NonNls public static final String IMPLICIT_CONSTRUCTOR = "implicit.constructor"; @NonNls public static final String FIELD = "field"; @NonNls public static final String PARAMETER = "parameter"; @NonNls public static final String JAVA_MODULE = "java.module"; // used in OfflineProjectDescriptor @NonNls public static final String PACKAGE = "package"; public static final Key<RefJavaManager> MANAGER = Key.create("RefJavaManager"); public abstract RefImplicitConstructor getImplicitConstructor(String classFQName); /** * Creates (if necessary) and returns the reference graph node for the package with the specified * name. * * @param packageName the name of the package for which the reference graph node is requested. * @return the node for the package. */ public abstract RefPackage getPackage(String packageName); /** * Creates (if necessary) and returns the reference graph node for the specified PSI parameter. * * @param param the parameter for which the reference graph node is requested. * @param index the index of the parameter in its parameter list. * @return the node for the element, or null if the element is not valid or does not have a * corresponding reference graph node type (is not a field, method, class or file). */ public abstract RefParameter getParameterReference(PsiParameter param, int index); public abstract RefPackage getDefaultPackage(); public abstract PsiMethod getAppMainPattern(); public abstract PsiMethod getAppPremainPattern(); public abstract PsiMethod getAppAgentmainPattern(); public abstract PsiClass getApplet(); public abstract PsiClass getServlet(); public abstract EntryPointsManager getEntryPointsManager(); @NotNull @Override public Language getLanguage() { return JavaLanguage.INSTANCE; } @NotNull @Override public Key<RefJavaManager> getID() { return MANAGER; } }
@NotNull @Override public VirtualFile getFile() { VirtualFile file = ALTERNATIVE_SOURCE_KEY.get(myFile); if (file != null) { return file; } return myFile; }
private static int getItemToSelect( LookupImpl lookup, List<LookupElement> items, boolean onExplicitAction, @Nullable LookupElement mostRelevant) { if (items.isEmpty() || lookup.getFocusDegree() == LookupImpl.FocusDegree.UNFOCUSED) { return 0; } if (lookup.isSelectionTouched() || !onExplicitAction) { final LookupElement lastSelection = lookup.getCurrentItem(); int old = ContainerUtil.indexOfIdentity(items, lastSelection); if (old >= 0) { return old; } Object selectedValue = ((LookupImpl) lookup).getList().getSelectedValue(); if (selectedValue instanceof EmptyLookupItem && ((EmptyLookupItem) selectedValue).isLoading()) { int index = ((LookupImpl) lookup).getList().getSelectedIndex(); if (index >= 0 && index < items.size()) { return index; } } for (int i = 0; i < items.size(); i++) { String invariant = PRESENTATION_INVARIANT.get(items.get(i)); if (invariant != null && invariant.equals(PRESENTATION_INVARIANT.get(lastSelection))) { return i; } } } String selectedText = lookup.getEditor().getSelectionModel().getSelectedText(); for (int i = 0; i < items.size(); i++) { LookupElement item = items.get(i); if (isPrefixItem(lookup, item, true) && !isLiveTemplate(item) || item.getLookupString().equals(selectedText)) { return i; } } return Math.max(0, ContainerUtil.indexOfIdentity(items, mostRelevant)); }
private static int getCompilerSdkVersion(CompileContext context) { final Integer cached = JAVA_COMPILER_VERSION_KEY.get(context); if (cached != null) { return cached; } int javaVersion = convertToNumber(SystemProperties.getJavaVersion()); if (!USE_EMBEDDED_JAVAC) { // in case of external javac, run compiler from the newest jdk that is used in the project for (JpsSdk<?> sdk : context.getProjectDescriptor().getProjectJavaSdks()) { final String version = sdk.getVersionString(); final int ver = convertToNumber(version); if (ver > javaVersion) { javaVersion = ver; } } } JAVA_COMPILER_VERSION_KEY.set(context, javaVersion); return javaVersion; }
public ExitCode build( final CompileContext context, final ModuleChunk chunk, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, OutputConsumer outputConsumer) throws ProjectBuildException { if (!IS_ENABLED.get(context, Boolean.TRUE)) { return ExitCode.NOTHING_DONE; } try { final Map<File, ModuleBuildTarget> filesToCompile = new THashMap<File, ModuleBuildTarget>(FileUtil.FILE_HASHING_STRATEGY); dirtyFilesHolder.processDirtyFiles( new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() { public boolean apply( ModuleBuildTarget target, File file, JavaSourceRootDescriptor descriptor) throws IOException { if (JAVA_SOURCES_FILTER.accept(file)) { filesToCompile.put(file, target); } return true; } }); if (context.isMake()) { final ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger(); if (logger.isEnabled()) { if (filesToCompile.size() > 0) { logger.logCompiledFiles(filesToCompile.keySet(), BUILDER_NAME, "Compiling files:"); } } } return compile(context, chunk, dirtyFilesHolder, filesToCompile.keySet(), outputConsumer); } catch (ProjectBuildException e) { throw e; } catch (Exception e) { String message = e.getMessage(); if (message == null) { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final PrintStream stream = new PrintStream(out); try { e.printStackTrace(stream); } finally { stream.close(); } message = "Internal error: \n" + out.toString(); } context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message)); throw new ProjectBuildException(message, e); } }
public static void deleteTypeAnnotations(@NotNull PsiTypeElement typeElement) { PsiElement left = PsiTreeUtil.skipSiblingsBackward( typeElement, PsiComment.class, PsiWhiteSpace.class, PsiTypeParameterList.class); if (left instanceof PsiModifierList) { for (PsiAnnotation annotation : ((PsiModifierList) left).getAnnotations()) { if (TYPE_ANNO_MARK.get(annotation) == Boolean.TRUE) { annotation.delete(); } } } }
public void setBreakpointDefaults( Key<? extends Breakpoint> category, BreakpointDefaults defaults) { Class typeCls = null; if (LineBreakpoint.CATEGORY.toString().equals(category.toString())) { typeCls = JavaLineBreakpointType.class; } else if (MethodBreakpoint.CATEGORY.toString().equals(category.toString())) { typeCls = JavaMethodBreakpointType.class; } else if (FieldBreakpoint.CATEGORY.toString().equals(category.toString())) { typeCls = JavaFieldBreakpointType.class; } else if (ExceptionBreakpoint.CATEGORY.toString().equals(category.toString())) { typeCls = JavaExceptionBreakpointType.class; } if (typeCls != null) { XBreakpointType<XBreakpoint<?>, ?> type = XDebuggerUtil.getInstance().findBreakpointType(typeCls); ((XBreakpointManagerImpl) getXBreakpointManager()) .getBreakpointDefaults(type) .setSuspendPolicy(Breakpoint.transformSuspendPolicy(defaults.getSuspendPolicy())); } // myBreakpointDefaults.put(category, defaults); }
private static <T extends Breakpoint> Element getCategoryGroupElement( @NotNull final Map<Key<? extends Breakpoint>, Element> categoryToElementMap, @NotNull final Key<T> category, @NotNull final Element parentNode) { Element group = categoryToElementMap.get(category); if (group == null) { group = new Element(category.toString()); categoryToElementMap.put(category, group); parentNode.addContent(group); } return group; }
// error tree view initialization must be invoked from event dispatch thread private void openMessageView() { if (isHeadlessMode()) { return; } if (myIndicator.isCanceled()) { return; } final JComponent component; synchronized (myMessageViewLock) { if (myErrorTreeView != null) { return; } myErrorTreeView = new CompilerErrorTreeView(myProject, myRestartWork); myErrorTreeView.setProcessController( new NewErrorTreeViewPanel.ProcessController() { @Override public void stopProcess() { cancel(); } @Override public boolean isProcessStopped() { return !myIndicator.isRunning(); } }); component = myErrorTreeView.getComponent(); } final MessageView messageView = MessageView.SERVICE.getInstance(myProject); final Content content = ContentFactory.SERVICE.getInstance().createContent(component, myContentName, true); CONTENT_ID_KEY.set(content, myContentId); SESSION_ID_KEY.set(content, mySessionId); messageView.getContentManager().addContent(content); myCloseListener.setContent(content, messageView.getContentManager()); removeAllContents(myProject, content); messageView.getContentManager().setSelectedContent(content); }
@Override public final ExitCode build( CompileContext context, ModuleChunk chunk, DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, OutputConsumer outputConsumer) throws ProjectBuildException, IOException { if (outputConsumer.getCompiledClasses().isEmpty() || !isEnabled(context, chunk)) { return ExitCode.NOTHING_DONE; } final String progress = getProgressMessage(); final boolean shouldShowProgress = !StringUtil.isEmptyOrSpaces(progress); if (shouldShowProgress) { context.processMessage(new ProgressMessage(progress + " [" + chunk.getName() + "]")); } ExitCode exitCode = ExitCode.NOTHING_DONE; try { InstrumentationClassFinder finder = CLASS_FINDER.get(context); // try using shared finder if (finder == null) { final Collection<File> platformCp = ProjectPaths.getPlatformCompilationClasspath(chunk, false); final Collection<File> classpath = new ArrayList<File>(); classpath.addAll(ProjectPaths.getCompilationClasspath(chunk, false)); classpath.addAll(ProjectPaths.getSourceRootsWithDependents(chunk).keySet()); finder = createInstrumentationClassFinder(platformCp, classpath, outputConsumer); CLASS_FINDER.set(context, finder); } exitCode = performBuild(context, chunk, finder, outputConsumer); } finally { if (shouldShowProgress) { context.processMessage(new ProgressMessage("")); // cleanup progress } } return exitCode; }
/** * @return breakpoints of one of the category: LINE_BREAKPOINTS, EXCEPTION_BREAKPOINTS, * FIELD_BREAKPOINTS, METHOD_BREAKPOINTS */ public <T extends Breakpoint> Breakpoint[] getBreakpoints(@NotNull final Key<T> category) { ApplicationManager.getApplication().assertIsDispatchThread(); removeInvalidBreakpoints(); final ArrayList<Breakpoint> breakpoints = new ArrayList<Breakpoint>(); for (Breakpoint breakpoint : getBreakpoints()) { if (category.equals(breakpoint.getCategory())) { breakpoints.add(breakpoint); } } return breakpoints.toArray(new Breakpoint[breakpoints.size()]); }
public void showCompilerContent() { synchronized (myMessageViewLock) { if (myErrorTreeView != null) { final MessageView messageView = MessageView.SERVICE.getInstance(myProject); Content[] contents = messageView.getContentManager().getContents(); for (Content content : contents) { if (CONTENT_ID_KEY.get(content) == myContentId) { messageView.getContentManager().setSelectedContent(content); return; } } } } }
/** @param category breakpoint category, null if the category does not matter */ @Nullable public <T extends BreakpointWithHighlighter> T findBreakpoint( final Document document, final int offset, @Nullable final Key<T> category) { for (final Breakpoint breakpoint : getBreakpoints()) { if (breakpoint instanceof BreakpointWithHighlighter && ((BreakpointWithHighlighter) breakpoint).isAt(document, offset)) { if (category == null || category.equals(breakpoint.getCategory())) { //noinspection CastConflictsWithInstanceof,unchecked return (T) breakpoint; } } } return null; }
/** * @author Dmitry Avdeev * @author peter */ public class PerIndexDocumentVersionMap { private static final int INVALID_STAMP = -1; // 0 isn't acceptable as Document has 0 stamp when loaded from unchanged file private volatile int mapVersion; private static class IdVersionInfo { private final ID<?, ?> id; private int mapVersion; private long docVersion; private IdVersionInfo(@NotNull ID<?, ?> id, long docVersion, int mapVersion) { this.docVersion = docVersion; this.mapVersion = mapVersion; this.id = id; } } private static final Key<List<IdVersionInfo>> KEY = Key.create("UnsavedDocIdVersionInfo"); public long getAndSet(@NotNull Document document, @NotNull ID<?, ?> indexId, long value) { List<IdVersionInfo> list = document.getUserData(KEY); if (list == null) { list = ((UserDataHolderEx) document).putUserDataIfAbsent(KEY, new ArrayList<IdVersionInfo>()); } synchronized (list) { for (IdVersionInfo info : list) { if (info.id == indexId) { long old = info.docVersion; if (info.mapVersion != mapVersion) { old = INVALID_STAMP; info.mapVersion = mapVersion; } info.docVersion = value; return old; } } list.add(new IdVersionInfo(indexId, value, mapVersion)); return INVALID_STAMP; } } public void clear() { mapVersion++; } }
public interface IGosuMethod extends IGosuMembersDeclaration, PsiMethod, IGosuNamedElement, IGosuMember, IGosuParametersOwner, IGosuTypeParameterListOwner { Key<Boolean> BUILDER_METHOD = Key.create("BUILDER_METHOD"); IGosuMethod[] EMPTY_ARRAY = new IGosuMethod[0]; ArrayFactory<IGosuMethod> ARRAY_FACTORY = new ArrayFactory<IGosuMethod>() { @NotNull public IGosuMethod[] create(int count) { return count == 0 ? EMPTY_ARRAY : new IGosuMethod[count]; } }; IGosuTypeElement getReturnTypeElementGosu(); /** @return the static return type, which will appear in the compiled Gosu class */ @Nullable PsiType getReturnType(); @Nullable IGosuTypeElement setReturnType(PsiType newReturnType); @NotNull String getName(); @NotNull IGosuParameterList getParameterList(); @NotNull IGosuModifierList getModifierList(); String[] getNamedParametersArray(); boolean isForProperty(); boolean isForPropertySetter(); boolean isForPropertyGetter(); }
public abstract class HTMLJavaHTMLComposer implements HTMLComposerExtension<HTMLJavaHTMLComposer> { public static final Key<HTMLJavaHTMLComposer> COMPOSER = Key.create("HTMLJavaComposer"); public abstract void appendClassOrInterface( StringBuffer buf, RefClass refClass, boolean capitalizeFirstLetter); public static String getClassOrInterface(RefClass refClass, boolean capitalizeFirstLetter) { if (refClass.isInterface()) { return capitalizeFirstLetter ? InspectionsBundle.message("inspection.export.results.capitalized.interface") : InspectionsBundle.message("inspection.export.results.interface"); } else if (refClass.isAbstract()) { return capitalizeFirstLetter ? InspectionsBundle.message("inspection.export.results.capitalized.abstract.class") : InspectionsBundle.message("inspection.export.results.abstract.class"); } else { return capitalizeFirstLetter ? InspectionsBundle.message("inspection.export.results.capitalized.class") : InspectionsBundle.message("inspection.export.results.class"); } } public abstract void appendClassExtendsImplements(StringBuffer buf, RefClass refClass); public abstract void appendDerivedClasses(StringBuffer buf, RefClass refClass); public abstract void appendLibraryMethods(StringBuffer buf, RefClass refClass); public abstract void appendSuperMethods(StringBuffer buf, RefMethod refMethod); public abstract void appendDerivedMethods(StringBuffer buf, RefMethod refMethod); public abstract void appendTypeReferences(StringBuffer buf, RefClass refClass); public Key<HTMLJavaHTMLComposer> getID() { return COMPOSER; } public Language getLanguage() { return StdLanguages.JAVA; } }
private static final class CompileStatistics { private static final Key<CompileStatistics> KEY = Key.create("_Compile_Statistics_"); private int myClassesCount; private int myFilesCount; public int getClassesCount() { return myClassesCount; } public int incClassesCount() { return ++myClassesCount; } public int getFilesCount() { return myFilesCount; } public int incFilesCount() { return ++myFilesCount; } }
public void process(CompileContext context, OutputFileObject out) { Map<String, String> stubToSrc = STUB_TO_SRC.get(context); if (stubToSrc == null) { return; } File src = out.getSourceFile(); if (src == null) { return; } String groovy = stubToSrc.get(FileUtil.toSystemIndependentName(src.getPath())); if (groovy == null) { return; } try { final File groovyFile = new File(groovy); if (!FSOperations.isMarkedDirty(context, CompilationRound.CURRENT, groovyFile)) { FSOperations.markDirty(context, CompilationRound.NEXT, groovyFile); FILES_MARKED_DIRTY_FOR_NEXT_ROUND.set(context, Boolean.TRUE); } } catch (IOException e) { LOG.error(e); } }
public class IndentsPass extends TextEditorHighlightingPass implements DumbAware { private static final ConcurrentMap<IElementType, String> COMMENT_PREFIXES = ContainerUtil.newConcurrentMap(); private static final String NO_COMMENT_INFO_MARKER = "hopefully, noone uses this string as a comment prefix"; private static final Key<List<RangeHighlighter>> INDENT_HIGHLIGHTERS_IN_EDITOR_KEY = Key.create("INDENT_HIGHLIGHTERS_IN_EDITOR_KEY"); private static final Key<Long> LAST_TIME_INDENTS_BUILT = Key.create("LAST_TIME_INDENTS_BUILT"); private final EditorEx myEditor; private final PsiFile myFile; public static final Comparator<TextRange> RANGE_COMPARATOR = new Comparator<TextRange>() { @Override public int compare(TextRange o1, TextRange o2) { if (o1.getStartOffset() == o2.getStartOffset()) { return o1.getEndOffset() - o2.getEndOffset(); } return o1.getStartOffset() - o2.getStartOffset(); } }; private static final CustomHighlighterRenderer RENDERER = new CustomHighlighterRenderer() { @Override @SuppressWarnings({"AssignmentToForLoopParameter"}) public void paint( @NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) { int startOffset = highlighter.getStartOffset(); final Document doc = highlighter.getDocument(); if (startOffset >= doc.getTextLength()) return; final int endOffset = highlighter.getEndOffset(); final int endLine = doc.getLineNumber(endOffset); int off; int startLine = doc.getLineNumber(startOffset); IndentGuideDescriptor descriptor = editor.getIndentsModel().getDescriptor(startLine, endLine); final CharSequence chars = doc.getCharsSequence(); do { int start = doc.getLineStartOffset(startLine); int end = doc.getLineEndOffset(startLine); off = CharArrayUtil.shiftForward(chars, start, end, " \t"); startLine--; } while (startLine > 1 && off < doc.getTextLength() && chars.charAt(off) == '\n'); final VisualPosition startPosition = editor.offsetToVisualPosition(off); int indentColumn = startPosition.column; // It's considered that indent guide can cross not only white space but comments, javadocs // etc. Hence, there is a possible // case that the first indent guide line is, say, single-line comment where comment // symbols ('//') are located at the first // visual column. We need to calculate correct indent guide column then. int lineShift = 1; if (indentColumn <= 0 && descriptor != null) { indentColumn = descriptor.indentLevel; lineShift = 0; } if (indentColumn <= 0) return; final FoldingModel foldingModel = editor.getFoldingModel(); if (foldingModel.isOffsetCollapsed(off)) return; final FoldRegion headerRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(doc.getLineNumber(off))); final FoldRegion tailRegion = foldingModel.getCollapsedRegionAtOffset( doc.getLineStartOffset(doc.getLineNumber(endOffset))); if (tailRegion != null && tailRegion == headerRegion) return; final boolean selected; final IndentGuideDescriptor guide = editor.getIndentsModel().getCaretIndentGuide(); if (guide != null) { final CaretModel caretModel = editor.getCaretModel(); final int caretOffset = caretModel.getOffset(); selected = caretOffset >= off && caretOffset < endOffset && caretModel.getLogicalPosition().column == indentColumn; } else { selected = false; } Point start = editor.visualPositionToXY( new VisualPosition(startPosition.line + lineShift, indentColumn)); final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset); Point end = editor.visualPositionToXY(new VisualPosition(endPosition.line, endPosition.column)); int maxY = end.y; if (endPosition.line == editor.offsetToVisualPosition(doc.getTextLength()).line) { maxY += editor.getLineHeight(); } Rectangle clip = g.getClipBounds(); if (clip != null) { if (clip.y >= maxY || clip.y + clip.height <= start.y) { return; } maxY = Math.min(maxY, clip.y + clip.height); } final EditorColorsScheme scheme = editor.getColorsScheme(); g.setColor( selected ? scheme.getColor(EditorColors.SELECTED_INDENT_GUIDE_COLOR) : scheme.getColor(EditorColors.INDENT_GUIDE_COLOR)); // There is a possible case that indent line intersects soft wrap-introduced text. // Example: // this is a long line <soft-wrap> // that| is soft-wrapped // | // | <- vertical indent // // Also it's possible that no additional intersections are added because of soft wrap: // this is a long line <soft-wrap> // | that is soft-wrapped // | // | <- vertical indent // We want to use the following approach then: // 1. Show only active indent if it crosses soft wrap-introduced text; // 2. Show indent as is if it doesn't intersect with soft wrap-introduced text; if (selected) { g.drawLine(start.x + 2, start.y, start.x + 2, maxY); } else { int y = start.y; int newY = start.y; SoftWrapModel softWrapModel = editor.getSoftWrapModel(); int lineHeight = editor.getLineHeight(); for (int i = Math.max(0, startLine + lineShift); i < endLine && newY < maxY; i++) { List<? extends SoftWrap> softWraps = softWrapModel.getSoftWrapsForLine(i); int logicalLineHeight = softWraps.size() * lineHeight; if (i > startLine + lineShift) { logicalLineHeight += lineHeight; // We assume that initial 'y' value points just below the target // line. } if (!softWraps.isEmpty() && softWraps.get(0).getIndentInColumns() < indentColumn) { if (y < newY || i > startLine + lineShift) { // There is a possible case that soft wrap is located on // indent start line. g.drawLine(start.x + 2, y, start.x + 2, newY + lineHeight); } newY += logicalLineHeight; y = newY; } else { newY += logicalLineHeight; } FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(i)); if (foldRegion != null && foldRegion.getEndOffset() < doc.getTextLength()) { i = doc.getLineNumber(foldRegion.getEndOffset()); } } if (y < maxY) { g.drawLine(start.x + 2, y, start.x + 2, maxY); } } } }; private volatile List<TextRange> myRanges; private volatile List<IndentGuideDescriptor> myDescriptors; public IndentsPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { super(project, editor.getDocument(), false); myEditor = (EditorEx) editor; myFile = file; } @Override public void doCollectInformation(@NotNull ProgressIndicator progress) { assert myDocument != null; final Long stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT); if (stamp != null && stamp.longValue() == nowStamp()) return; myDescriptors = buildDescriptors(); ArrayList<TextRange> ranges = new ArrayList<TextRange>(); for (IndentGuideDescriptor descriptor : myDescriptors) { ProgressManager.checkCanceled(); int endOffset = descriptor.endLine < myDocument.getLineCount() ? myDocument.getLineStartOffset(descriptor.endLine) : myDocument.getTextLength(); ranges.add(new TextRange(myDocument.getLineStartOffset(descriptor.startLine), endOffset)); } Collections.sort(ranges, RANGE_COMPARATOR); myRanges = ranges; } private long nowStamp() { if (!myEditor.getSettings().isIndentGuidesShown()) return -1; assert myDocument != null; return myDocument.getModificationStamp(); } @Override public void doApplyInformationToEditor() { final Long stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT); if (stamp != null && stamp.longValue() == nowStamp()) return; List<RangeHighlighter> oldHighlighters = myEditor.getUserData(INDENT_HIGHLIGHTERS_IN_EDITOR_KEY); final List<RangeHighlighter> newHighlighters = new ArrayList<RangeHighlighter>(); final MarkupModel mm = myEditor.getMarkupModel(); int curRange = 0; if (oldHighlighters != null) { int curHighlight = 0; while (curRange < myRanges.size() && curHighlight < oldHighlighters.size()) { TextRange range = myRanges.get(curRange); RangeHighlighter highlighter = oldHighlighters.get(curHighlight); int cmp = compare(range, highlighter); if (cmp < 0) { newHighlighters.add(createHighlighter(mm, range)); curRange++; } else if (cmp > 0) { highlighter.dispose(); curHighlight++; } else { newHighlighters.add(highlighter); curHighlight++; curRange++; } } for (; curHighlight < oldHighlighters.size(); curHighlight++) { RangeHighlighter highlighter = oldHighlighters.get(curHighlight); highlighter.dispose(); } } final int startRangeIndex = curRange; assert myDocument != null; DocumentUtil.executeInBulk( myDocument, myRanges.size() > 10000, new Runnable() { @Override public void run() { for (int i = startRangeIndex; i < myRanges.size(); i++) { newHighlighters.add(createHighlighter(mm, myRanges.get(i))); } } }); myEditor.putUserData(INDENT_HIGHLIGHTERS_IN_EDITOR_KEY, newHighlighters); myEditor.putUserData(LAST_TIME_INDENTS_BUILT, nowStamp()); myEditor.getIndentsModel().assumeIndents(myDescriptors); } private List<IndentGuideDescriptor> buildDescriptors() { if (!myEditor.getSettings().isIndentGuidesShown()) return Collections.emptyList(); IndentsCalculator calculator = new IndentsCalculator(); calculator.calculate(); int[] lineIndents = calculator.lineIndents; TIntIntHashMap effectiveCommentColumns = calculator.indentAfterUncomment; List<IndentGuideDescriptor> descriptors = new ArrayList<IndentGuideDescriptor>(); IntStack lines = new IntStack(); IntStack indents = new IntStack(); lines.push(0); indents.push(0); assert myDocument != null; final CharSequence chars = myDocument.getCharsSequence(); for (int line = 1; line < lineIndents.length; line++) { ProgressManager.checkCanceled(); int curIndent = lineIndents[line]; while (!indents.empty() && curIndent <= indents.peek()) { ProgressManager.checkCanceled(); final int level = indents.pop(); int startLine = lines.pop(); if (level > 0) { boolean addDescriptor = effectiveCommentColumns.contains(startLine); // Indent started at comment if (!addDescriptor) { for (int i = startLine; i < line; i++) { if (level != lineIndents[i] && level != effectiveCommentColumns.get(i)) { addDescriptor = true; break; } } } if (addDescriptor) { descriptors.add(createDescriptor(level, startLine, line, chars)); } } } int prevLine = line - 1; int prevIndent = lineIndents[prevLine]; if (curIndent - prevIndent > 1) { lines.push(prevLine); indents.push(prevIndent); } } while (!indents.empty()) { ProgressManager.checkCanceled(); final int level = indents.pop(); int startLine = lines.pop(); if (level > 0) { descriptors.add(createDescriptor(level, startLine, myDocument.getLineCount(), chars)); } } return descriptors; } private IndentGuideDescriptor createDescriptor( int level, int startLine, int endLine, CharSequence chars) { while (startLine > 0 && isBlankLine(startLine, chars)) startLine--; return new IndentGuideDescriptor(level, startLine, endLine); } private boolean isBlankLine(int line, CharSequence chars) { Document document = myDocument; if (document == null) { return true; } int startOffset = document.getLineStartOffset(line); int endOffset = document.getLineEndOffset(line); return CharArrayUtil.shiftForward(chars, startOffset, endOffset, " \t") >= myDocument.getLineEndOffset(line); } /** * We want to treat comments specially in a way to skip comment prefix on line indent calculation. * * <p>Example: * * <pre> * if (true) { * int i1; * // int i2; * int i3; * } * </pre> * * We want to use 'int i2;' start offset as the third line indent (though it has non-white space * comment prefix (//) at the first column. * * <p>This method tries to parse comment prefix for the language implied by the given comment * type. It uses {@link #NO_COMMENT_INFO_MARKER} as an indicator that that information is * unavailable * * @param commentType target comment type * @return prefix of the comment denoted by the given type if any; {@link #NO_COMMENT_INFO_MARKER} * otherwise */ @NotNull private static String getCommentPrefix(@NotNull IElementType commentType) { Commenter c = LanguageCommenters.INSTANCE.forLanguage(commentType.getLanguage()); if (!(c instanceof CodeDocumentationAwareCommenter)) { COMMENT_PREFIXES.put(commentType, NO_COMMENT_INFO_MARKER); return NO_COMMENT_INFO_MARKER; } CodeDocumentationAwareCommenter commenter = (CodeDocumentationAwareCommenter) c; IElementType lineCommentType = commenter.getLineCommentTokenType(); String lineCommentPrefix = commenter.getLineCommentPrefix(); if (lineCommentType != null) { COMMENT_PREFIXES.put( lineCommentType, lineCommentPrefix == null ? NO_COMMENT_INFO_MARKER : lineCommentPrefix); } IElementType blockCommentType = commenter.getBlockCommentTokenType(); String blockCommentPrefix = commenter.getBlockCommentPrefix(); if (blockCommentType != null) { COMMENT_PREFIXES.put( blockCommentType, blockCommentPrefix == null ? NO_COMMENT_INFO_MARKER : blockCommentPrefix); } IElementType docCommentType = commenter.getDocumentationCommentTokenType(); String docCommentPrefix = commenter.getDocumentationCommentPrefix(); if (docCommentType != null) { COMMENT_PREFIXES.put( docCommentType, docCommentPrefix == null ? NO_COMMENT_INFO_MARKER : docCommentPrefix); } COMMENT_PREFIXES.putIfAbsent(commentType, NO_COMMENT_INFO_MARKER); return COMMENT_PREFIXES.get(commentType); } @NotNull private static RangeHighlighter createHighlighter(MarkupModel mm, TextRange range) { final RangeHighlighter highlighter = mm.addRangeHighlighter( range.getStartOffset(), range.getEndOffset(), 0, null, HighlighterTargetArea.EXACT_RANGE); highlighter.setCustomRenderer(RENDERER); return highlighter; } private static int compare(@NotNull TextRange r, @NotNull RangeHighlighter h) { int answer = r.getStartOffset() - h.getStartOffset(); return answer != 0 ? answer : r.getEndOffset() - h.getEndOffset(); } private class IndentsCalculator { @NotNull public final Map<Language, TokenSet> myComments = ContainerUtilRt.newHashMap(); /** * We need to treat specially commented lines. Consider a situation like below: * * <pre> * void test() { * if (true) { * int i; * // int j; * } * } * </pre> * * We don't want to show indent guide after 'int i;' line because un-commented line below ('int * j;') would have the same indent level. That's why we remember 'indents after un-comment' at * this collection. */ @NotNull public final TIntIntHashMap /* line -> indent column after un-comment */ indentAfterUncomment = new TIntIntHashMap(); @NotNull public final int[] lineIndents; @NotNull public final CharSequence myChars; IndentsCalculator() { assert myDocument != null; lineIndents = new int[myDocument.getLineCount()]; myChars = myDocument.getCharsSequence(); } /** Calculates line indents for the {@link #myDocument target document}. */ void calculate() { final FileType fileType = myFile.getFileType(); int prevLineIndent = -1; for (int line = 0; line < lineIndents.length; line++) { ProgressManager.checkCanceled(); int lineStart = myDocument.getLineStartOffset(line); int lineEnd = myDocument.getLineEndOffset(line); final int nonWhitespaceOffset = CharArrayUtil.shiftForward(myChars, lineStart, lineEnd, " \t"); if (nonWhitespaceOffset == lineEnd) { lineIndents[line] = -1; // Blank line marker } else { final int column = ((EditorImpl) myEditor).calcColumnNumber(nonWhitespaceOffset, line, true, myChars); if (prevLineIndent > 0 && prevLineIndent > column) { lineIndents[line] = calcIndent(line, nonWhitespaceOffset, lineEnd, column); } else { lineIndents[line] = column; } prevLineIndent = lineIndents[line]; } } int topIndent = 0; for (int line = 0; line < lineIndents.length; line++) { ProgressManager.checkCanceled(); if (lineIndents[line] >= 0) { topIndent = lineIndents[line]; } else { int startLine = line; while (line < lineIndents.length && lineIndents[line] < 0) { //noinspection AssignmentToForLoopParameter line++; } int bottomIndent = line < lineIndents.length ? lineIndents[line] : topIndent; int indent = Math.min(topIndent, bottomIndent); if (bottomIndent < topIndent) { int lineStart = myDocument.getLineStartOffset(line); int lineEnd = myDocument.getLineEndOffset(line); int nonWhitespaceOffset = CharArrayUtil.shiftForward(myChars, lineStart, lineEnd, " \t"); HighlighterIterator iterator = myEditor.getHighlighter().createIterator(nonWhitespaceOffset); if (BraceMatchingUtil.isRBraceToken(iterator, myChars, fileType)) { indent = topIndent; } } for (int blankLine = startLine; blankLine < line; blankLine++) { assert lineIndents[blankLine] == -1; lineIndents[blankLine] = Math.min(topIndent, indent); } //noinspection AssignmentToForLoopParameter line--; // will be incremented back at the end of the loop; } } } /** * Tries to calculate given line's indent column assuming that there might be a comment at the * given indent offset (see {@link #getCommentPrefix(IElementType)}). * * @param line target line * @param indentOffset start indent offset to use for the given line * @param lineEndOffset given line's end offset * @param fallbackColumn column to return if it's not possible to apply comment-specific indent * calculation rules * @return given line's indent column to use */ private int calcIndent(int line, int indentOffset, int lineEndOffset, int fallbackColumn) { final HighlighterIterator it = myEditor.getHighlighter().createIterator(indentOffset); IElementType tokenType = it.getTokenType(); Language language = tokenType.getLanguage(); TokenSet comments = myComments.get(language); if (comments == null) { ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(language); if (definition != null) { comments = definition.getCommentTokens(); } if (comments == null) { return fallbackColumn; } else { myComments.put(language, comments); } } if (comments.contains(tokenType) && indentOffset == it.getStart()) { String prefix = COMMENT_PREFIXES.get(tokenType); if (prefix == null) { prefix = getCommentPrefix(tokenType); } if (!NO_COMMENT_INFO_MARKER.equals(prefix)) { final int indentInsideCommentOffset = CharArrayUtil.shiftForward( myChars, indentOffset + prefix.length(), lineEndOffset, " \t"); if (indentInsideCommentOffset < lineEndOffset) { int indent = myEditor.calcColumnNumber(indentInsideCommentOffset, line); indentAfterUncomment.put(line, indent - prefix.length()); return indent; } } } return fallbackColumn; } } }
/** @author Eugene.Kudelevsky */ public class AndroidDependenciesCache { private static final Key<SoftReference<AndroidDependenciesCache>> KEY = Key.create("ANDROID_DEPENDENCIES_CACHE"); private final Module myModule; private final Ref<List<WeakReference<AndroidModuleExtension>>> myAllDependencies = Ref.create(); private final Ref<List<WeakReference<AndroidModuleExtension>>> myAllLibraryDependencies = Ref.create(); private AndroidDependenciesCache(@NotNull Module module) { myModule = module; module .getProject() .getMessageBus() .connect(module) .subscribe( ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { @Override public void rootsChanged(ModuleRootEvent event) { dropCache(); } }); } private synchronized void dropCache() { myAllDependencies.set(null); myAllLibraryDependencies.set(null); } @NotNull public static AndroidDependenciesCache getInstance(@NotNull Module module) { AndroidDependenciesCache cache = SoftReference.dereference(module.getUserData(KEY)); if (cache == null) { cache = new AndroidDependenciesCache(module); module.putUserData(KEY, new SoftReference<AndroidDependenciesCache>(cache)); } return cache; } @NotNull public synchronized List<AndroidModuleExtension> getAllAndroidDependencies( boolean androidLibrariesOnly) { return getAllAndroidDependencies( myModule, androidLibrariesOnly, getListRef(androidLibrariesOnly)); } @NotNull private Ref<List<WeakReference<AndroidModuleExtension>>> getListRef( boolean androidLibrariesOnly) { return androidLibrariesOnly ? myAllLibraryDependencies : myAllDependencies; } @NotNull private static List<AndroidModuleExtension> getAllAndroidDependencies( @NotNull Module module, boolean androidLibrariesOnly, Ref<List<WeakReference<AndroidModuleExtension>>> listRef) { List<WeakReference<AndroidModuleExtension>> refs = listRef.get(); if (refs == null) { final List<AndroidModuleExtension> facets = new ArrayList<AndroidModuleExtension>(); collectAllAndroidDependencies( module, androidLibrariesOnly, facets, new HashSet<AndroidModuleExtension>()); refs = ContainerUtil.map( ContainerUtil.reverse(facets), new Function<AndroidModuleExtension, WeakReference<AndroidModuleExtension>>() { @Override public WeakReference<AndroidModuleExtension> fun(AndroidModuleExtension facet) { return new WeakReference<AndroidModuleExtension>(facet); } }); listRef.set(refs); } return dereference(refs); } @NotNull private static List<AndroidModuleExtension> dereference( @NotNull List<WeakReference<AndroidModuleExtension>> refs) { return ContainerUtil.mapNotNull( refs, new Function<WeakReference<AndroidModuleExtension>, AndroidModuleExtension>() { @Override public AndroidModuleExtension fun(WeakReference<AndroidModuleExtension> ref) { return ref.get(); } }); } private static void collectAllAndroidDependencies( Module module, boolean androidLibrariesOnly, List<AndroidModuleExtension> result, Set<AndroidModuleExtension> visited) { final OrderEntry[] entries = ModuleRootManager.getInstance(module).getOrderEntries(); // loop in the inverse order to resolve dependencies on the libraries, so that if a library // is required by two higher level libraries it can be inserted in the correct place for (int i = entries.length - 1; i >= 0; i--) { final OrderEntry orderEntry = entries[i]; if (orderEntry instanceof ModuleOrderEntry) { final ModuleOrderEntry moduleOrderEntry = (ModuleOrderEntry) orderEntry; if (moduleOrderEntry.getScope() == DependencyScope.COMPILE) { final Module depModule = moduleOrderEntry.getModule(); if (depModule != null) { final AndroidModuleExtension depFacet = ModuleUtilCore.getExtension(depModule, AndroidModuleExtension.class); if (depFacet != null && (!androidLibrariesOnly || depFacet.isLibraryProject()) && visited.add(depFacet)) { final List<WeakReference<AndroidModuleExtension>> cachedDepDeps = getInstance(depModule).getListRef(androidLibrariesOnly).get(); if (cachedDepDeps != null) { final List<AndroidModuleExtension> depDeps = dereference(cachedDepDeps); for (AndroidModuleExtension depDepFacet : depDeps) { if (visited.add(depDepFacet)) { result.add(depDepFacet); } } } else { collectAllAndroidDependencies(depModule, androidLibrariesOnly, result, visited); } result.add(depFacet); } } } } } } }
/** @author dsl */ public class ElementToWorkOn { public static final Key<PsiElement> PARENT = Key.create("PARENT"); private final PsiExpression myExpression; private final PsiLocalVariable myLocalVariable; public static final Key<String> PREFIX = Key.create("prefix"); public static final Key<String> SUFFIX = Key.create("suffix"); public static final Key<RangeMarker> TEXT_RANGE = Key.create("range"); public static final Key<Boolean> OUT_OF_CODE_BLOCK = Key.create("out_of_code_block"); private ElementToWorkOn(PsiLocalVariable localVariable, PsiExpression expr) { myLocalVariable = localVariable; myExpression = expr; } public PsiExpression getExpression() { return myExpression; } public PsiLocalVariable getLocalVariable() { return myLocalVariable; } public boolean isInvokedOnDeclaration() { return myExpression == null; } public static void processElementToWorkOn( final Editor editor, final PsiFile file, final String refactoringName, final String helpId, final Project project, final Pass<ElementToWorkOn> processor) { PsiLocalVariable localVar = null; PsiExpression expr = null; if (!editor.getSelectionModel().hasSelection()) { PsiElement element = TargetElementUtilBase.findTargetElement( editor, TargetElementUtilBase.ELEMENT_NAME_ACCEPTED | TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED | TargetElementUtilBase.LOOKUP_ITEM_ACCEPTED); if (element instanceof PsiLocalVariable) { localVar = (PsiLocalVariable) element; final PsiElement elementAt = file.findElementAt(editor.getCaretModel().getOffset()); if (elementAt instanceof PsiIdentifier && elementAt.getParent() instanceof PsiReferenceExpression) { expr = (PsiExpression) elementAt.getParent(); } } else { final PsiLocalVariable variable = PsiTreeUtil.getParentOfType( file.findElementAt(editor.getCaretModel().getOffset()), PsiLocalVariable.class); final int offset = editor.getCaretModel().getOffset(); final PsiElement[] statementsInRange = IntroduceVariableBase.findStatementsAtOffset(editor, file, offset); if (statementsInRange.length == 1 && (PsiUtil.hasErrorElementChild(statementsInRange[0]) || !PsiUtil.isStatement(statementsInRange[0]))) { editor.getSelectionModel().selectLineAtCaret(); final ElementToWorkOn elementToWorkOn = getElementToWorkOn(editor, file, refactoringName, helpId, project, localVar, expr); if (elementToWorkOn == null || elementToWorkOn.getLocalVariable() == null && elementToWorkOn.getExpression() == null) { editor.getSelectionModel().removeSelection(); } } if (!editor.getSelectionModel().hasSelection()) { final List<PsiExpression> expressions = IntroduceVariableBase.collectExpressions(file, editor, offset, statementsInRange); if (expressions.isEmpty()) { editor.getSelectionModel().selectLineAtCaret(); } else if (expressions.size() == 1) { expr = expressions.get(0); } else { IntroduceTargetChooser.showChooser( editor, expressions, new Pass<PsiExpression>() { @Override public void pass(final PsiExpression selectedValue) { PsiLocalVariable var = null; // replace var if selected expression == var initializer if (variable != null && variable.getInitializer() == selectedValue) { var = variable; } processor.pass( getElementToWorkOn( editor, file, refactoringName, helpId, project, var, selectedValue)); } }, new PsiExpressionTrimRenderer.RenderFunction()); return; } } } } processor.pass( getElementToWorkOn(editor, file, refactoringName, helpId, project, localVar, expr)); } private static ElementToWorkOn getElementToWorkOn( final Editor editor, final PsiFile file, final String refactoringName, final String helpId, final Project project, PsiLocalVariable localVar, PsiExpression expr) { int startOffset = 0; int endOffset = 0; if (localVar == null && expr == null) { startOffset = editor.getSelectionModel().getSelectionStart(); endOffset = editor.getSelectionModel().getSelectionEnd(); expr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); if (expr == null) { PsiIdentifier ident = CodeInsightUtil.findElementInRange(file, startOffset, endOffset, PsiIdentifier.class); if (ident != null) { localVar = PsiTreeUtil.getParentOfType(ident, PsiLocalVariable.class); } } } if (expr == null && localVar == null) { PsiElement[] statements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); if (statements.length == 1 && statements[0] instanceof PsiExpressionStatement) { expr = ((PsiExpressionStatement) statements[0]).getExpression(); } else if (statements.length == 1 && statements[0] instanceof PsiDeclarationStatement) { PsiDeclarationStatement decl = (PsiDeclarationStatement) statements[0]; PsiElement[] declaredElements = decl.getDeclaredElements(); if (declaredElements.length == 1 && declaredElements[0] instanceof PsiLocalVariable) { localVar = (PsiLocalVariable) declaredElements[0]; } } } if (localVar == null && expr == null) { expr = IntroduceVariableBase.getSelectedExpression(project, file, startOffset, endOffset); } if (localVar == null && expr == null) { String message = RefactoringBundle.getCannotRefactorMessage( RefactoringBundle.message("error.wrong.caret.position.local.or.expression.name")); CommonRefactoringUtil.showErrorHint(project, editor, message, refactoringName, helpId); return null; } return new ElementToWorkOn(localVar, expr); } }
/** @author gregsh */ public class GeneratedParserUtilBase { private static final Logger LOG = Logger.getInstance("org.intellij.grammar.parser.GeneratedParserUtilBase"); private static final int MAX_RECURSION_LEVEL = 1000; private static final int MAX_VARIANTS_SIZE = 10000; private static final int MAX_VARIANTS_TO_DISPLAY = 50; private static final int INITIAL_VARIANTS_SIZE = 1000; private static final int VARIANTS_POOL_SIZE = 10000; private static final int FRAMES_POOL_SIZE = 500; public static final IElementType DUMMY_BLOCK = new DummyBlockElementType(); public interface Parser { boolean parse(PsiBuilder builder, int level); } public static final Parser TOKEN_ADVANCER = new Parser() { @Override public boolean parse(PsiBuilder builder, int level) { if (builder.eof()) return false; builder.advanceLexer(); return true; } }; public static final Parser TRUE_CONDITION = new Parser() { @Override public boolean parse(PsiBuilder builder, int level) { return true; } }; public static boolean eof(PsiBuilder builder, int level) { return builder.eof(); } public static int current_position_(PsiBuilder builder) { return builder.rawTokenIndex(); } public static boolean recursion_guard_(PsiBuilder builder, int level, String funcName) { if (level > MAX_RECURSION_LEVEL) { builder.error( "Maximum recursion level (" + MAX_RECURSION_LEVEL + ") reached in '" + funcName + "'"); return false; } return true; } public static boolean empty_element_parsed_guard_(PsiBuilder builder, String funcName, int pos) { if (pos == current_position_(builder)) { builder.error( "Empty element parsed in '" + funcName + "' at offset " + builder.getCurrentOffset()); return false; } return true; } public static boolean invalid_left_marker_guard_( PsiBuilder builder, PsiBuilder.Marker marker, String funcName) { // builder.error("Invalid left marker encountered in " + funcName_ +" at offset " + // builder.getCurrentOffset()); boolean goodMarker = marker != null; // && ((LighterASTNode)marker).getTokenType() != TokenType.ERROR_ELEMENT; if (!goodMarker) return false; ErrorState state = ErrorState.get(builder); return !state.frameStack.isEmpty(); } public static TokenSet create_token_set_(IElementType... tokenTypes) { return TokenSet.create(tokenTypes); } public static boolean leftMarkerIs(PsiBuilder builder, IElementType type) { LighterASTNode marker = builder.getLatestDoneMarker(); return marker != null && marker.getTokenType() == type; } private static boolean consumeTokens( PsiBuilder builder, boolean smart, int pin, IElementType... tokens) { ErrorState state = ErrorState.get(builder); if (state.completionState != null && state.predicateSign) { addCompletionVariant(builder, state.completionState, tokens); } // suppress single token completion CompletionState completionState = state.completionState; state.completionState = null; boolean result = true; boolean pinned = false; for (int i = 0, tokensLength = tokens.length; i < tokensLength; i++) { if (pin > 0 && i == pin) pinned = result; if (result || pinned) { boolean fast = smart && i == 0; if (!(fast ? consumeTokenFast(builder, tokens[i]) : consumeToken(builder, tokens[i]))) { result = false; if (pin < 0 || pinned) report_error_(builder, state, false); } } } state.completionState = completionState; return pinned || result; } public static boolean consumeTokens(PsiBuilder builder, int pin, IElementType... token) { return consumeTokens(builder, false, pin, token); } public static boolean consumeTokensSmart(PsiBuilder builder, int pin, IElementType... token) { return consumeTokens(builder, true, pin, token); } public static boolean parseTokens(PsiBuilder builder, int pin, IElementType... tokens) { return parseTokens(builder, false, pin, tokens); } public static boolean parseTokensSmart(PsiBuilder builder, int pin, IElementType... tokens) { return parseTokens(builder, true, pin, tokens); } public static boolean parseTokens( PsiBuilder builder, boolean smart, int pin, IElementType... tokens) { PsiBuilder.Marker marker = builder.mark(); boolean result = consumeTokens(builder, smart, pin, tokens); if (!result) { marker.rollbackTo(); } else { marker.drop(); } return result; } public static boolean consumeTokenSmart(PsiBuilder builder, IElementType token) { addCompletionVariantSmart(builder, token); return consumeTokenFast(builder, token); } public static boolean consumeTokenSmart(PsiBuilder builder, String token) { addCompletionVariantSmart(builder, token); return consumeTokenFast(builder, token); } public static boolean consumeToken(PsiBuilder builder, IElementType token) { addVariantSmart(builder, token, true); if (nextTokenIsFast(builder, token)) { builder.advanceLexer(); return true; } return false; } public static boolean consumeTokenFast(PsiBuilder builder, IElementType token) { if (nextTokenIsFast(builder, token)) { builder.advanceLexer(); return true; } return false; } public static boolean consumeToken(PsiBuilder builder, String text) { return consumeToken(builder, text, ErrorState.get(builder).caseSensitive); } public static boolean consumeToken(PsiBuilder builder, String text, boolean caseSensitive) { addVariantSmart(builder, text, true); int count = nextTokenIsFast(builder, text, caseSensitive); if (count > 0) { while (count-- > 0) builder.advanceLexer(); return true; } return false; } public static boolean consumeTokenFast(PsiBuilder builder, String text) { int count = nextTokenIsFast(builder, text, ErrorState.get(builder).caseSensitive); if (count > 0) { while (count-- > 0) builder.advanceLexer(); return true; } return false; } public static boolean nextTokenIsFast(PsiBuilder builder, IElementType token) { return builder.getTokenType() == token; } public static boolean nextTokenIsFast(PsiBuilder builder, IElementType... tokens) { IElementType tokenType = builder.getTokenType(); for (IElementType token : tokens) { if (token == tokenType) return true; } return false; } public static boolean nextTokenIs(PsiBuilder builder, String frameName, IElementType... tokens) { ErrorState state = ErrorState.get(builder); if (state.completionState != null) return true; boolean track = !state.suppressErrors && state.predicateCount < 2 && state.predicateSign; if (!track) return nextTokenIsFast(builder, tokens); IElementType tokenType = builder.getTokenType(); if (StringUtil.isNotEmpty(frameName)) { addVariantInner(state, builder.rawTokenIndex(), frameName); } else { for (IElementType token : tokens) { addVariant(builder, state, token); } } if (tokenType == null) return false; for (IElementType token : tokens) { if (tokenType == token) return true; } return false; } public static boolean nextTokenIs(PsiBuilder builder, IElementType token) { if (!addVariantSmart(builder, token, false)) return true; return nextTokenIsFast(builder, token); } public static boolean nextTokenIs(PsiBuilder builder, String tokenText) { if (!addVariantSmart(builder, tokenText, false)) return true; return nextTokenIsFast(builder, tokenText, ErrorState.get(builder).caseSensitive) > 0; } public static boolean nextTokenIsFast(PsiBuilder builder, String tokenText) { return nextTokenIsFast(builder, tokenText, ErrorState.get(builder).caseSensitive) > 0; } public static int nextTokenIsFast(PsiBuilder builder, String tokenText, boolean caseSensitive) { CharSequence sequence = builder.getOriginalText(); int offset = builder.getCurrentOffset(); int endOffset = offset + tokenText.length(); CharSequence subSequence = sequence.subSequence(offset, Math.min(endOffset, sequence.length())); if (!Comparing.equal(subSequence, tokenText, caseSensitive)) return 0; int count = 0; while (true) { int nextOffset = builder.rawTokenTypeStart(++count); if (nextOffset > endOffset) { return -count; } else if (nextOffset == endOffset) { break; } } return count; } private static void addCompletionVariantSmart(PsiBuilder builder, Object token) { ErrorState state = ErrorState.get(builder); CompletionState completionState = state.completionState; if (completionState != null && state.predicateSign) { addCompletionVariant(builder, completionState, token); } } private static boolean addVariantSmart(PsiBuilder builder, Object token, boolean force) { ErrorState state = ErrorState.get(builder); // skip FIRST check in completion mode if (state.completionState != null && !force) return false; builder.eof(); if (!state.suppressErrors && state.predicateCount < 2) { addVariant(builder, state, token); } return true; } public static void addVariant(PsiBuilder builder, String text) { addVariant(builder, ErrorState.get(builder), text); } private static void addVariant(PsiBuilder builder, ErrorState state, Object o) { builder.eof(); // skip whitespaces addVariantInner(state, builder.rawTokenIndex(), o); CompletionState completionState = state.completionState; if (completionState != null && state.predicateSign) { addCompletionVariant(builder, completionState, o); } } private static void addVariantInner(ErrorState state, int pos, Object o) { Variant variant = state.VARIANTS.alloc().init(pos, o); if (state.predicateSign) { state.variants.add(variant); if (state.lastExpectedVariantPos < variant.position) { state.lastExpectedVariantPos = variant.position; } } else { state.unexpected.add(variant); } } private static void addCompletionVariant( @NotNull PsiBuilder builder, @NotNull CompletionState completionState, Object o) { int offset = builder.getCurrentOffset(); if (!builder.eof() && offset == builder.rawTokenTypeStart(1)) return; // suppress for zero-length tokens boolean add = false; int diff = completionState.offset - offset; String text = completionState.convertItem(o); int length = text == null ? 0 : text.length(); if (length == 0) return; if (diff == 0) { add = true; } else if (diff > 0 && diff <= length) { CharSequence fragment = builder.getOriginalText().subSequence(offset, completionState.offset); add = completionState.prefixMatches(fragment.toString(), text); } else if (diff < 0) { for (int i = -1; ; i--) { IElementType type = builder.rawLookup(i); int tokenStart = builder.rawTokenTypeStart(i); if (isWhitespaceOrComment(builder, type)) { diff = completionState.offset - tokenStart; } else if (type != null && tokenStart < completionState.offset) { CharSequence fragment = builder.getOriginalText().subSequence(tokenStart, completionState.offset); if (completionState.prefixMatches(fragment.toString(), text)) { diff = completionState.offset - tokenStart; } break; } else break; } add = diff >= 0 && diff < length; } add = add && length > 1 && !(text.charAt(0) == '<' && text.charAt(length - 1) == '>') && !(text.charAt(0) == '\'' && text.charAt(length - 1) == '\'' && length < 5); if (add) { completionState.addItem(builder, text); } } public static boolean isWhitespaceOrComment( @NotNull PsiBuilder builder, @Nullable IElementType type) { return ((PsiBuilderImpl) ((Builder) builder).getDelegate()).whitespaceOrComment(type); } // here's the new section API for compact parsers & less IntelliJ platform API exposure public static final int _NONE_ = 0x0; public static final int _COLLAPSE_ = 0x1; public static final int _LEFT_ = 0x2; public static final int _LEFT_INNER_ = 0x4; public static final int _AND_ = 0x8; public static final int _NOT_ = 0x10; // simple enter/exit methods pair that doesn't require frame object public static PsiBuilder.Marker enter_section_(PsiBuilder builder) { return builder.mark(); } public static void exit_section_( PsiBuilder builder, PsiBuilder.Marker marker, @Nullable IElementType elementType, boolean result) { close_marker_impl_(ErrorState.get(builder).frameStack.peekLast(), marker, elementType, result); } // complex enter/exit methods pair with frame object public static PsiBuilder.Marker enter_section_( PsiBuilder builder, int level, int modifiers, @Nullable String frameName) { PsiBuilder.Marker marker = builder.mark(); enter_section_impl_(builder, level, modifiers, frameName); return marker; } private static void enter_section_impl_( PsiBuilder builder, int level, int modifiers, @Nullable String frameName) { ErrorState state = ErrorState.get(builder); Frame frame = state.FRAMES.alloc().init(builder, state, level, modifiers, frameName); Frame prevFrame = state.frameStack.peekLast(); if (prevFrame != null && prevFrame.errorReportedAt > frame.position) { // report error for previous unsuccessful frame reportError(builder, state, frame, null, true, false); } if (((frame.modifiers & _LEFT_) | (frame.modifiers & _LEFT_INNER_)) != 0) { PsiBuilder.Marker left = (PsiBuilder.Marker) builder.getLatestDoneMarker(); if (invalid_left_marker_guard_(builder, left, frameName)) { frame.leftMarker = left; } } state.frameStack.add(frame); if ((modifiers & _AND_) != 0) { if (state.predicateCount == 0 && !state.predicateSign) { throw new AssertionError("Incorrect false predicate sign"); } state.predicateCount++; } else if ((modifiers & _NOT_) != 0) { if (state.predicateCount == 0) { state.predicateSign = false; } else { state.predicateSign = !state.predicateSign; } state.predicateCount++; } } public static void exit_section_( PsiBuilder builder, int level, PsiBuilder.Marker marker, @Nullable IElementType elementType, boolean result, boolean pinned, @Nullable Parser eatMore) { ErrorState state = ErrorState.get(builder); Frame frame = state.frameStack.pollLast(); if (frame == null || level != frame.level) { LOG.error("Unbalanced error section: got " + frame + ", expected level " + level); if (frame != null) state.FRAMES.recycle(frame); close_marker_impl_(frame, marker, elementType, result); return; } if (((frame.modifiers & _AND_) | (frame.modifiers & _NOT_)) != 0) { close_marker_impl_(frame, marker, null, false); state.predicateCount--; if ((frame.modifiers & _NOT_) != 0) state.predicateSign = !state.predicateSign; state.FRAMES.recycle(frame); return; } close_frame_impl_(state, frame, builder, marker, elementType, result, pinned); exit_section_impl_(state, frame, builder, elementType, result, pinned, eatMore); state.FRAMES.recycle(frame); } private static void exit_section_impl_( ErrorState state, Frame frame, PsiBuilder builder, @Nullable IElementType elementType, boolean result, boolean pinned, @Nullable Parser eatMore) { int initialPos = builder.rawTokenIndex(); boolean willFail = !result && !pinned; if (willFail && initialPos == frame.position && state.lastExpectedVariantPos == frame.position && frame.name != null && state.variants.size() - frame.variantCount > 1) { state.clearVariants(true, frame.variantCount); addVariantInner(state, initialPos, frame.name); } int lastErrorPos = getLastVariantPos(state, initialPos); if (!state.suppressErrors && eatMore != null) { state.suppressErrors = true; final boolean eatMoreFlagOnce = !builder.eof() && eatMore.parse(builder, frame.level + 1); boolean eatMoreFlag = eatMoreFlagOnce || !result && frame.position == initialPos && lastErrorPos > frame.position; PsiBuilderImpl.ProductionMarker latestDoneMarker = (pinned || result) && (state.altMode || elementType != null) && eatMoreFlagOnce ? (PsiBuilderImpl.ProductionMarker) builder.getLatestDoneMarker() : null; PsiBuilder.Marker extensionMarker = null; IElementType extensionTokenType = null; // whitespace prefix makes the very first frame offset bigger than marker start offset which // is always 0 if (latestDoneMarker != null && frame.position >= latestDoneMarker.getStartIndex() && frame.position <= latestDoneMarker.getEndIndex()) { extensionMarker = ((PsiBuilder.Marker) latestDoneMarker).precede(); extensionTokenType = latestDoneMarker.getTokenType(); ((PsiBuilder.Marker) latestDoneMarker).drop(); } // advance to the last error pos // skip tokens until lastErrorPos. parseAsTree might look better here... int parenCount = 0; while ((eatMoreFlag || parenCount > 0) && builder.rawTokenIndex() < lastErrorPos) { if (state.braces != null) { if (builder.getTokenType() == state.braces[0].getLeftBraceType()) parenCount++; else if (builder.getTokenType() == state.braces[0].getRightBraceType()) parenCount--; } builder.advanceLexer(); eatMoreFlag = eatMore.parse(builder, frame.level + 1); } boolean errorReported = frame.errorReportedAt == initialPos || !result && frame.errorReportedAt >= frame.position; if (errorReported) { if (eatMoreFlag) { builder.advanceLexer(); parseAsTree(state, builder, frame.level + 1, DUMMY_BLOCK, true, TOKEN_ADVANCER, eatMore); } } else if (eatMoreFlag) { errorReported = reportError(builder, state, frame, null, true, true); parseAsTree(state, builder, frame.level + 1, DUMMY_BLOCK, true, TOKEN_ADVANCER, eatMore); } else if (eatMoreFlagOnce || (!result && frame.position != builder.rawTokenIndex()) || frame.errorReportedAt > initialPos) { errorReported = reportError(builder, state, frame, null, true, false); } if (extensionMarker != null) { extensionMarker.done(extensionTokenType); } state.suppressErrors = false; if (errorReported || result) { state.clearVariants(true, 0); state.clearVariants(false, 0); state.lastExpectedVariantPos = -1; } } else if (!result && pinned && frame.errorReportedAt < 0) { // do not report if there are errors beyond current position if (lastErrorPos == initialPos) { // do not force, inner recoverRoot might have skipped some tokens reportError(builder, state, frame, elementType, false, false); } else if (lastErrorPos > initialPos) { // set error pos here as if it is reported for future reference frame.errorReportedAt = lastErrorPos; } } // propagate errorReportedAt up the stack to avoid duplicate reporting Frame prevFrame = willFail && eatMore == null ? null : state.frameStack.peekLast(); if (prevFrame != null && prevFrame.errorReportedAt < frame.errorReportedAt) { prevFrame.errorReportedAt = frame.errorReportedAt; } } private static void close_frame_impl_( ErrorState state, Frame frame, PsiBuilder builder, PsiBuilder.Marker marker, IElementType elementType, boolean result, boolean pinned) { if (elementType != null && marker != null) { if ((frame.modifiers & _COLLAPSE_) != 0) { PsiBuilderImpl.ProductionMarker last = result || pinned ? (PsiBuilderImpl.ProductionMarker) builder.getLatestDoneMarker() : null; if (last != null && last.getStartIndex() == frame.position && state.typeExtends(last.getTokenType(), elementType)) { IElementType resultType = last.getTokenType(); ((PsiBuilder.Marker) last).drop(); marker.done(resultType); return; } } if (result || pinned) { if ((frame.modifiers & _LEFT_INNER_) != 0 && frame.leftMarker != null) { marker.done(elementType); frame.leftMarker.precede().done(((LighterASTNode) frame.leftMarker).getTokenType()); frame.leftMarker.drop(); } else if ((frame.modifiers & _LEFT_) != 0 && frame.leftMarker != null) { marker.drop(); frame.leftMarker.precede().done(elementType); } else { if (frame.level == 0) builder.eof(); // skip whitespaces marker.done(elementType); } } else { close_marker_impl_(frame, marker, null, false); } } else if (result || pinned) { if (marker != null) marker.drop(); if ((frame.modifiers & _LEFT_INNER_) != 0 && frame.leftMarker != null) { frame.leftMarker.precede().done(((LighterASTNode) frame.leftMarker).getTokenType()); frame.leftMarker.drop(); } } else { close_marker_impl_(frame, marker, null, false); } } private static void close_marker_impl_( Frame frame, PsiBuilder.Marker marker, IElementType elementType, boolean result) { if (marker == null) return; if (result) { if (elementType != null) { marker.done(elementType); } else { marker.drop(); } } else { if (frame != null) { int position = ((PsiBuilderImpl.ProductionMarker) marker).getStartIndex(); if (frame.errorReportedAt > position) { frame.errorReportedAt = frame.errorReportedAtPrev; } } marker.rollbackTo(); } } public static boolean report_error_(PsiBuilder builder, boolean result) { if (!result) report_error_(builder, ErrorState.get(builder), false); return result; } public static void report_error_(PsiBuilder builder, ErrorState state, boolean advance) { Frame frame = state.frameStack.isEmpty() ? null : state.frameStack.getLast(); if (frame == null) { LOG.error("unbalanced enter/exit section call: got null"); return; } int position = builder.rawTokenIndex(); if (frame.errorReportedAt < position && getLastVariantPos(state, position + 1) <= position) { reportError(builder, state, frame, null, true, advance); } } private static int getLastVariantPos(ErrorState state, int defValue) { return state.lastExpectedVariantPos < 0 ? defValue : state.lastExpectedVariantPos; } private static boolean reportError( PsiBuilder builder, ErrorState state, Frame frame, IElementType elementType, boolean force, boolean advance) { String expectedText = state.getExpectedText(builder); boolean notEmpty = StringUtil.isNotEmpty(expectedText); if (force || notEmpty || advance) { String gotText = builder.eof() ? "unexpected end of file" : notEmpty ? "got '" + builder.getTokenText() + "'" : "'" + builder.getTokenText() + "' unexpected"; String message = expectedText + gotText; if (advance) { PsiBuilder.Marker mark = builder.mark(); builder.advanceLexer(); mark.error(message); } else if (!force) { PsiBuilder.Marker extensionMarker = null; IElementType extensionTokenType = null; PsiBuilderImpl.ProductionMarker latestDoneMarker = elementType == null ? null : (PsiBuilderImpl.ProductionMarker) builder.getLatestDoneMarker(); if (latestDoneMarker != null && frame.position >= latestDoneMarker.getStartIndex() && frame.position <= latestDoneMarker.getEndIndex()) { extensionMarker = ((PsiBuilder.Marker) latestDoneMarker).precede(); extensionTokenType = latestDoneMarker.getTokenType(); ((PsiBuilder.Marker) latestDoneMarker).drop(); } builder.error(message); if (extensionMarker != null) extensionMarker.done(extensionTokenType); } else { builder.error(message); } builder.eof(); // skip whitespaces frame.errorReportedAt = builder.rawTokenIndex(); return true; } return false; } public static final Key<CompletionState> COMPLETION_STATE_KEY = Key.create("COMPLETION_STATE_KEY"); public static class CompletionState implements Function<Object, String> { public final int offset; public final Collection<String> items = ContainerUtil.newTroveSet(); public CompletionState(int offset_) { offset = offset_; } @Nullable public String convertItem(Object o) { return o instanceof Object[] ? StringUtil.join((Object[]) o, this, " ") : o.toString(); } @Override public String fun(Object o) { return o.toString(); } public void addItem(@NotNull PsiBuilder builder, @NotNull String text) { items.add(text); } public boolean prefixMatches(@NotNull String prefix, @NotNull String variant) { boolean matches = new CamelHumpMatcher(prefix, false).prefixMatches(variant.replace(' ', '_')); if (matches && StringUtil.isWhiteSpace(prefix.charAt(prefix.length() - 1))) { return StringUtil.startsWithIgnoreCase(variant, prefix); } return matches; } } public static class Builder extends PsiBuilderAdapter { public final ErrorState state; public final PsiParser parser; public Builder(PsiBuilder builder, ErrorState state_, PsiParser parser_) { super(builder); state = state_; parser = parser_; } public Lexer getLexer() { return ((PsiBuilderImpl) myDelegate).getLexer(); } } public static PsiBuilder adapt_builder_(IElementType root, PsiBuilder builder, PsiParser parser) { return adapt_builder_(root, builder, parser, null); } public static PsiBuilder adapt_builder_( IElementType root, PsiBuilder builder, PsiParser parser, TokenSet[] extendsSets) { ErrorState state = new ErrorState(); ErrorState.initState(state, builder, root, extendsSets); return new Builder(builder, state, parser); } public static class ErrorState { TokenSet[] extendsSets; public PairProcessor<IElementType, IElementType> altExtendsChecker; int predicateCount; boolean predicateSign = true; boolean suppressErrors; public final LinkedList<Frame> frameStack = new LinkedList<Frame>(); public CompletionState completionState; private boolean caseSensitive; public BracePair[] braces; public boolean altMode; int lastExpectedVariantPos = -1; MyList<Variant> variants = new MyList<Variant>(INITIAL_VARIANTS_SIZE); MyList<Variant> unexpected = new MyList<Variant>(INITIAL_VARIANTS_SIZE / 10); final LimitedPool<Variant> VARIANTS = new LimitedPool<Variant>( VARIANTS_POOL_SIZE, new LimitedPool.ObjectFactory<Variant>() { @NotNull @Override public Variant create() { return new Variant(); } @Override public void cleanup(@NotNull final Variant o) {} }); final LimitedPool<Frame> FRAMES = new LimitedPool<Frame>( FRAMES_POOL_SIZE, new LimitedPool.ObjectFactory<Frame>() { @NotNull @Override public Frame create() { return new Frame(); } @Override public void cleanup(@NotNull final Frame o) {} }); public static ErrorState get(PsiBuilder builder) { return ((Builder) builder).state; } public static void initState( ErrorState state, PsiBuilder builder, IElementType root, TokenSet[] extendsSets) { state.extendsSets = extendsSets; PsiFile file = builder.getUserDataUnprotected(FileContextUtil.CONTAINING_FILE_KEY); state.completionState = file == null ? null : file.getUserData(COMPLETION_STATE_KEY); Language language = file == null ? root.getLanguage() : file.getLanguage(); state.caseSensitive = language.isCaseSensitive(); PairedBraceMatcher matcher = LanguageBraceMatching.INSTANCE.forLanguage(language); state.braces = matcher == null ? null : matcher.getPairs(); if (state.braces != null && state.braces.length == 0) state.braces = null; } public String getExpectedText(PsiBuilder builder) { int position = builder.rawTokenIndex(); StringBuilder sb = new StringBuilder(); if (addExpected(sb, position, true)) { sb.append(" expected, "); } return sb.toString(); } private boolean addExpected(StringBuilder sb, int position, boolean expected) { MyList<Variant> list = expected ? variants : unexpected; String[] strings = new String[list.size()]; long[] hashes = new long[strings.length]; Arrays.fill(strings, ""); int count = 0; loop: for (Variant variant : list) { if (position == variant.position) { String text = variant.object.toString(); long hash = StringHash.calc(text); for (int i = 0; i < count; i++) { if (hashes[i] == hash) continue loop; } hashes[count] = hash; strings[count] = text; count++; } } Arrays.sort(strings); count = 0; for (String s : strings) { if (s.length() == 0) continue; if (count++ > 0) { if (count > MAX_VARIANTS_TO_DISPLAY) { sb.append(" and ..."); break; } else { sb.append(", "); } } char c = s.charAt(0); String displayText = c == '<' || StringUtil.isJavaIdentifierStart(c) ? s : '\'' + s + '\''; sb.append(displayText); } if (count > 1 && count < MAX_VARIANTS_TO_DISPLAY) { int idx = sb.lastIndexOf(", "); sb.replace(idx, idx + 1, " or"); } return count > 0; } public int getVariantsSize() { return variants.size(); } public void clearVariants(boolean expected, int start) { MyList<Variant> list = expected ? variants : unexpected; if (start < 0 || start >= list.size()) return; for (int i = start, len = list.size(); i < len; i++) { VARIANTS.recycle(list.get(i)); } list.setSize(start); if (expected) { lastExpectedVariantPos = -1; for (Variant variant : list) { if (lastExpectedVariantPos < variant.position) lastExpectedVariantPos = variant.position; } } } boolean typeExtends(IElementType child, IElementType parent) { if (child == parent) return true; if (extendsSets != null) { for (TokenSet set : extendsSets) { if (set.contains(child) && set.contains(parent)) return true; } } return altExtendsChecker != null && altExtendsChecker.process(child, parent); } } public static class Frame { public int offset; public int position; public int level; public int modifiers; public String name; public int variantCount; public int errorReportedAt; public int errorReportedAtPrev; public PsiBuilder.Marker leftMarker; public Frame() {} public Frame init( PsiBuilder builder, ErrorState state, int level_, int modifiers_, String name_) { offset = builder.getCurrentOffset(); position = builder.rawTokenIndex(); level = level_; modifiers = modifiers_; name = name_; variantCount = state.variants.size(); errorReportedAt = -1; Frame prev = state.frameStack.peekLast(); errorReportedAtPrev = prev == null ? -1 : prev.errorReportedAt; leftMarker = null; return this; } @Override public String toString() { String mod = modifiers == _NONE_ ? "_NONE_, " : ((modifiers & _COLLAPSE_) != 0 ? "_CAN_COLLAPSE_, " : "") + ((modifiers & _LEFT_) != 0 ? "_LEFT_, " : "") + ((modifiers & _LEFT_INNER_) != 0 ? "_LEFT_INNER_, " : "") + ((modifiers & _AND_) != 0 ? "_AND_, " : "") + ((modifiers & _NOT_) != 0 ? "_NOT_, " : ""); return String.format( "{%s:%s:%d, %d, %s%s}", offset, position, level, errorReportedAt, mod, name); } } private static class Variant { int position; Object object; public Variant init(int pos, Object o) { position = pos; object = o; return this; } @Override public String toString() { return "<" + position + ", " + object + ">"; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Variant variant = (Variant) o; if (position != variant.position) return false; if (!this.object.equals(variant.object)) return false; return true; } @Override public int hashCode() { int result = position; result = 31 * result + object.hashCode(); return result; } } private static final int MAX_CHILDREN_IN_TREE = 10; public static boolean parseAsTree( ErrorState state, final PsiBuilder builder, int level, final IElementType chunkType, boolean checkBraces, final Parser parser, final Parser eatMoreCondition) { final LinkedList<Pair<PsiBuilder.Marker, PsiBuilder.Marker>> parenList = new LinkedList<Pair<PsiBuilder.Marker, PsiBuilder.Marker>>(); final LinkedList<Pair<PsiBuilder.Marker, Integer>> siblingList = new LinkedList<Pair<PsiBuilder.Marker, Integer>>(); PsiBuilder.Marker marker = null; final Runnable checkSiblingsRunnable = new Runnable() { @Override public void run() { main: while (!siblingList.isEmpty()) { final Pair<PsiBuilder.Marker, PsiBuilder.Marker> parenPair = parenList.peek(); final int rating = siblingList.getFirst().second; int count = 0; for (Pair<PsiBuilder.Marker, Integer> pair : siblingList) { if (pair.second != rating || parenPair != null && pair.first == parenPair.second) break main; if (++count >= MAX_CHILDREN_IN_TREE) { PsiBuilder.Marker parentMarker = pair.first.precede(); parentMarker.setCustomEdgeTokenBinders( WhitespacesBinders.GREEDY_LEFT_BINDER, null); while (count-- > 0) { siblingList.removeFirst(); } parentMarker.done(chunkType); siblingList.addFirst(Pair.create(parentMarker, rating + 1)); continue main; } } break; } } }; boolean checkParens = state.braces != null && checkBraces; int totalCount = 0; int tokenCount = 0; if (checkParens) { int tokenIdx = -1; while (builder.rawLookup(tokenIdx) == TokenType.WHITE_SPACE) tokenIdx--; LighterASTNode doneMarker = builder.rawLookup(tokenIdx) == state.braces[0].getLeftBraceType() ? builder.getLatestDoneMarker() : null; if (doneMarker != null && doneMarker.getStartOffset() == builder.rawTokenTypeStart(tokenIdx) && doneMarker.getTokenType() == TokenType.ERROR_ELEMENT) { parenList.add( Pair.create(((PsiBuilder.Marker) doneMarker).precede(), (PsiBuilder.Marker) null)); } } while (true) { final IElementType tokenType = builder.getTokenType(); if (checkParens && (tokenType == state.braces[0].getLeftBraceType() || tokenType == state.braces[0].getRightBraceType() && !parenList.isEmpty())) { if (marker != null) { marker.done(chunkType); siblingList.addFirst(Pair.create(marker, 1)); marker = null; tokenCount = 0; } if (tokenType == state.braces[0].getLeftBraceType()) { final Pair<PsiBuilder.Marker, Integer> prev = siblingList.peek(); parenList.addFirst(Pair.create(builder.mark(), prev == null ? null : prev.first)); } checkSiblingsRunnable.run(); builder.advanceLexer(); if (tokenType == state.braces[0].getRightBraceType()) { final Pair<PsiBuilder.Marker, PsiBuilder.Marker> pair = parenList.removeFirst(); pair.first.done(chunkType); // drop all markers inside parens while (!siblingList.isEmpty() && siblingList.getFirst().first != pair.second) { siblingList.removeFirst(); } siblingList.addFirst(Pair.create(pair.first, 1)); checkSiblingsRunnable.run(); } } else { if (marker == null) { marker = builder.mark(); marker.setCustomEdgeTokenBinders(WhitespacesBinders.GREEDY_LEFT_BINDER, null); } final boolean result = (!parenList.isEmpty() || eatMoreCondition.parse(builder, level + 1)) && parser.parse(builder, level + 1); if (result) { tokenCount++; totalCount++; } if (!result) { break; } } if (tokenCount >= MAX_CHILDREN_IN_TREE && marker != null) { marker.done(chunkType); siblingList.addFirst(Pair.create(marker, 1)); checkSiblingsRunnable.run(); marker = null; tokenCount = 0; } } if (marker != null) { marker.drop(); } for (Pair<PsiBuilder.Marker, PsiBuilder.Marker> pair : parenList) { pair.first.drop(); } return totalCount != 0; } private static class DummyBlockElementType extends IElementType implements ICompositeElementType { DummyBlockElementType() { super("DUMMY_BLOCK", Language.ANY); } @NotNull @Override public ASTNode createCompositeNode() { return new DummyBlock(); } } public static class DummyBlock extends CompositePsiElement { DummyBlock() { super(DUMMY_BLOCK); } @NotNull @Override public PsiReference[] getReferences() { return PsiReference.EMPTY_ARRAY; } @NotNull @Override public Language getLanguage() { return getParent().getLanguage(); } } private static class MyList<E> extends ArrayList<E> { MyList(int initialCapacity) { super(initialCapacity); } protected void setSize(int fromIndex) { removeRange(fromIndex, size()); } @Override public boolean add(E e) { int size = size(); if (size >= MAX_VARIANTS_SIZE) { removeRange(MAX_VARIANTS_SIZE / 4, size - MAX_VARIANTS_SIZE / 4); } return super.add(e); } } }