+
+ /**
+ * Inspects all lines of the given document and wraps all of them that exceed {@link CodeStyleSettings#RIGHT_MARGIN right margin}.
+ * <p/>
+ * I.e. the algorithm is to do the following for every line:
+ * <p/>
+ * <pre>
+ * <ol>
+ * <li>
+ * Check if the line exceeds {@link CodeStyleSettings#RIGHT_MARGIN right margin}. Go to the next line in the case of
+ * negative answer;
+ * </li>
+ * <li>Determine line wrap position; </li>
+ * <li>
+ * Perform 'smart wrap', i.e. not only wrap the line but insert additional characters over than line feed if necessary.
+ * For example consider that we wrap a single-line comment - we need to insert comment symbols on a start of the wrapped
+ * part as well. Generally, we get the same behavior as during pressing 'Enter' at wrap position during editing document;
+ * </li>
+ * </ol>
+ </pre>
+ *
+ * @param file file that holds parsed document tree
+ * @param document target document
+ * @param startOffset start offset of the first line to check for wrapping (inclusive)
+ * @param endOffset end offset of the first line to check for wrapping (exclusive)
+ */
+ private void wrapLongLinesIfNecessary(@NotNull PsiFile file, @NotNull Document document, int startOffset, int endOffset) {
+ Editor editor = PsiUtilBase.findEditor(file);
+ if (editor == null) {
+ return;
+ }
+
+ LineWrapPositionStrategy strategy = LanguageLineWrapPositionStrategy.INSTANCE.forEditor(editor);
+ CharSequence text = document.getCharsSequence();
+ int startLine = document.getLineNumber(startOffset);
+ int endLine = document.getLineNumber(Math.min(document.getTextLength(), endOffset) - 1);
+ int maxLine = Math.min(document.getLineCount(), endLine + 1);
+ int tabSize = EditorUtil.getTabSize(editor);
+ int spaceSize = EditorUtil.getSpaceWidth(Font.PLAIN, editor);
+
+ for (int line = startLine; line < maxLine; line++) {
+ int startLineOffset = document.getLineStartOffset(line);
+ int endLineOffset = document.getLineEndOffset(line);
+
+ boolean hasTabs = false;
+ boolean canOptimize = true;
+ boolean hasNonSpaceSymbols = false;
+ loop:
+ for (int i = startLineOffset; i < Math.min(endLineOffset, endOffset); i++) {
+ char c = text.charAt(i);
+ switch (c) {
+ case '\t': {
+ hasTabs = true;
+ if (hasNonSpaceSymbols) {
+ canOptimize = false;
+ break loop;
+ }
+ }
+ case ' ': break;
+ default: hasNonSpaceSymbols = true;
+ }
+ }
+
+ int preferredWrapPosition = Integer.MAX_VALUE;
+ if (!hasTabs) {
+ if (Math.min(endLineOffset, endOffset) >= mySettings.RIGHT_MARGIN) {
+ preferredWrapPosition = startLineOffset + mySettings.RIGHT_MARGIN - FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS;
+ }
+ }
+ else if (canOptimize) {
+ int width = 0;
+ int symbolWidth;
+ for (int i = startLineOffset; i < Math.min(endLineOffset, endOffset); i++) {
+ char c = text.charAt(i);
+ switch (c) {
+ case '\t': symbolWidth = tabSize - (width % tabSize); break;
+ default: symbolWidth = 1;
+ }
+ if (width + symbolWidth + FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS >= mySettings.RIGHT_MARGIN
+ && (Math.min(endLineOffset, endOffset) - i) >= FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS)
+ {
+ preferredWrapPosition = i - 1;
+ break;
+ }
+ width += symbolWidth;
+ }
+ }
+ else {
+ int width = 0;
+ int x = 0;
+ int newX;
+ int symbolWidth;
+ for (int i = startLineOffset; i < Math.min(endLineOffset, endOffset); i++) {
+ char c = text.charAt(i);
+ switch (c) {
+ case '\t':
+ newX = EditorUtil.nextTabStop(x, editor);
+ int diffInPixels = newX - x;
+ symbolWidth = diffInPixels / spaceSize;
+ if (diffInPixels % spaceSize > 0) {
+ symbolWidth++;
+ }
+ break;
+ default: newX = x + EditorUtil.charWidth(c, Font.PLAIN, editor); symbolWidth = 1;
+ }
+ if (width + symbolWidth + FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS >= mySettings.RIGHT_MARGIN
+ && (Math.min(endLineOffset, endOffset) - i) >= FormatConstants.RESERVED_LINE_WRAP_WIDTH_IN_COLUMNS)
+ {
+ preferredWrapPosition = i - 1;
+ break;
+ }
+ x = newX;
+ width += symbolWidth;
+ }
+ }
+ if (preferredWrapPosition >= endLineOffset) {
+ continue;
+ }
+ if (preferredWrapPosition >= endOffset) {
+ return;
+ }
+
+ // We know that current line exceeds right margin if control flow reaches this place, so, wrap it.
+ int wrapOffset = strategy.calculateWrapPosition(
+ text, Math.max(startLineOffset, startOffset), Math.min(endLineOffset, endOffset), preferredWrapPosition, false
+ );
+ editor.getCaretModel().moveToOffset(wrapOffset);
+ DataContext dataContext = DataManager.getInstance().getDataContext(editor.getComponent());
+
+ SelectionModel selectionModel = editor.getSelectionModel();
+ boolean restoreSelection;
+ int startSelectionOffset = 0;
+ int endSelectionOffset = 0;
+ if (restoreSelection = selectionModel.hasSelection()) {
+ startSelectionOffset = selectionModel.getSelectionStart();
+ endSelectionOffset = selectionModel.getSelectionEnd();
+ selectionModel.removeSelection();
+ }
+ int textLengthBeforeWrap = document.getTextLength();
+ EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_ENTER).execute(editor, dataContext);
+ if (restoreSelection) {
+ int symbolsDiff = document.getTextLength() - textLengthBeforeWrap;
+ int newSelectionStart = startSelectionOffset;
+ int newSelectionEnd = endSelectionOffset;
+ if (startSelectionOffset >= wrapOffset) {
+ newSelectionStart += symbolsDiff;
+ }
+ if (endSelectionOffset >= wrapOffset) {
+ newSelectionEnd += symbolsDiff;
+ }
+ selectionModel.setSelection(newSelectionStart, newSelectionEnd);
+ }
+
+
+ // There is a possible case that particular line is so long, that its part that exceeds right margin and is wrapped
+ // still exceeds right margin. Hence, we recursively call 'wrap long lines' sub-routine in order to handle that.
+
+ wrapLongLinesIfNecessary(file, document, document.getLineStartOffset(line + 1), endOffset);
+ return;
+ }
+ }