private Pair<MutableTextRange, StringBuffer> getFragmentByRange(int start, final int length) { final StringBuffer fragmentBuffer = new StringBuffer(); int end = start + length; // restoring buffer and remove all subfragments from the list int documentOffset = 0; int effectiveOffset = 0; Iterator<Pair<MutableTextRange, StringBuffer>> iterator = myAffectedFragments.iterator(); while (iterator.hasNext() && effectiveOffset <= end) { final Pair<MutableTextRange, StringBuffer> pair = iterator.next(); final MutableTextRange range = pair.getFirst(); final StringBuffer buffer = pair.getSecond(); int effectiveFragmentEnd = range.getStartOffset() + buffer.length(); if (range.getStartOffset() <= start && effectiveFragmentEnd >= end) return pair; if (effectiveFragmentEnd >= start) { final int effectiveStart = Math.max(effectiveOffset, start); if (range.getStartOffset() > start) { fragmentBuffer.append( myDocument.getCharsSequence(), effectiveStart - effectiveOffset + documentOffset, Math.min(range.getStartOffset(), end) - effectiveOffset + documentOffset); } if (end >= range.getStartOffset()) { fragmentBuffer.append(buffer); end = end > effectiveFragmentEnd ? end - (buffer.length() - range.getLength()) : range.getEndOffset(); effectiveFragmentEnd = range.getEndOffset(); start = Math.min(start, range.getStartOffset()); iterator.remove(); } } documentOffset += range.getEndOffset() - effectiveOffset; effectiveOffset = effectiveFragmentEnd; } if (effectiveOffset < end) { final int effectiveStart = Math.max(effectiveOffset, start); fragmentBuffer.append( myDocument.getCharsSequence(), effectiveStart - effectiveOffset + documentOffset, end - effectiveOffset + documentOffset); } MutableTextRange newRange = new MutableTextRange(start, end); final Pair<MutableTextRange, StringBuffer> pair = new Pair<MutableTextRange, StringBuffer>(newRange, fragmentBuffer); for (Pair<MutableTextRange, StringBuffer> affectedFragment : myAffectedFragments) { MutableTextRange range = affectedFragment.getFirst(); assert end <= range.getStartOffset() || range.getEndOffset() <= start : "Range :" + range + "; Added: " + newRange; } myAffectedFragments.add(pair); return pair; }
@Override @NotNull public String getPlainText() { int startOffset = getNavigationOffset(); final PsiElement element = getElement(); if (element != null && startOffset != -1) { final Document document = getDocument(); if (document != null) { int lineNumber = document.getLineNumber(startOffset); int lineStart = document.getLineStartOffset(lineNumber); int lineEnd = document.getLineEndOffset(lineNumber); String prefixSuffix = null; if (lineEnd - lineStart > ChunkExtractor.MAX_LINE_LENGTH_TO_SHOW) { prefixSuffix = "..."; lineStart = Math.max( startOffset - ChunkExtractor.OFFSET_BEFORE_TO_SHOW_WHEN_LONG_LINE, lineStart); lineEnd = Math.min(startOffset + ChunkExtractor.OFFSET_AFTER_TO_SHOW_WHEN_LONG_LINE, lineEnd); } String s = document.getCharsSequence().subSequence(lineStart, lineEnd).toString(); if (prefixSuffix != null) s = prefixSuffix + s + prefixSuffix; return s; } } return UsageViewBundle.message("node.invalid"); }
@NotNull private Dimension expandIfNecessary(@NotNull Dimension base) { if (base.width >= myMinWidth && base.height >= myMinHeight) { return base; } return new Dimension(Math.max(myMinWidth, base.width), Math.max(myMinHeight, base.height)); }
private static LineRange expandLineRangeToCoverPsiElements( final LineRange range, Editor editor, final PsiFile file) { Pair<PsiElement, PsiElement> psiRange = getElementRange(editor, file, range); if (psiRange == null) return null; final PsiElement parent = PsiTreeUtil.findCommonParent(psiRange.getFirst(), psiRange.getSecond()); Pair<PsiElement, PsiElement> elementRange = getElementRange(parent, psiRange.getFirst(), psiRange.getSecond()); if (elementRange == null) return null; int endOffset = elementRange.getSecond().getTextRange().getEndOffset(); Document document = editor.getDocument(); if (endOffset > document.getTextLength()) { LOG.assertTrue(!PsiDocumentManager.getInstance(file.getProject()).isUncommited(document)); LOG.assertTrue(PsiDocumentManagerImpl.checkConsistency(file, document)); } int endLine; if (endOffset == document.getTextLength()) { endLine = document.getLineCount(); } else { endLine = editor.offsetToLogicalPosition(endOffset).line + 1; endLine = Math.min(endLine, document.getLineCount()); } int startLine = Math.min( range.startLine, editor.offsetToLogicalPosition(elementRange.getFirst().getTextOffset()).line); endLine = Math.max(endLine, range.endLine); return new LineRange(startLine, endLine); }
/** * Returns the original comment info of the specified element or null * * @param element the specified element * @return text chunk */ @Nullable private static String getOrigCommentInfo(PsiDocCommentOwner element) { StringBuilder sb = new StringBuilder(); PsiElement e = element.getFirstChild(); if (!(e instanceof PsiComment)) { // no comments for this element return null; } else { boolean first = true; while (true) { if (e instanceof PsiDocComment) { PsiComment cm = (PsiComment) e; String text = cm.getText(); if (text.startsWith("//")) { if (!first) sb.append('\n'); sb.append(text.substring(2).trim()); } else if (text.startsWith("/*")) { if (text.charAt(2) == '*') { text = text.substring(3, Math.max(3, text.length() - 2)); } else { text = text.substring(2, Math.max(2, text.length() - 2)); } sb.append(text); } } else if (!(e instanceof PsiWhiteSpace) && !(e instanceof PsiComment)) { break; } first = false; e = e.getNextSibling(); } return sb.toString(); } }
int getNavigationOffset() { Document document = getDocument(); if (document == null) return -1; int offset = getUsageInfo().getNavigationOffset(); if (offset == -1) offset = myOffset; if (offset >= document.getTextLength()) { int line = Math.max(0, Math.min(myLineNumber, document.getLineCount() - 1)); offset = document.getLineStartOffset(line); } return offset; }
private void updatedMovedIntoEnd( final Document document, @NotNull final MoveInfo info, final int offset) { if (offset + 1 < document.getTextLength()) { final int line = document.getLineNumber(offset + 1); final LineRange toMove2 = info.toMove2; if (toMove2 == null) return; info.toMove2 = new LineRange( toMove2.startLine, Math.min(Math.max(line, toMove2.endLine), document.getLineCount() - 1)); } }
public static boolean filterEquals(ClassFilter[] filters1, ClassFilter[] filters2) { if (filters1.length != filters2.length) { return false; } final Set<ClassFilter> f1 = new HashSet<ClassFilter>(Math.max((int) (filters1.length / .75f) + 1, 16)); final Set<ClassFilter> f2 = new HashSet<ClassFilter>(Math.max((int) (filters2.length / .75f) + 1, 16)); Collections.addAll(f1, filters1); Collections.addAll(f2, filters2); return f2.equals(f1); }
private boolean isWhiteSpaceOrComment(@NotNull PsiElement element, @NotNull TextRange range) { final TextRange textRange = element.getTextRange(); TextRange intersection = range.intersection(textRange); if (intersection == null) { return false; } intersection = TextRange.create( Math.max(intersection.getStartOffset() - textRange.getStartOffset(), 0), Math.min( intersection.getEndOffset() - textRange.getStartOffset(), textRange.getLength())); return isWhiteSpaceOrComment(element) || intersection.substring(element.getText()).trim().length() == 0; }
private static int calcMatch(final String expectedName, final List<String> words, int max) { if (expectedName == null) return max; String[] expectedWords = NameUtil.nameToWords(expectedName); int limit = Math.min(words.size(), expectedWords.length); for (int i = 0; i < limit; i++) { String word = words.get(words.size() - i - 1); String expectedWord = expectedWords[expectedWords.length - i - 1]; if (word.equalsIgnoreCase(expectedWord)) { max = Math.max(max, i + 1); } else { break; } } return max; }
public static boolean filterEquals(ClassFilter[] filters1, ClassFilter[] filters2) { if (filters1.length != filters2.length) { return false; } final Set<ClassFilter> f1 = new HashSet<ClassFilter>(Math.max((int) (filters1.length / .75f) + 1, 16)); final Set<ClassFilter> f2 = new HashSet<ClassFilter>(Math.max((int) (filters2.length / .75f) + 1, 16)); for (ClassFilter filter : filters1) { f1.add(filter); } for (ClassFilter aFilters2 : filters2) { f2.add(aFilters2); } return f2.equals(f1); }
private static int getQuickFixType(PsiVariable variable) { PsiElement outerCodeBlock = PsiUtil.getVariableCodeBlock(variable, null); if (outerCodeBlock == null) return -1; List<PsiReferenceExpression> outerReferences = new ArrayList<PsiReferenceExpression>(); collectReferences(outerCodeBlock, variable, outerReferences); int type = MAKE_FINAL; for (PsiReferenceExpression expression : outerReferences) { // if it happens that variable referenced from another inner class, make sure it can be make // final from there PsiClass innerClass = HighlightControlFlowUtil.getInnerClassVariableReferencedFrom(variable, expression); if (innerClass != null) { int thisType = MAKE_FINAL; if (writtenInside(variable, innerClass)) { // cannot make parameter array if (variable instanceof PsiParameter) return -1; thisType = MAKE_ARRAY; } if (thisType == MAKE_FINAL && !canBeFinal(variable, outerReferences)) { thisType = COPY_TO_FINAL; } type = Math.max(type, thisType); } } return type; }
private static void logStats(Collection<PsiFile> otherFiles, long start) { long time = System.currentTimeMillis() - start; final Multiset<String> stats = HashMultiset.create(); for (PsiFile file : otherFiles) { stats.add( StringUtil.notNullize(file.getViewProvider().getVirtualFile().getExtension()) .toLowerCase()); } List<String> extensions = ContainerUtil.newArrayList(stats.elementSet()); Collections.sort( extensions, new Comparator<String>() { @Override public int compare(String o1, String o2) { return stats.count(o2) - stats.count(o1); } }); String message = "Search in " + otherFiles.size() + " files with unknown types took " + time + "ms.\n" + "Mapping their extensions to an existing file type (e.g. Plain Text) might speed up the search.\n" + "Most frequent non-indexed file extensions: "; for (int i = 0; i < Math.min(10, extensions.size()); i++) { String extension = extensions.get(i); message += extension + "(" + stats.count(extension) + ") "; } LOG.info(message); }
public static void initOffsets(final PsiFile file, final OffsetMap offsetMap) { int offset = Math.max( offsetMap.getOffset(CompletionInitializationContext.SELECTION_END_OFFSET), offsetMap.getOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET)); PsiElement element = file.findElementAt(offset); if (element instanceof PsiWhiteSpace && (!element.textContains('\n') || CodeStyleSettingsManager.getSettings(file.getProject()) .getCommonSettings(JavaLanguage.INSTANCE) .METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE)) { element = file.findElementAt(element.getTextRange().getEndOffset()); } if (element == null) return; if (LEFT_PAREN.accepts(element)) { offsetMap.addOffset(LPAREN_OFFSET, element.getTextRange().getStartOffset()); PsiElement list = element.getParent(); PsiElement last = list.getLastChild(); if (last instanceof PsiJavaToken && ((PsiJavaToken) last).getTokenType() == JavaTokenType.RPARENTH) { offsetMap.addOffset(RPAREN_OFFSET, last.getTextRange().getStartOffset()); } offsetMap.addOffset(ARG_LIST_END_OFFSET, list.getTextRange().getEndOffset()); } }
private void enterScope(PsiElement element) { scopeEntered.add(element); m_currentDepth++; if ((m_maximumDepth = Math.max(m_maximumDepth, m_currentDepth)) > myLimit) { stopWalking(); } }
private int getNamesMaxLength() { int len = 0; for (ParameterTableModelItemBase<ParameterInfoImpl> item : myParametersTableModel.getItems()) { final String text = item.parameter.getName(); len = Math.max(len, text == null ? 0 : text.length()); } return len; }
@NotNull private static String toVfString(@NotNull Collection<VirtualFile> list) { List<VirtualFile> sub = new ArrayList<VirtualFile>(list).subList(0, Math.min(list.size(), 100)); return list.size() + " files: " + StringUtil.join(sub, file -> file.getName(), ", ") + (list.size() == sub.size() ? "" : "..."); }
private int getTypesMaxLength() { int len = 0; for (ParameterTableModelItemBase<ParameterInfoImpl> item : myParametersTableModel.getItems()) { final String text = item.typeCodeFragment == null ? null : item.typeCodeFragment.getText(); len = Math.max(len, text == null ? 0 : text.length()); } return len; }
QuickDocInfoPane( @NotNull PsiElement documentationAnchor, @NotNull PsiElement elementUnderMouse, @NotNull JComponent baseDocControl) { myBaseDocControl = baseDocControl; PresentationFactory presentationFactory = new PresentationFactory(); for (AbstractDocumentationTooltipAction action : ourTooltipActions) { Icon icon = action.getTemplatePresentation().getIcon(); Dimension minSize = new Dimension(icon.getIconWidth(), icon.getIconHeight()); myButtons.add( new ActionButton( action, presentationFactory.getPresentation(action), IdeTooltipManager.IDE_TOOLTIP_PLACE, minSize)); action.setDocInfo(documentationAnchor, elementUnderMouse); } Collections.reverse(myButtons); setPreferredSize(baseDocControl.getPreferredSize()); setMaximumSize(baseDocControl.getMaximumSize()); setMinimumSize(baseDocControl.getMinimumSize()); setBackground(baseDocControl.getBackground()); add(baseDocControl, Integer.valueOf(0)); int minWidth = 0; int minHeight = 0; int buttonWidth = 0; for (JComponent button : myButtons) { button.setBorder(null); button.setBackground(baseDocControl.getBackground()); add(button, Integer.valueOf(1)); button.setVisible(false); Dimension preferredSize = button.getPreferredSize(); minWidth += preferredSize.width; minHeight = Math.max(minHeight, preferredSize.height); buttonWidth = Math.max(buttonWidth, preferredSize.width); } myButtonWidth = buttonWidth; int margin = 2; myMinWidth = minWidth + margin * 2 + (myButtons.size() - 1) * BUTTON_HGAP; myMinHeight = minHeight + margin * 2; }
@Override public void exchangeRows(int row, int targetRow) { if (row < 0 || row >= getVariableData().length) return; if (targetRow < 0 || targetRow >= getVariableData().length) return; final VariableData currentItem = getVariableData()[row]; getVariableData()[row] = getVariableData()[targetRow]; getVariableData()[targetRow] = currentItem; TypeSelector currentSelector = myParameterTypeSelectors[row]; myParameterTypeSelectors[row] = myParameterTypeSelectors[targetRow]; myParameterTypeSelectors[targetRow] = currentSelector; myTypeRendererCombo.setModel(new DefaultComboBoxModel(getVariableData())); myTableModel.fireTableRowsUpdated(Math.min(targetRow, row), Math.max(targetRow, row)); myTable.getSelectionModel().setSelectionInterval(targetRow, targetRow); updateSignature(); }
private String getText(final int start, final int end) { int currentOldDocumentOffset = 0; int currentNewDocumentOffset = 0; StringBuilder text = new StringBuilder(); Iterator<Pair<MutableTextRange, StringBuffer>> iterator = myAffectedFragments.iterator(); while (iterator.hasNext() && currentNewDocumentOffset < end) { final Pair<MutableTextRange, StringBuffer> pair = iterator.next(); final MutableTextRange range = pair.getFirst(); final StringBuffer buffer = pair.getSecond(); final int fragmentEndInNewDocument = range.getStartOffset() + buffer.length(); if (range.getStartOffset() <= start && fragmentEndInNewDocument >= end) { return buffer.substring(start - range.getStartOffset(), end - range.getStartOffset()); } if (range.getStartOffset() >= start) { final int effectiveStart = Math.max(currentNewDocumentOffset, start); text.append( myDocument.getCharsSequence(), effectiveStart - currentNewDocumentOffset + currentOldDocumentOffset, Math.min(range.getStartOffset(), end) - currentNewDocumentOffset + currentOldDocumentOffset); if (end > range.getStartOffset()) { text.append( buffer.substring(0, Math.min(end - range.getStartOffset(), buffer.length()))); } } currentOldDocumentOffset += range.getEndOffset() - currentNewDocumentOffset; currentNewDocumentOffset = fragmentEndInNewDocument; } if (currentNewDocumentOffset < end) { final int effectiveStart = Math.max(currentNewDocumentOffset, start); text.append( myDocument.getCharsSequence(), effectiveStart - currentNewDocumentOffset + currentOldDocumentOffset, end - currentNewDocumentOffset + currentOldDocumentOffset); } return text.toString(); }
private void commitToOriginalInner() { final String text = myNewDocument.getText(); final Map< PsiLanguageInjectionHost, Set<Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>>> map = ContainerUtil.classify( myMarkers.iterator(), new Convertor< Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>, PsiLanguageInjectionHost>() { @Override public PsiLanguageInjectionHost convert( final Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> o) { final PsiElement element = o.third.getElement(); return (PsiLanguageInjectionHost) element; } }); PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); documentManager.commitDocument(myOrigDocument); // commit here and after each manipulator update int localInsideFileCursor = 0; for (PsiLanguageInjectionHost host : map.keySet()) { if (host == null) continue; String hostText = host.getText(); ProperTextRange insideHost = null; StringBuilder sb = new StringBuilder(); for (Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> entry : map.get(host)) { RangeMarker origMarker = entry.first; // check for validity? int hostOffset = host.getTextRange().getStartOffset(); ProperTextRange localInsideHost = new ProperTextRange( origMarker.getStartOffset() - hostOffset, origMarker.getEndOffset() - hostOffset); RangeMarker rangeMarker = entry.second; ProperTextRange localInsideFile = new ProperTextRange( Math.max(localInsideFileCursor, rangeMarker.getStartOffset()), rangeMarker.getEndOffset()); if (insideHost != null) { // append unchanged inter-markers fragment sb.append( hostText.substring(insideHost.getEndOffset(), localInsideHost.getStartOffset())); } sb.append( localInsideFile.getEndOffset() <= text.length() && !localInsideFile.isEmpty() ? localInsideFile.substring(text) : ""); localInsideFileCursor = localInsideFile.getEndOffset(); insideHost = insideHost == null ? localInsideHost : insideHost.union(localInsideHost); } assert insideHost != null; ElementManipulators.getManipulator(host).handleContentChange(host, insideHost, sb.toString()); documentManager.commitDocument(myOrigDocument); } }
private int updateMovedRegionEnd( final Document document, int movedLineStart, final int valueStart, @NotNull final MoveInfo info, final boolean down) { final int line = document.getLineNumber(valueStart); final LineRange toMove = info.toMove; int delta = line - toMove.endLine; info.toMove = new LineRange(toMove.startLine, Math.max(line, toMove.endLine)); // update moved range if (delta > 0 && down) { final LineRange toMove2 = info.toMove2; info.toMove2 = new LineRange( toMove2.startLine + delta, Math.min(toMove2.endLine + delta, document.getLineCount() - 1)); movedLineStart = document.getLineStartOffset(toMove.startLine); } return movedLineStart; }
private static void addNamesFromStatistics( Set<String> names, VariableKind variableKind, @Nullable String propertyName, @Nullable PsiType type) { String[] allNames = JavaStatisticsManager.getAllVariableNamesUsed(variableKind, propertyName, type); int maxFrequency = 0; for (String name : allNames) { int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); maxFrequency = Math.max(maxFrequency, count); } int frequencyLimit = Math.max(5, maxFrequency / 2); for (String name : allNames) { if (names.contains(name)) { continue; } int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); if (LOG.isDebugEnabled()) { LOG.debug("new name:" + name + " count:" + count); LOG.debug("frequencyLimit:" + frequencyLimit); } if (count >= frequencyLimit) { names.add(name); } } if (propertyName != null && type != null) { addNamesFromStatistics(names, variableKind, propertyName, null); addNamesFromStatistics(names, variableKind, null, type); } }
private int psiToDocumentOffset(int offset) { for (Map.Entry<TextRange, CharSequence> entry : myAffectedFragments.entrySet()) { int lengthAfter = entry.getValue().length(); TextRange range = entry.getKey(); if (range.getStartOffset() + lengthAfter < offset) { offset += range.getLength() - lengthAfter; continue; } // for offsets inside replaced ranges, return the starts of the original affected fragments // in document return Math.min(range.getStartOffset(), offset); } return offset; }
private static void adjustIndentationInRange( final PsiFile file, final Document document, final TextRange[] indents, final int indentAdjustment) { final CharSequence charsSequence = document.getCharsSequence(); for (final TextRange indent : indents) { final String oldIndentStr = charsSequence.subSequence(indent.getStartOffset() + 1, indent.getEndOffset()).toString(); final int oldIndent = IndentHelperImpl.getIndent(file.getProject(), file.getFileType(), oldIndentStr, true); final String newIndentStr = IndentHelperImpl.fillIndent( file.getProject(), file.getFileType(), Math.max(oldIndent + indentAdjustment, 0)); document.replaceString(indent.getStartOffset() + 1, indent.getEndOffset(), newIndentStr); } }
private int updatedMovedRegionStart( final Document document, int movedLineStart, final int offset, @NotNull final MoveInfo info, final boolean down) { final int line = document.getLineNumber(offset); final LineRange toMove = info.toMove; int delta = toMove.startLine - line; info.toMove = new LineRange(Math.min(line, toMove.startLine), toMove.endLine); // update moved range if (delta > 0 && !down) { final LineRange toMove2 = info.toMove2; info.toMove2 = new LineRange(toMove2.startLine - delta, toMove2.endLine - delta); movedLineStart = document.getLineStartOffset(toMove.startLine); } return movedLineStart; }
/** * It's possible that we need to expand quick doc control's width in order to provide better * visual representation (see http://youtrack.jetbrains.com/issue/IDEA-101425). This method * calculates that width expand. * * @param buttonWidth icon button's width * @param updatedText text which will be should at the quick doc control * @return width increase to apply to the target quick doc control (zero if no additional width * increase is required) */ private static int calculateWidthIncrease(int buttonWidth, String updatedText) { int maxLineWidth = 0; TIntArrayList lineWidths = new TIntArrayList(); for (String lineText : StringUtil.split(updatedText, "<br/>")) { String html = HintUtil.prepareHintText(lineText, HintUtil.getInformationHint()); int width = new JLabel(html).getPreferredSize().width; maxLineWidth = Math.max(maxLineWidth, width); lineWidths.add(width); } if (!lineWidths.isEmpty()) { int firstLineAvailableTrailingWidth = maxLineWidth - lineWidths.get(0); if (firstLineAvailableTrailingWidth >= buttonWidth) { return 0; } else { return buttonWidth - firstLineAvailableTrailingWidth; } } return 0; }
private void queueUnresolvedFilesSinceLastRestart() { PersistentFS fs = PersistentFS.getInstance(); int maxId = FSRecords.getMaxId(); TIntArrayList list = new TIntArrayList(); for (int id = fileIsResolved.nextClearBit(1); id >= 0 && id < maxId; id = fileIsResolved.nextClearBit(id + 1)) { int nextSetBit = fileIsResolved.nextSetBit(id); int endOfRun = Math.min(maxId, nextSetBit == -1 ? maxId : nextSetBit); do { VirtualFile virtualFile = fs.findFileById(id); if (queueIfNeeded(virtualFile, myProject)) { list.add(id); } else { fileIsResolved.set(id); } } while (++id < endOfRun); } log("Initially added to resolve " + toVfString(list.toNativeArray())); }
private static PsiStatement getAnchor(PsiExpression[] expressionOccurences) { PsiElement parent = expressionOccurences[0]; int minOffset = expressionOccurences[0].getTextRange().getStartOffset(); for (int i = 1; i < expressionOccurences.length; i++) { parent = PsiTreeUtil.findCommonParent(parent, expressionOccurences[i]); LOG.assertTrue(parent != null); minOffset = Math.min(minOffset, expressionOccurences[i].getTextRange().getStartOffset()); } PsiCodeBlock block = (PsiCodeBlock) (parent instanceof PsiCodeBlock ? parent : PsiTreeUtil.getParentOfType(parent, PsiCodeBlock.class)); LOG.assertTrue(block != null && block.getStatements().length > 0); PsiStatement[] statements = block.getStatements(); for (int i = 1; i < statements.length; i++) { if (statements[i].getTextRange().getStartOffset() > minOffset) return statements[i - 1]; } return statements[statements.length - 1]; }