int (int startOffset, int targetColumn, int xOffset) { int x = xOffset; if (startOffset == 0 && myPrefixText != null) { x += myPrefixWidthInPixels; } if (targetColumn <= 0) return x; ++myTotalRequests; for (int i = 0; i < myLastStartOffsets.length; ++i) { if (startOffset == myLastStartOffsets[i] && targetColumn == myLastTargetColumns[i] && xOffset == myLastXOffsets[i]) { ++myLastCacheHits; if ((myLastCacheHits & 0xFFF) == 0) { // todo remove PsiFile file = myProject != null ? PsiDocumentManager.getInstance(myProject).getCachedPsiFile(myDocument) : null; LOG.info("Cache hits:" + myLastCacheHits + ", total requests:" + myTotalRequests + "," + (file != null ? file.getViewProvider().getVirtualFile() : null)); } return myLastXs[i]; } } int offset = startOffset; CharSequence text = myDocument.getImmutableCharSequence(); int textLength = myDocument.getTextLength(); // We need to calculate max offset to provide to the IterationState here based on the given start offset and target // visual column. The problem is there is a possible case that there is a collapsed fold region at the target interval, // so, we can't just use 'startOffset + targetColumn' as a max end offset. IterationState state = new IterationState(this, startOffset, calcEndOffset(startOffset, targetColumn), false); int fontType = state.getMergedAttributes().getFontType(); int plainSpaceSize = EditorUtil.getSpaceWidth(Font.PLAIN, this); int column = 0; outer: while (column < targetColumn) { if (offset >= textLength) break; if (offset >= state.getEndOffset()) { state.advance(); fontType = state.getMergedAttributes().getFontType(); } // We need to consider 'before soft wrap drawing'. SoftWrap softWrap = getSoftWrapModel().getSoftWrap(offset); if (softWrap != null && offset > startOffset) { column++; x += getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED); // Assuming that first soft wrap symbol is line feed or all soft wrap symbols before the first line feed are spaces. break; } FoldRegion region = state.getCurrentFold(); if (region != null) { char[] placeholder = region.getPlaceholderText().toCharArray(); for (char aPlaceholder : placeholder) { x += EditorUtil.charWidth(aPlaceholder, fontType, this); column++; if (column >= targetColumn) break outer; } offset = region.getEndOffset(); } else { char c = text.charAt(offset); if (c == '\n') { break; } if (c == '\t') { int prevX = x; x = EditorUtil.nextTabStop(x, this); int columnDiff = (x - prevX) / plainSpaceSize; if ((x - prevX) % plainSpaceSize > 0) { // There is a possible case that tabulation symbol takes more than one visual column to represent and it's shown at // soft-wrapped line. Soft wrap sign width may be not divisible by space size, hence, part of tabulation symbol represented // as a separate visual column may take less space than space width. columnDiff++; } column += columnDiff; } else { x += EditorUtil.charWidth(c, fontType, this); column++; } offset++; } } if (column != targetColumn) { x += EditorUtil.getSpaceWidth(fontType, this) * (targetColumn - column); } myLastTargetColumns[myCurrentCachePosition] = targetColumn; myLastStartOffsets[myCurrentCachePosition] = startOffset; myLastXs[myCurrentCachePosition] = x; myLastXOffsets[myCurrentCachePosition] = xOffset; myCurrentCachePosition = (myCurrentCachePosition + 1) % myLastStartOffsets.length; return x; }