/**
* Copyright (c) 2009, 2010 Mark Feber, MulgaSoft
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.mulgasoft.emacsplus.commands;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.text.IFindReplaceTargetExtension3;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.texteditor.ITextEditor;
import com.mulgasoft.emacsplus.IEmacsPlusCommandDefinitionIds;
import com.mulgasoft.emacsplus.MarkUtils;
import java.util.regex.Pattern;
/**
* Implement simple version of paragraph commands suitable for programming languages
*
* We don't pay attention to paragraph start, just paragraph separate
* Since, as in Emacs major modes for programs, paragraphs begin and end only at blank
* lines. This makes the paragraph commands useful, even though there are no paragraphs
* as such in a program.
*
* @author Mark Feber - initial API and implementation
*/
public abstract class ParagraphHandler extends EmacsPlusCmdHandler {
final static String PARAGRAPH_START = "^[ \t\f\r\n]*$"; //$NON-NLS-1$
final static String REGEX_BOL_HACK = "^[\\s\\S]"; //$NON-NLS-1$
final static Pattern BLANK_CHECK = Pattern.compile(PARAGRAPH_START);
protected IFindReplaceTarget getTarget(ITextEditor editor) {
return (IFindReplaceTarget)editor.getAdapter(IFindReplaceTarget.class);
}
protected int getParagraphOffset(ITextEditor editor, boolean forward) {
if (findNext(editor, forward) == -1) {
findNextEmpty(editor, forward);
}
// get correct empty line in model coords
return getCurrentSelection(editor).getOffset();
}
// Work around bug in FindReplaceDocumentAdapter (we use this because it provides support for narrowed regions, etc.)
// First, look for normal pattern with findNext, but it will return -1 on ^$ lines
// so, if -1 then look for empty lines with findNextEmpty
/**
* Find the next occurrence of empty line(s)
* Will match all valid lines except single ^$
*
* @param editor
* @param forward true if moving forward
* @return -1 if not found, else offset of match in widget coords
*/
protected int findNext(ITextEditor editor, boolean forward) {
IFindReplaceTarget frt = getTarget(editor);
Point selection = frt.getSelection();
// don't move past eob
int offset = Math.min(MarkUtils.getCharCount(editor), Math.max(0,MarkUtils.getCaretOffset(editor) + (forward ? 1 : -2)));
int result = ((IFindReplaceTargetExtension3)frt).findAndSelect(offset, PARAGRAPH_START, forward, false, false, true);
if (result != -1) {
if ((forward && offset > result) || (!forward && offset < result)) {
// reset if we've wrapped around in the search
MarkUtils.setWidgetSelection(editor, selection.x, selection.x + selection.y);
result = -1;
}
}
return result;
}
/**
* Find the next occurrence of empty line consisting of ^$
* Since findNext will find multiple sequential ^$'s, we only need find one
*
* @param editor
* @param forward
* @return offset of match in widget coords
*/
protected int findNextEmpty(ITextEditor editor, boolean forward) {
IFindReplaceTarget frt = getTarget(editor);
Point selection = frt.getSelection();
int count = MarkUtils.getCharCount(editor);
int coffset = MarkUtils.getCaretOffset(editor);
// don't move past EOB
int offset = Math.min(count,coffset + (forward ? 1 : -1)); //(forward ? selection.x + selection.y+1 : selection.x-2);
int next = 0;
int prev = -1;
do {
next = ((IFindReplaceTargetExtension3)frt).findAndSelect(offset, REGEX_BOL_HACK, forward, false, false, true);
if (next == -1 || prev == next){
// we've run out of buffer
offset = (forward ? count : 0);
MarkUtils.setWidgetSelection(editor, offset, offset);
return offset;
} else if (forward && offset > next) {
// protect against overrun
MarkUtils.setWidgetSelection(editor, count, count);
return count;
} else if (!forward && offset < next) {
// protect against overrun
MarkUtils.setWidgetSelection(editor, 0, 0);
return 0;
} else {
selection = frt.getSelection();
// Eclipse internals (frda) return different regions depending on whether the file has a
// single or double line delimiter e.g. \r\n or \n. In the former it returns a 0 length
// region, and in the latter a length of 1
if (selection.y == 0 || (!forward && selection.x == 0)) {
// found it, or reached the top
return selection.x;
} if (selection.y == 1) {
// check for single line delimiter
char c = frt.getSelectionText().charAt(0);
if (c == '\n' || c == '\r'){
return selection.x;
}
}
// the widget count could change if a folded region expands automatically
count = MarkUtils.getCharCount(editor);
// don't move past EOB
offset = Math.min(count,(forward ? selection.x + selection.y+1 : selection.x-1));
prev = next;
}
} while (next != -1);
return next;
}
/**
* Check if the specified line is blank
*
* @param document
* @param line
* @return true if blank, else false
*
* @throws BadLocationException
*/
boolean isBlank(IDocument document, int line) throws BadLocationException {
IRegion lineInfo = document.getLineInformation(line);
return lineInfo.getLength() == 0 || BLANK_CHECK.matcher(document.get(lineInfo.getOffset(), lineInfo.getLength())).matches();
}
/**
* Check if we're at buffer top (or top of narrowed region)
*
* @param editor
* @param selection
* @return true is condition satisfied, else false
*/
boolean isAtTop(ITextEditor editor, ITextSelection selection) {
// if we're not at buffer top (or top of narrowed region)
return (selection.getOffset() == 0 ||
(editor.showsHighlightRangeOnly() && selection.getOffset() == editor.getHighlightRange().getOffset()));
}
boolean isAtBottom(ITextEditor editor, IDocument document, ITextSelection selection) {
int offset = selection.getOffset() + selection.getLength();
// TODO account for EOL length
return (offset+1 >= document.getLength());
}
/**
* Called when we're expanding a mark selection
*
* @param editor
* @param offset
* @param markSelection
* @return NO_OFFSET
*/
protected int selectTransform(ITextEditor editor, int offset, ITextSelection markSelection) {
// we're expanding a mark selection
int mark = getMark(editor);
if (mark == -1 ||
((markSelection != null && !checkMark(mark,markSelection.getOffset(),markSelection.getLength())))){
try {
executeCommand(IEmacsPlusCommandDefinitionIds.SET_MARK, null, editor);
mark = getMark(editor);
} catch (Exception e) {
}
}
selectAndReveal(editor,offset,mark);
// set mark flag if we're back at ground zero, else clear
setFlagMark(getCurrentSelection(editor).getLength() == 0);
return NO_OFFSET;
}
}