blob: 05785b44ad847c67d5ac05021d15ef828ce5ac98 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Chris.Dennis@invidi.com - http://bugs.eclipse.org/bugs/show_bug.cgi?id=29027
*******************************************************************************/
package org.eclipse.ui.texteditor;
import java.util.ResourceBundle;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.ISourceViewer;
/**
* This action implements smart return. Instead of breaking the line where we
* are, we do the following:
* <p>
* <b>Smart Enter</b>
* </p>
* <ul>
* <li>if the caret is on a line containing any non-whitespace, a line is
* inserted below the current one and the caret moved to it,</li>
* <li>if the caret is on a whitespace-only line, a line is inserted below the
* current line, but the caret stays in its position.</li>
* </ul>
* <p>
* <b>Smart Enter Inverse</b>
* </p>
* <ul>
* <li>if the caret is on a line containing any non-whitespace, we insert a line
* above the current one and move the caret to it (i.e. it stays at the same
* offset in the widget),</li>
* <li>if the caret is on a whitespace-only line, a line is inserted above the
* current line, but the caret stays in its logical position (i.e., it gets
* shifted one line down in the document, but keeps its position relative to the
* content following the caret).</li>
* </ul>
*
* @since 3.0
*/
public class InsertLineAction extends TextEditorAction {
/**
* <code>true</code> if this action inserts a line above the current (Smart Enter Inverse),
* <code>false</code> otherwise
*/
protected boolean fAbove;
/**
* Creates a new smart enter action.
* @param bundle the resource bundle
* @param prefix the prefix to use to get properties from <code>bundle</code>
* @param textEditor the editor that the action acts upon
* @param above whether new lines are inserted above or below the caret's line.
*/
public InsertLineAction(ResourceBundle bundle, String prefix, ITextEditor textEditor, boolean above) {
super(bundle, prefix, textEditor);
fAbove= above;
}
@Override
public void update() {
super.update();
if (isEnabled())
setEnabled(canModifyEditor());
}
@Override
public void run() {
/*
* Implementation note: instead of computing any indentations needed
* (which we can't at this generic level), we simply insert a new
* line delimiter either at the end of the current line (normal) or
* the end of the previous line (reverse). By operating directly on
* the text widget, any auto-indent strategies can pick up on the
* delimiter and perform any content-dependent modifications.
*/
ITextEditor ed= getTextEditor();
if (!(ed instanceof AbstractTextEditor))
return;
if (!validateEditorInputState())
return;
AbstractTextEditor editor= (AbstractTextEditor) ed;
ISourceViewer sv= editor.getSourceViewer();
if (sv == null)
return;
IDocument document= sv.getDocument();
if (document == null)
return;
StyledText st= sv.getTextWidget();
if (st == null || st.isDisposed())
return;
try {
// get current line
int widgetOffset= st.getCaretOffset();
int offset= AbstractTextEditor.widgetOffset2ModelOffset(sv, widgetOffset);
int currentLineNumber= document.getLineOfOffset(offset);
IRegion currentLine= document.getLineInformation(currentLineNumber);
int insertionOffset= -1;
if (fAbove) {
if (currentLineNumber != 0) {
IRegion previousLine= document.getLineInformation(currentLineNumber - 1);
insertionOffset= previousLine.getOffset() + previousLine.getLength();
}
} else {
insertionOffset= currentLine.getOffset() + currentLine.getLength();
}
boolean updateCaret= true;
int widgetInsertionOffset= AbstractTextEditor.modelOffset2WidgetOffset(sv, insertionOffset);
if (widgetInsertionOffset == -1 && fAbove) {
// assume that the previous line was not accessible
// (e.g. folded, or we are on line 0)
// -> we insert the newline at the beginning of the current line, after any leading WS
insertionOffset= currentLine.getOffset() + getIndentationLength(document, currentLine);
widgetInsertionOffset= AbstractTextEditor.modelOffset2WidgetOffset(sv, insertionOffset);
updateCaret= false;
}
if (widgetInsertionOffset == -1)
return;
// mark caret
Position caret= new Position(insertionOffset, 0);
document.addPosition(caret);
st.setSelectionRange(widgetInsertionOffset, 0);
// operate directly on the widget
st.replaceTextRange(widgetInsertionOffset, 0, st.getLineDelimiter());
// restore caret unless an auto-indenter has already moved the caret
// then leave it alone
document.removePosition(caret);
if (updateCaret && st.getSelection().x == widgetInsertionOffset) {
int widgetCaret= AbstractTextEditor.modelOffset2WidgetOffset(sv, caret.getOffset());
if (widgetCaret != -1)
st.setSelectionRange(widgetCaret, 0);
st.showSelection();
}
} catch (BadLocationException e) {
// ignore
}
}
/**
* Computes the indentation length of a line.
*
* @param document the document
* @param line the line
* @return the number of whitespace characters at the beginning of
* <code>line</code>
* @throws BadLocationException on document access error
*/
private int getIndentationLength(IDocument document, IRegion line) throws BadLocationException {
int pos= line.getOffset();
int max= pos + line.getLength();
while (pos < max) {
if (!Character.isWhitespace(document.getChar(pos)))
break;
pos++;
}
return pos - line.getOffset();
}
}