void (@NotNull Editor editor, @NotNull DataContext dataContext, long modificationStampBeforeTyping) { Project project = editor.getProject(); Document document = editor.getDocument(); AutoWrapChange change = myAutoWrapChanges.get(document); if (change != null) { change.charTyped(editor, modificationStampBeforeTyping); } // Return eagerly if we don't need to auto-wrap line, e.g. because of right margin exceeding. if (/*editor.isOneLineMode() || */project == null || !editor.getSettings().isWrapWhenTypingReachesRightMargin(project) || (TemplateManager.getInstance(project) != null && TemplateManager.getInstance(project).getActiveTemplate(editor) != null)) { return; } CaretModel caretModel = editor.getCaretModel(); int caretOffset = caretModel.getOffset(); int line = document.getLineNumber(caretOffset); int startOffset = document.getLineStartOffset(line); int endOffset = document.getLineEndOffset(line); final CharSequence endOfString = document.getCharsSequence().subSequence(caretOffset, endOffset); final boolean endsWithSpaces = StringUtil.isEmptyOrSpaces(String.valueOf(endOfString)); // Check if right margin is exceeded. int margin = editor.getSettings().getRightMargin(project); if (margin <= 0) { return; } LogicalPosition logEndLinePosition = editor.offsetToLogicalPosition(endOffset); if (margin >= logEndLinePosition.column) { if (change != null) { change.modificationStamp = document.getModificationStamp(); } return; } // We assume that right margin is exceeded if control flow reaches this place. Hence, we define wrap position and perform // smart line break there. LineWrapPositionStrategy strategy = LanguageLineWrapPositionStrategy.INSTANCE.forEditor(editor); // There is a possible case that user starts typing in the middle of the long string. Hence, there is a possible case that // particular symbols were already wrapped because of typing. Example: // a b c d e f g <caret>h i j k l m n o p| <- right margin // Suppose the user starts typing at caret: // type '1': a b c d e f g 1<caret>h i j k l m n o | <- right margin // p | <- right margin // type '2': a b c d e f g 12<caret>h i j k l m n o| <- right margin // | <- right margin // p | <- right margin // type '3': a b c d e f g 123<caret>h i j k l m n | <- right margin // o | <- right margin // | <- right margin // p | <- right margin // We want to prevent such behavior, hence, we remove automatically generated wraps and wrap the line as a whole. if (change == null) { change = new AutoWrapChange(); myAutoWrapChanges.put(document, change); } else { final int start = change.change.getStart(); final int end = change.change.getEnd(); if (!change.isEmpty() && start < end) { document.replaceString(start, end, change.change.getText()); } change.reset(); } change.update(editor); // Is assumed to be max possible number of characters inserted on the visual line with caret. int maxPreferredOffset = editor.logicalPositionToOffset( new LogicalPosition(caretModel.getLogicalPosition().line, Math.max(0, margin - FormatConstants.getReservedLineWrapWidthInColumns(editor))) ); int wrapOffset = strategy.calculateWrapPosition(document, project, startOffset, endOffset, maxPreferredOffset, true, false); if (wrapOffset < 0) { return; } WhiteSpaceFormattingStrategy formattingStrategy = WhiteSpaceFormattingStrategyFactory.getStrategy(editor); if (wrapOffset <= startOffset || wrapOffset > maxPreferredOffset || formattingStrategy.check(document.getCharsSequence(), startOffset, wrapOffset) >= wrapOffset) { // Don't perform hard line wrapping if it doesn't makes sense (no point to wrap at first position and no point to wrap // on first non-white space symbol because wrapped part will have the same indent value). return; } final int[] wrapIntroducedSymbolsNumber = new int[1]; final int[] caretOffsetDiff = new int[1]; final int baseCaretOffset = caretModel.getOffset(); DocumentListener listener = new DocumentListener() { @Override public void beforeDocumentChange(@NotNull DocumentEvent event) { if (event.getOffset() < baseCaretOffset + caretOffsetDiff[0]) { caretOffsetDiff[0] += event.getNewLength() - event.getOldLength(); } if (autoFormatted(event)) { return; } wrapIntroducedSymbolsNumber[0] += event.getNewLength() - event.getOldLength(); } private boolean autoFormatted(DocumentEvent event) { return event.getNewLength() <= event.getOldLength() && endsWithSpaces; } }; caretModel.moveToOffset(wrapOffset); DataManager.getInstance().saveInDataContext(dataContext, AUTO_WRAP_LINE_IN_PROGRESS_KEY, true); document.addDocumentListener(listener); try { EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_ENTER).execute(editor, editor.getCaretModel().getCurrentCaret(), dataContext); } finally { DataManager.getInstance().saveInDataContext(dataContext, AUTO_WRAP_LINE_IN_PROGRESS_KEY, null); document.removeDocumentListener(listener); } change.modificationStamp = document.getModificationStamp(); change.change.setStart(wrapOffset); change.change.setEnd(wrapOffset + wrapIntroducedSymbolsNumber[0]); caretModel.moveToOffset(baseCaretOffset + caretOffsetDiff[0]); }