/**
* Copyright (c) 2009, 2013 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.minibuffer;
//import static com.mulgasoft.emacsplus.EmacsPlusUtils.getPreferenceStore;
//import static com.mulgasoft.emacsplus.preferences.PrefVars.SEARCH_EXIT_OPTION;
import java.util.Stack;
import java.util.regex.Pattern;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.text.IFindReplaceTargetExtension3;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.MarkSelection;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.source.ISourceViewer;
//import org.eclipse.jface.util.IPropertyChangeListener;
//import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.texteditor.ITextEditor;
import com.mulgasoft.emacsplus.EmacsPlusUtils;
import com.mulgasoft.emacsplus.KillRing;
import com.mulgasoft.emacsplus.MarkUtils;
import com.mulgasoft.emacsplus.RegexpRingBuffer;
import com.mulgasoft.emacsplus.RegexpRingBuffer.RegexpRingBufferElement;
import com.mulgasoft.emacsplus.RingBuffer;
import com.mulgasoft.emacsplus.RingBuffer.IRingBufferElement;
//import com.mulgasoft.emacsplus.preferences.PrefVars;
import com.mulgasoft.emacsplus.preferences.PrefVars.SEOptions;
/**
* Search minibuffer handles Ctrl and Alt
*
* @author Mark Feber - initial API and implementation
*/
public abstract class SearchMinibuffer extends HistoryMinibuffer {
private final static String WORD_EXP = "\\W*$*\\W*\\w*"; //$NON-NLS-1$
private final static String LINE_EXP = ".*$"; //$NON-NLS-1$
protected final static String KOLON = ": "; //$NON-NLS-1$
// Used to work around bug in org.eclipse.jface.text.FindReplaceDocumentAdapter
public final static String REGEX_BOL = "^"; //$NON-NLS-1$
public final static String REGEX_BOL_HACK = "^[\\s\\S]"; //$NON-NLS-1$
public final static String REGEX_EOL = "$"; //$NON-NLS-1$
public final static String REGEX_EOL_HACK = "$[\\s\\S]"; //$NON-NLS-1$
private static final String CASE_SENSITIVE = "Case_Sensitive_Message"; //$NON-NLS-1$
private static final String CASE_INSENSITIVE = "Case_Insensitive_Message"; //$NON-NLS-1$
// TODO preferences?
// Alts
protected static final char YANK = 'y';
protected static final char CASE = 'c';
// Ctrls
protected static final char EOL = 'j';
protected static final char WORD = 'w';
protected static final char LINEorYANK = 'y';
protected static final char CM_DEL= 'w';
protected static final char CM_ADD= 'y';
protected static final char CANCEL = 'g';
protected static final char CTRL_QUOTE = 'q';
// position of the last upper case character
private int currentCasePos = -1;
private int wrapPosition = -1;
private boolean found = false;
private boolean forward = true;
private boolean incrFind = true;
// in widget offsets
private int startOffset = 0;
private int searchOffset = 0;
// in doc offset
private int markOffset = 0;
protected static final int WRAP_INDEX = -1;
private boolean regexp = false;
private boolean quoting = false;
private boolean searching = false;
// if true, interpret C-y and M-y sub-commands as gnu yank commands
private boolean gnuYankCommands = false;
private boolean wasYanked = false;
private boolean yanked = false;
/**
* Record manual case sensitivity state; override text input case if not OFF
*/
enum Case_Sensitive {
/** No manual case sensitivity has been set */
OFF,
/** Search string should be used in a case sensitive manner */
SENSITIVE,
/** Search string should be used in a case insensitive manner */
INSENSITIVE
};
// command driven case sensitivity state
Case_Sensitive toggleCase = Case_Sensitive.OFF;
private static SEOptions search_exit_option = SEOptions.t;
// private static SEOptions search_exit_option = PrefVars.SEOptions.valueOf(EmacsPlusUtils.getPreferenceString(SEARCH_EXIT_OPTION.getPref()));
//
// static {
// listen for changes in the property store
// getPreferenceStore().addPropertyChangeListener(
// new IPropertyChangeListener() {
// public void propertyChange(PropertyChangeEvent event) {
// if (SEARCH_EXIT_OPTION.getPref().equals(event.getProperty())) {
// search_exit_option = PrefVars.SEOptions.valueOf((String)event.getNewValue());
// }
// }
// }
// );
// }
/**
*/
public SearchMinibuffer() {
super();
}
public SearchMinibuffer(boolean forward, boolean regexp) {
super();
setRegexp(regexp);
setForward(forward);
}
/**
* Get the state of the manual case sensitivity setting.
* @see com.mulgasoft.emacsplus.minibuffer.SearchMinibuffer.Case_Sensitive
*
* @return the Case_Sensitive setting
*/
Case_Sensitive isToggleCase() {
return toggleCase;
}
/**
* Set whether to force case insensitive search
*
* @param caseState the case state of the search string
*/
void toggleCase(boolean caseState) {
if (toggleCase == Case_Sensitive.OFF) {
toggleCase = (caseState ? Case_Sensitive.INSENSITIVE : Case_Sensitive.SENSITIVE);
} else {
toggleCase = (toggleCase == Case_Sensitive.SENSITIVE ? Case_Sensitive.INSENSITIVE : Case_Sensitive.SENSITIVE);
}
EmacsPlusUtils.showMessage(getEditor(),(toggleCase == Case_Sensitive.SENSITIVE ? CASE_SENSITIVE : CASE_INSENSITIVE), false);
}
/**
* If user has explicitly chosen a case sensitivity, return that, else rely on super
*
* @see com.mulgasoft.emacsplus.minibuffer.SearchMinibuffer#isCaseSensitive()
*/
protected boolean isCaseSensitive() {
boolean result;
if (toggleCase != Case_Sensitive.OFF) {
result = toggleCase == Case_Sensitive.SENSITIVE;
} else {
result = caseCaseSensitive();
}
return result;
}
private boolean caseCaseSensitive() {
return currentCasePos != -1;
}
/**
* If true, yank from kill buffer onto search string
*
* @return the gnuSubCommand state
*/
protected boolean isGnuYankCommands() {
return gnuYankCommands;
}
/**
* Set whether to yank from kill buffer onto search string
*
* @param gnuSubCommands state; if true, then enable
*/
protected void setGnuSubCommands(boolean gnuSubCommands) {
this.gnuYankCommands = gnuSubCommands;
}
/**
* @return the quoting
*/
protected boolean isQuoting() {
return quoting;
}
/**
* @param quoting the quoting to set
*/
protected void setQuoting(boolean quoting) {
this.quoting = quoting;
}
/**
* @return the searching
*/
protected boolean isSearching() {
return searching;
}
/**
* @param searching the searching to set
*/
protected void setSearching(boolean searching) {
this.searching = searching;
}
/**
* @return the incrFind
*/
protected boolean isIncrFind() {
return incrFind;
}
/**
* @param incrFind the incrFind to set
*/
protected void setIncrFind(boolean incrFind) {
this.incrFind = incrFind;
}
protected void setForward(boolean val){
forward = val;
}
protected boolean isForward(){
return forward;
}
/**
* @return the regexp state
*/
protected boolean isRegexp() {
return regexp;
}
/**
* @param regexp is true if working with regexps
*/
public void setRegexp(boolean regexp) {
this.regexp = regexp;
}
protected void setWrapPosition() {
if (wrapPosition == WRAP_INDEX) {
wrapPosition = getSearchStates().size();
} else {
wrapPosition = WRAP_INDEX;
}
}
protected boolean isWrapped() {
return wrapPosition != -1;
}
private void setWasYanked(boolean val) {
wasYanked = val;
}
private void setYanked(boolean val) {
yanked = val;
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#executeResult(org.eclipse.ui.texteditor.ITextEditor, java.lang.Object)
*/
protected boolean executeResult(ITextEditor editor, Object commandResult) {
if (commandResult != null) {
addToHistory((String)commandResult, getRXString());
}
return true;
}
public boolean beginSession(ITextEditor editor, IWorkbenchPage page, ExecutionEvent event) {
try {
setSearching(true);
boolean result = super.beginSession(editor, page, event);
if (result) {
setStartOffset(getTextWidget().getCaretOffset());
setMarkOffset(MarkUtils.getCursorOffset(editor));
setSearchOffset(getStartOffset());
}
return result;
} finally {
setSearching(false);
}
}
/**
* Perform incremental search on minibuffer string
*
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#addIt(org.eclipse.swt.events.VerifyEvent)
*/
protected void addIt(VerifyEvent event) {
addIt(event,true);
}
protected void addIt(VerifyEvent event, boolean searcher) {
char c = event.character;
saveState();
super.addIt(event); // add text to minibuffer display
regAddit(event); // add text to regexp string
if (searcher) {
if (addCheckCaseRegexp() && !isCaseSensitive() && Character.isUpperCase(c) && Character.toLowerCase(c) != c) {
currentCasePos = getMBString().length();
}
findNext(getSearchString(),true);
}
}
protected void addIt(String addStr) {
String adder = addStr;
saveState();
if (!isCaseSensitive() && addStr != null) {
adder = addStr.toLowerCase();
}
super.addIt(adder);
regQAddit(adder);
}
private boolean addCheckCaseRegexp() {
boolean result = true;
// ignore quoted characters in regexp
if (isRegexp() && getMB().charAt(-2) == '\\' && getMB().charAt(-3) != '\\') {
result = false;
}
return result;
}
// for use on whole string replacement
protected void checkCasePos(String str) {
currentCasePos = -1;
if (str != null) {
if (isRegexp()) {
MinibufferImpl mb = getMB();
boolean isQuote = false;
for (int i = 0; i< mb.getLength(); i++) {
char c;
if (isQuote) {
isQuote = false;
continue;
} else {
if ((c = mb.charAt(i)) == '\\') {
isQuote = true;
} else if (Character.isUpperCase(c) && Character.toLowerCase(c) != c) {
currentCasePos = i;
}
}
}
} else if (!str.equals(str.toLowerCase())) {
currentCasePos = getMBLength();
}
}
}
protected String normalizeString(String message) {
return EmacsPlusUtils.normalizeString(message,-1);
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#getMinibufferPrefix()
*/
@Override
protected String getMinibufferPrefix() {return EMPTY_STR;}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#handlesAlt()
*/
@Override
protected boolean handlesAlt() {
return true;
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#handlesCtrl()
*/
@Override
protected boolean handlesCtrl() {
return true;
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#handleKey(org.eclipse.swt.events.VerifyEvent)
*/
@Override
protected void handleKey(VerifyEvent event) {
boolean isQuoting = quoting;
try {
if (isQuoting) {
event.data = QUOTING;
}
if (event.keyCode != SWT.ALT) {
// ignore naked alt
setYanked(false);
}
setSearching(true);
super.handleKey(event);
} finally {
if (isQuoting){
quoting = false;
}
setWasYanked(yanked);
setSearching(false);
}
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#noCharEvent(org.eclipse.swt.events.VerifyEvent)
*/
protected void noCharEvent(VerifyEvent event) {
switch (event.keyCode) {
// remove minimal support for in line editing
case SWT.HOME:
case SWT.END:
case SWT.ARROW_LEFT:
case SWT.ARROW_RIGHT:
case SWT.PAGE_DOWN:
case SWT.PAGE_UP:
// Since we've disabled the key filter force the action by
// disabling the key, and calling the command directly
// since Mac doesn't handle simple resendEvent well
event.doit = false;
ITextEditor ed= this.getEditor();
leave();
executeBinding(ed, event.stateMask, event);
break;
default:
super.noCharEvent(event);
break;
}
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#charEvent(org.eclipse.swt.events.VerifyEvent)
*/
@Override
protected void charEvent(VerifyEvent event) {
switch (event.character) {
case 0x1B: // ESC - leave, but re-send for processing
super.charEvent(event);
this.resendEvent(event);
break;
default:
super.charEvent(event);
}
}
protected boolean dispatchCtrl(VerifyEvent event) {
return dispatchCtrl(event,true);
}
protected boolean dispatchCtrl(VerifyEvent event, boolean search) {
boolean result = false;
//
String regExp = null;
if (quoting) {
switch (event.character) {
// ^J == current eol character(s)
case CR:
case LF:
addIt(getEol());
break;
default:
addIt(event);
}
if (search) {
findNext(getSearchString(),true);
}
updateStatusLine(getMBString());
result = true;
} else {
switch (event.keyCode) {
case EOL:
addIt(getEol());
if (search) {
findNext(getSearchString(),true);
}
updateStatusLine(getMBString());
result = true;
break;
case LINEorYANK: // C-y
switch(search_exit_option) {
case t:
if (isGnuYankCommands()) {
// yank from kill buffer onto search string
yankStr(false);
result = true;
break;
}
// yank rest of line from buffer onto end of search string & continue
regExp = LINE_EXP;
break;
case nil:
setQuoting(true);
// result = true;
SearchMinibuffer.this.handleKey(event);
break;
case disable:
result = super.dispatchCtrl(event);
break;
}
case WORD: // yank word from buffer onto end of search string & continue
switch(search_exit_option) {
case t:
if (isFound() || getMBLength() == 0) {
if (regExp == null) {
regExp = WORD_EXP;
}
StyledText w = getTextWidget();
int caretOff = w.getCaretOffset();
int slen = getMBLength();
try {
w.setRedraw(false);
int catPos = caretOff + (isForward() ? 0 : slen);
IFindReplaceTarget target = getTarget();
// always search forward when concatenating
int fpos = ((IFindReplaceTargetExtension3)target).findAndSelect(catPos, regExp, true, false, false, true);
if (fpos > -1) {
addIt(target.getSelectionText());
setSearchOffset(caretOff + (isForward() ? -slen : getMBLength()));
findNext(getSearchString(), true);
updateStatusLine(getMBString());
}
} catch (Exception e) {
popSearchState();
} finally {
w.setRedraw(true);
}
result = true;
break;
}
case nil:
setQuoting(true);
// result = true;
SearchMinibuffer.this.handleKey(event);
break;
case disable:
result = super.dispatchCtrl(event);
break;
}
break;
case CANCEL:
if (!cancelSearch()){
event.doit = false;
}
result = true;
break;
case CTRL_QUOTE:
quoting = true;
result = true;
break;
default:
result = super.dispatchCtrl(event);
break;
}
}
return result;
}
protected boolean dispatchAlt(VerifyEvent event) {
boolean result = false;
switch (event.keyCode) {
case YANK: // yank (or yank pop) killed text onto end of search string and search for it.
switch(search_exit_option) {
case t:
yankStr(isGnuYankCommands());
result = true;
event.doit = false;
break;
case nil:
quoting = true;
result = true;
break;
case disable:
result = super.dispatchCtrl(event);
break;
}
break;
default:
result = super.dispatchAlt(event);
}
return result;
}
/**
* Yank or Yank Pop onto search string
* if yank pop, then remove previous yank
*
* @param popit true if popping
*/
private void yankStr(boolean popit) {
String killStr = null;
if (popit) {
if (wasYanked) {
popSearchState(); // remove previous yank
killStr = KillRing.getInstance().yankPop();
}
} else {
killStr = KillRing.getInstance().yank();
}
if (killStr != null) {
addIt(killStr);
findNext(getSearchString(),true);
updateStatusLine(getMBString());
setYanked(true);
} else {
beep();
}
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#dispatchAltCtrl(org.eclipse.swt.events.VerifyEvent)
*/
protected boolean dispatchAltCtrl(VerifyEvent event) {
return dispatchAltCtrl(event,true);
}
/**
* Allow single character operations on an existing search string
*
* @param event
* @param search - true if we should search after operation
* @return true if event handled
*/
protected boolean dispatchAltCtrl(VerifyEvent event, boolean search) {
// allows editing of history/yanked search string
boolean result = false;
switch (event.keyCode) {
case CM_DEL:
// remove the last character and search
super.backSpaceChar(event);
getRX().bsChar();
if (search) {
findNext(getSearchString(),true);
}
updateStatusLine(getMBString());
result = true;
break;
case CM_ADD:
StyledText w = getTextWidget();
int catPos = w.getCaretOffset() + (isForward() ? 0 : getMBLength());
String text = w.getText(catPos, catPos);
if (w.getLineDelimiter().contains(text)) {
text = w.getLineDelimiter();
}
addIt(text);
if (search) {
findNext(getSearchString(),true);
}
updateStatusLine(getMBString());
result = true;
break;
}
if (!result && hasBinding(event)) {
// exit search and execute the command
ITextEditor ed = getEditor();
leave();
executeBinding(ed,event);
result = true;
}
return result;
}
protected boolean handlesTab() {
return true;
}
protected boolean dispatchTab(VerifyEvent event) {
addIt(event);
return true;
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#leave(boolean)
*/
@Override
protected void leave(boolean closeDialog) {
leave();
}
protected void leave() {
StyledText text = getTextWidget();
if (text != null && !text.isDisposed()) {
leave(text.getCaretOffset());
} else {
super.leave(true);
}
}
protected void leave(int off) {
leave(off,0, true);
}
protected void leave(int offset, int len, boolean isWidget) {
addToHistory();
ISourceViewer viewer = getViewer();
if (viewer != null) {
int off = (isWidget ? MarkUtils.widget2ModelOffset(getViewer(), offset) : offset);
viewer.setSelectedRange(off, len);
viewer.revealRange(off,0);
}
super.leave(true);
}
protected boolean cancelSearch() {
leave();
return true;
}
protected String getSelectionText() {
String result = null;
IFindReplaceTarget target = getTarget();
if (target != null) {
result = target.getSelectionText();
}
return result;
}
protected Point getSelection() {
Point result = null;
IFindReplaceTarget target = getTarget();
if (target != null) {
result = target.getSelection();
}
return result;
}
protected boolean findNext(String searchStr) {
return findNext(searchStr,false);
}
private boolean searchEnabled = true;
/**
* @return the searchEnabled
*/
protected boolean isSearchEnabled() {
return searchEnabled;
}
/**
* @param searchEnabled the searchEnabled to set
*/
protected void setSearchEnabled(boolean searchEnabled) {
this.searchEnabled = searchEnabled;
}
/*
* In order to work around a bug in Eclipse, where it doesn't deal properly with
* the return result from the Matcher in FindReplaceDocumentAdapter, we need to
* provide special processing here and in SearchReplace
*/
/**
* true if the search string consists solely of a regex line delimiter (^|$)
*/
private boolean regexLD = false;
/**
* @return the regexLD
*/
protected boolean isRegexLD() {
return regexLD;
}
/**
* @param regexLD the regexLD to set
*/
private void setRegexLD(boolean regLD) {
this.regexLD = regLD;
}
/**
* Find the next instance of the search string in the buffer
*
* @param searchStr
* @param addit - true when just added text to search string
* @return true if we found a match
*/
protected boolean findNext(String searchStr,boolean addit) {
if (isSearchEnabled()) {
setRegexLD(false);
StyledText text = getTextWidget();
IFindReplaceTarget target = getTarget();
if (text != null && target != null) {
int searchIndex = getSearchOffset();
if (searchIndex != WRAP_INDEX) {
Point p = getSelection();
// if not building the string (or wrapping) search from caret position
if (!addit) {
if (isFound() && p != null) {
searchIndex = p.x;
if (isForward()) {
// increment index by (actual or implicit) selection width
searchIndex += (p.y == 0 ? getEol().length() : p.y);
}
} else {
// Cannot use target.getSelection since that does not return which side of the
// selection the caret is on.
searchIndex = text.getCaretOffset();
}
}
if (!forward && p != null) {
// if at beginning of line, back up by full eol, else just 1
searchIndex -= ((p.x == text.getOffsetAtLine(text.getLineAtOffset(p.x))) ? getEol().length() : 1);
}
}
int index = findTarget(target,searchStr,searchIndex, forward);
boolean justFound = (index != WRAP_INDEX);
if (isFound() && !justFound && (addit && !isRegexp())) {
beep();
} else if (!addit) {
setSearchOffset(index);
}
setFound(justFound);
}
}
return isFound();
}
protected int findTarget(IFindReplaceTarget target, String searchStr, int searchIndex, boolean forward) {
int result = WRAP_INDEX;
StyledText text = getTextWidget();
try {
text.setRedraw(false);
IFindReplaceTargetExtension3 target3 = (IFindReplaceTargetExtension3) target;
// index is widget offset (or -1)
try {
String searcher = searchStr;
// syntax check
if (isRegexp()) {
Pattern.compile(searchStr);
// Work around bug in org.eclipse.jface.text.FindReplaceDocumentAdapter
// - remember that it is a line delimiter search
// and use a hacked search string instead
if (searcher.length() == 1 ) {
if (REGEX_BOL.equals(searcher)) {
searcher = REGEX_BOL_HACK;
setRegexLD(true);
} else if (REGEX_EOL.equals(searcher)) {
searcher = REGEX_EOL_HACK;
setRegexLD(true);
}
}
}
result = target3.findAndSelect(searchIndex, searcher, forward, isCaseSensitive(), false, isRegexp());
} catch (Exception e) {
}
// Eclipse fails on edge case (with '\s' search string) when searching in reverse direction
// it decides that it has a bad location in org.eclipse.jface.text.TextViewer.validateSelectionRange(int[])
// so, while result is ~EOF, the selection has not been moved, so regexp-isearch-backward will get stuck
// at the beginning of the file instead of looping back over the end
Point p = target.getSelection();
if (!forward) {
if (result > 0 && (result != p.x && result != (p.x + p.y))) {
// get the last valid position (note we won't get here if text does not end with delimiter)
int last = text.getCharCount() - text.getLineDelimiter().length();
if (last < result) {
// validate last position and if it passes use it as the search result
int check = MarkUtils.widget2ModelOffset(getViewer(), last);
try {
getDocument().getLineInformationOfOffset(check);
result = last;
p = new Point(result,0);
} catch (BadLocationException e) {
// validation failed, just stick with broken behavior
}
}
}
text.setSelectionRange(p.x + p.y, -p.y);
}
if (isRegexLD()) {
text.setSelectionRange(p.x, 0);
}
text.showSelection();
} finally {
text.setRedraw(true);
}
return result;
}
// ISelectionChangedListener
public void selectionChanged(SelectionChangedEvent event) {
boolean ignore= false;
ISelection selection= event.getSelection();
ITextSelection textSelection = ((selection instanceof ITextSelection) ? (ITextSelection)selection : null);
if (textSelection != null) {
Point range= getSelection();
ignore= textSelection.getOffset() + textSelection.getLength() == range.x + range.y;
} else if (selection instanceof MarkSelection) {
// ignore mark selections as this is a side-effect of leaving the search
ignore = true;
}
if (!isSearching() && !ignore) {
// leave with affecting the selection position
super.leave(false);
}
}
// ITextListener
/*
* @see ITextListener#textChanged(TextEvent)
*/
public void textChanged(TextEvent event) {
if (event.getDocumentEvent() != null) {
// leave();
}
}
/**
* @return the found state
*/
protected boolean isFound() {
return found;
}
/**
* @param found the new found state
*/
protected void setFound(boolean found) {
this.found = found;
}
/**
* @return the startOffset
*/
protected int getStartOffset() {
return startOffset;
}
protected void setStartOffset(int offset) {
startOffset = offset;
}
/**
* @return the searchOffset
*/
protected int getSearchOffset() {
return searchOffset;
}
/**
* @param searchOffset the searchOffset to set
*/
protected void setSearchOffset(int searchOffset) {
this.searchOffset = searchOffset;
}
/**
* @return the markOffset
*/
protected int getMarkOffset() {
return markOffset;
}
/**
* @param markOffset the markOffset to set
*/
protected void setMarkOffset(int markOffset) {
this.markOffset = markOffset;
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#initializeBuffer(org.eclipse.ui.texteditor.ITextEditor, org.eclipse.ui.IWorkbenchPage)
*/
@Override
protected boolean initializeBuffer(ITextEditor editor, IWorkbenchPage page) {
return true;
}
/**** Search State Stack ****/
private Stack<SearchState> searchState;
private Stack<SearchState> getSearchStates() {
if (searchState == null) {
searchState = new Stack<SearchState>();
}
return searchState;
}
/**
* Store the search result.
*/
protected void saveState() {
saveState(false);
}
/**
* Store the search state unless the previous state was from the history
*
* @param fromHistory - flag where current state comes from
*/
protected void saveState(boolean fromHistory) {
// delay once if current element is from history ring buffer
// as we don't search immediately (unlike yank, or ^y & ^w)
if (!getHistoryState()) {
getSearchStates().push(new SearchState());
}
setHistoryState(fromHistory);
}
// true if last search string was supplied by history buffer
private boolean historyState = false;
private boolean getHistoryState() {
return historyState;
}
private void setHistoryState(boolean state) {
historyState = state;
}
/**
* Pop the search stack to previous state
*/
protected boolean popSearchState() {
boolean result = true;
StyledText text= getTextWidget();
if (text == null || text.isDisposed()) {
result = false;;
} else {
SearchState searchResult= null;
if (!getSearchStates().empty())
searchResult= getSearchStates().pop();
if (searchResult == null) {
result = false;
} else {
searchResult.restoreState(text);
updateStatusLine(getMBString());
}
}
setHistoryState(false);
return result;
}
/**
* If not in found state, pop the state stack until we are (or it's empty)
*
* @return false if already in found state, else true
*/
protected boolean goToFoundState() {
boolean result = false;
if (!isFound() && popSearchState()) {
while (!isFound() && popSearchState()) {
;
}
result = true;
}
return result;
}
/**
* Data structure for a search result.
*/
private class SearchState {
String mb, rx;
int selection, length, index, findLength, regLength;
boolean sfound, sforward;
/**
* Creates a new search result data object and fills
* it with the current values of this target.
*/
public SearchState() {
// WIDGET OFFSET
Point p= getTarget().getSelection();
selection= p.x;
length= p.y;
index= getSearchOffset();
mb = getMBString();
rx = getRXString();
findLength= getMBLength();
regLength = getRXLength();
sfound= found;
sforward= forward;
checkState(this);
}
/**
* Just save one version of the string(s) at the top
*
* @param currentState
*/
private void checkState(SearchState currentState) {
SearchState prevState= null;
if (!getSearchStates().empty())
prevState= getSearchStates().peek();
if (prevState != null) {
if (prevState.mb != null && prevState.mb.equals(currentState.mb)) {
prevState.mb = null;
}
if (prevState.rx != null && prevState.rx.equals(currentState.rx)) {
prevState.rx = null;
}
}
}
public void restoreState(StyledText text) {
// WIDGET OFFSET
text.setSelectionRange(selection, length);
text.showSelection();
// relies on the contents of the StringBuilder
if (mb != null) {
getMB().init(mb);
}
if (rx != null) {
getRX().init(rx);
}
setMBLength(findLength);
setRXLength(regLength);
setSearchOffset(index);
found= sfound;
forward= sforward;
// Recalculate the indices
if (findLength <= currentCasePos)
currentCasePos= -1;
if (getSearchStates().size() < wrapPosition)
setWrapPosition();
}
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.WithMinibuffer#initMinibuffer(java.lang.String)
*/
@Override
protected void initMinibuffer(String newString) {
super.initMinibuffer(newString);
if (isRegexp()) {
getRX().init(newString);
}
}
/* ***************** regexp string buffer *******************/
private MinibufferImpl regexpStringImpl;
protected MinibufferImpl getRX() {
if (regexpStringImpl == null){
regexpStringImpl = new MinibufferImpl(getDocument());
}
return regexpStringImpl;
}
protected void regAddit(VerifyEvent event) {
getRX().addChar(event.character);
}
protected void regQAddit(String addStr) {
String str = (isRegexp() ? Pattern.quote(addStr) : addStr);
getRX().append(str);
}
protected String getRXString() {
return getRX().getString();
}
protected int getRXLength() {
return getRX().getLength();
}
protected void setRXLength(int length) {
getRX().setLength(length);
}
protected String getSearchString() {
String result;
if (isRegexp()) {
result = getRXString();
} else {
result = super.getSearchString();
}
return result;
}
/**
* Add the strings to the ring buffer
*
* @param searchStr the search string
* @param regexpStr the regexp version of the string; can be null
* @return the ring buffer element
*/
protected IRingBufferElement<String> addToHistory(String searchStr, String regexpStr) {
IRingBufferElement<String> result = null;
if (searchStr != null && searchStr.length() > 0) {
result = super.addToHistory(searchStr);
if (result != null && result instanceof RegexpRingBufferElement) {
((RegexpRingBufferElement) result).setRegexp(regexpStr);
}
}
return result;
}
protected IRingBufferElement<String> addToHistory(String historyStr) {
return addToHistory(historyStr, historyStr);
}
protected IRingBufferElement<String> addToHistory() {
return addToHistory(getMBString(), getRXString());
}
/**
* @see com.mulgasoft.emacsplus.minibuffer.HistoryMinibuffer#replaceFromHistory(com.mulgasoft.emacsplus.RingBuffer.IRingBufferElement)
*/
@Override
protected void replaceFromHistory(IRingBufferElement<?> rbe) {
saveState(true);
super.replaceFromHistory(rbe);
if (rbe != null && rbe instanceof RegexpRingBufferElement) {
getRX().init(((RegexpRingBufferElement)rbe).getRegexp());
}
checkCasePos(getMBString());
}
/**** Local RingBuffers: use lazy initialization holder class idiom ****/
/**
* @see com.mulgasoft.emacsplus.minibuffer.HistoryMinibuffer#getHistoryRing()
*/
@Override
@SuppressWarnings("unchecked")
protected RingBuffer<String> getHistoryRing() {
return (isRegexp() ? RegexpRing.ring : SearchRing.ring);
}
private static class SearchRing {
static final RingBuffer<String> ring = new RingBuffer<String>();
}
private static class RegexpRing {
static final RegexpRingBuffer ring = new RegexpRingBuffer();
}
}