fakevim: refactor searching

Less random jumps now.
This commit is contained in:
hjk
2010-05-18 18:24:00 +02:00
parent 2f99706031
commit 7e8c345a64

View File

@@ -250,6 +250,19 @@ struct Register
RangeMode rangemode;
};
struct SearchData
{
SearchData() { init(); }
void init() { forward = true; mustMove = true; highlightMatches = true;
highlightCursor = true; }
QString needle;
bool forward;
bool mustMove;
bool highlightMatches;
bool highlightCursor;
};
Range::Range()
@@ -357,6 +370,12 @@ public:
return m_key == Key_Return;
}
bool isEscape() const
{
return isKey(Key_Escape) || isKey(27) || isControl('c')
|| isControl(Key_BracketLeft);
}
bool is(int c) const
{
return m_xkey == c && (m_modifiers == 0 || m_modifiers == Qt::ShiftModifier);
@@ -442,6 +461,26 @@ void Inputs::parseFrom(const QString &str)
}
}
class History
{
public:
History() : m_index(0) {}
void append(const QString &item)
{ //qDebug() << "APP: " << item << m_items;
m_items.removeAll(item);
m_items.append(item); m_index = m_items.size() - 1; }
void down() { m_index = qMin(m_index + 1, m_items.size()); }
void up() { m_index = qMax(m_index - 1, 0); }
//void clear() { m_items.clear(); m_index = 0; }
void restart() { m_index = m_items.size(); }
QString current() const { return m_items.value(m_index, QString()); }
QStringList items() const { return m_items; }
private:
QStringList m_items;
int m_index;
};
// Mappings for a specific mode.
class ModeMapping : public QList<QPair<Inputs, Inputs> >
{
@@ -544,7 +583,7 @@ public:
void finishMovement(const QString &dotCommand = QString());
void finishMovement(const QString &dotCommand, int count);
void resetCommandMode();
void search(const QString &needle, bool forward, bool incSearch = false);
void search(const SearchData &sd);
void highlightMatches(const QString &needle);
void stopIncrementalFind();
@@ -749,9 +788,6 @@ public:
Input m_semicolonType; // 'f', 'F', 't', 'T'
QString m_semicolonKey;
// history for '/'
QString lastSearchString() const;
// visual line mode
void enterVisualMode(VisualMode visualMode);
void leaveVisualMode();
@@ -786,6 +822,7 @@ public:
QVector<CursorPosition> m_jumpListRedo;
QList<QTextEdit::ExtraSelection> m_searchSelections;
QTextCursor m_searchCursor;
QString m_oldNeedle;
bool handleExCommandHelper(const ExCommand &cmd); // Returns success.
@@ -816,8 +853,6 @@ public:
{
inReplay = false;
inputTimer = -1;
searchHistoryIndex = 0;
commandHistoryIndex = 0;
}
// Input.
@@ -828,11 +863,11 @@ public:
QString dotCommand;
bool inReplay; // true if we are executing a '.'
// Histories.
QStringList searchHistory;
int searchHistoryIndex;
QStringList commandHistory;
int commandHistoryIndex;
// History for searches.
History searchHistory;
// History for :ex commands.
History commandHistory;
QHash<int, Register> registers;
@@ -1240,10 +1275,8 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
setPosition(qMin(anchor(), position()));
enterExMode();
m_currentMessage.clear();
m_commandPrefix = QChar();
m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
g.commandHistory.append(QString());
g.commandHistoryIndex = g.commandHistory.size() - 1;
//g.commandHistory.append(QString());
updateMiniBuffer();
updateCursor();
return;
@@ -1386,6 +1419,14 @@ void FakeVimHandler::Private::resetCommandMode()
void FakeVimHandler::Private::updateSelection()
{
QList<QTextEdit::ExtraSelection> selections = m_searchSelections;
if (!m_searchCursor.isNull()) {
QTextEdit::ExtraSelection sel;
sel.cursor = m_searchCursor;
sel.format = m_searchCursor.blockCharFormat();
sel.format.setForeground(Qt::white);
sel.format.setBackground(Qt::black);
selections.append(sel);
}
if (isVisualMode()) {
QTextEdit::ExtraSelection sel;
sel.cursor = m_tc;
@@ -1477,11 +1518,10 @@ void FakeVimHandler::Private::updateMiniBuffer()
msg = "-- INSERT --";
} else if (m_mode == ReplaceMode) {
msg = "-- REPLACE --";
} else {
if (m_subsubmode == SearchSubSubMode)
msg += m_commandPrefix;
else if (m_mode == ExMode)
msg += m_commandPrefix;
} else if (!m_commandPrefix.isEmpty()) {
//QTC_ASSERT(m_mode == ExMode || m_subsubmode == SearchSubSubMode,
// qDebug() << "MODE: " << m_mode << m_subsubmode);
msg = m_commandPrefix;
foreach (QChar c, m_commandBuffer) {
if (c.unicode() < 32) {
msg += '^';
@@ -1492,6 +1532,9 @@ void FakeVimHandler::Private::updateMiniBuffer()
}
if (!msg.isEmpty() && m_mode != CommandMode)
msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
} else {
QTC_ASSERT(m_mode == CommandMode && m_subsubmode != SearchSubSubMode, /**/);
msg = "-- COMMAND --";
}
emit q->commandBufferChanged(msg);
@@ -1630,7 +1673,7 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
{
EventResult handled = EventHandled;
if (input.isKey(Key_Escape) || input.isControl(Key_BracketLeft)) {
if (input.isEscape()) {
if (isVisualMode()) {
leaveVisualMode();
} else if (m_submode != NoSubMode) {
@@ -1760,16 +1803,15 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
finishMovement();
} else if (input.is(':')) {
enterExMode();
g.commandHistory.restart();
m_currentMessage.clear();
m_commandPrefix = input.asChar();
m_commandBuffer.clear();
if (isVisualMode())
m_commandBuffer = "'<,'>";
g.commandHistory.append(QString());
g.commandHistoryIndex = g.commandHistory.size() - 1;
updateMiniBuffer();
} else if (input.is('/') || input.is('?')) {
m_lastSearchForward = input.is('/');
g.searchHistory.restart();
if (hasConfig(ConfigUseCoreSearch)) {
// re-use the core dialog.
m_findPending = true;
@@ -1785,8 +1827,6 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
m_subsubmode = SearchSubSubMode;
m_commandPrefix = QLatin1Char(m_lastSearchForward ? '/' : '?');
m_commandBuffer = QString();
g.searchHistory.append(QString());
g.searchHistoryIndex = g.searchHistory.size() - 1;
updateCursor();
updateMiniBuffer();
}
@@ -1794,16 +1834,23 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
m_subsubmode = BackTickSubSubMode;
} else if (input.is('#') || input.is('*')) {
// FIXME: That's not proper vim behaviour
m_tc.select(QTextCursor::WordUnderCursor);
QString needle = "\\<" + m_tc.selection().toPlainText() + "\\>";
QTextCursor tc = m_tc;
tc.select(QTextCursor::WordUnderCursor);
QString needle = "\\<" + tc.selection().toPlainText() + "\\>";
g.searchHistory.append(needle);
m_lastSearchForward = input.is('*');
m_currentMessage.clear();
m_commandPrefix = QLatin1Char(m_lastSearchForward ? '/' : '?');
m_commandBuffer = needle;
updateMiniBuffer();
search(needle, m_lastSearchForward);
recordJump();
SearchData sd;
sd.needle = needle;
sd.forward = m_lastSearchForward;
sd.highlightCursor = false;
sd.highlightMatches = true;
search(sd);
//m_searchCursor = QTextCursor();
//updateSelection();
//updateMiniBuffer();
} else if (input.is('\'')) {
m_subsubmode = TickSubSubMode;
} else if (input.is('|')) {
@@ -1817,8 +1864,7 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
enterExMode();
m_currentMessage.clear();
m_commandBuffer = "'<,'>!";
g.commandHistory.append(QString());
g.commandHistoryIndex = g.commandHistory.size() - 1;
//g.commandHistory.append(QString());
updateMiniBuffer();
} else if (input.is('"')) {
m_submode = RegisterSubMode;
@@ -2137,12 +2183,13 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2)));
handleStartOfLine();
finishMovement();
} else if (input.is('n')) {
search(lastSearchString(), m_lastSearchForward);
recordJump();
} else if (input.is('N')) {
search(lastSearchString(), !m_lastSearchForward);
recordJump();
} else if (input.is('n') || input.is('N')) {
SearchData sd;
sd.needle = g.searchHistory.current();
sd.forward = input.is('n') ? m_lastSearchForward : !m_lastSearchForward;
sd.highlightCursor = false;
sd.highlightMatches = true;
search(sd);
} else if (isVisualMode() && (input.is('o') || input.is('O'))) {
int pos = position();
setPosition(anchor());
@@ -2409,8 +2456,7 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input)
{
if (input.isKey(Key_Escape) || input.isKey(27) || input.isControl('c') ||
input.isControl(Key_BracketLeft)) {
if (input.isEscape()) {
moveLeft(qMin(1, leftDist()));
setTargetColumn();
m_submode = NoSubMode;
@@ -2437,8 +2483,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
//const int key = input.key;
//const QString &text = input.text;
if (input.isKey(Key_Escape) || input.isKey(27) || input.isControl('c') ||
input.isControl(Key_BracketLeft)) {
if (input.isEscape()) {
if (isVisualBlockMode() && !m_lastInsertion.contains('\n')) {
leaveVisualMode();
joinPreviousEditBlock();
@@ -2603,13 +2648,13 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
EventResult FakeVimHandler::Private::handleExMode(const Input &input)
{
if (input.isKey(Key_Escape) || input.isControl('c')
|| input.isControl(Key_BracketLeft)) {
if (input.isEscape()) {
m_commandBuffer.clear();
enterCommandMode();
updateMiniBuffer();
} else if (input.isBackspace()) {
if (m_commandBuffer.isEmpty()) {
m_commandPrefix.clear();
enterCommandMode();
} else {
m_commandBuffer.chop(1);
@@ -2622,22 +2667,21 @@ EventResult FakeVimHandler::Private::handleExMode(const Input &input)
updateMiniBuffer();
} else if (input.isReturn()) {
if (!m_commandBuffer.isEmpty()) {
g.commandHistory.takeLast();
//g.commandHistory.takeLast();
g.commandHistory.append(m_commandBuffer);
handleExCommand(m_commandBuffer);
if (m_textedit || m_plaintextedit)
leaveVisualMode();
}
updateMiniBuffer();
} else if (input.isKey(Key_Up) || input.isKey(Key_PageUp)) {
if (g.commandHistoryIndex > 0) {
--g.commandHistoryIndex;
showBlackMessage(g.commandHistory.at(g.commandHistoryIndex));
}
g.commandHistory.up();
m_commandBuffer = g.commandHistory.current();
updateMiniBuffer();
} else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) {
if (g.commandHistoryIndex < g.commandHistory.size() - 1) {
++g.commandHistoryIndex;
showBlackMessage(g.commandHistory.at(g.commandHistoryIndex));
}
g.commandHistory.down();
m_commandBuffer = g.commandHistory.current();
updateMiniBuffer();
} else if (input.isKey(Key_Tab)) {
m_commandBuffer += QChar(9);
updateMiniBuffer();
@@ -2653,65 +2697,73 @@ EventResult FakeVimHandler::Private::handleExMode(const Input &input)
EventResult FakeVimHandler::Private::handleSearchSubSubMode(const Input &input)
{
if (input.isKey(Key_Escape) || input.isControl('c')
|| input.isControl(Key_BracketLeft)) {
if (input.isEscape()) {
m_commandBuffer.clear();
m_searchCursor = QTextCursor();
updateSelection();
enterCommandMode();
updateMiniBuffer();
} else if (input.isBackspace()) {
if (m_commandBuffer.isEmpty())
if (m_commandBuffer.isEmpty()) {
m_commandPrefix.clear();
m_searchCursor = QTextCursor();
enterCommandMode();
else
} else {
m_commandBuffer.chop(1);
}
updateMiniBuffer();
} else if (input.isKey(Key_Left)) {
if (!m_commandBuffer.isEmpty())
m_commandBuffer.chop(1);
updateMiniBuffer();
} else if (input.isReturn() && !hasConfig(ConfigIncSearch)) {
} else if (input.isReturn()) {
m_searchCursor = QTextCursor();
QString needle = m_commandBuffer;
if (!needle.isEmpty()) {
g.searchHistory.takeLast();
g.searchHistory.append(needle);
search(lastSearchString(), m_lastSearchForward);
recordJump();
if (!hasConfig(ConfigIncSearch)) {
SearchData sd;
sd.needle = needle;
sd.forward = m_lastSearchForward;
sd.highlightCursor = false;
sd.highlightMatches = true;
search(sd);
}
finishMovement(m_commandPrefix + needle + "\n");
}
finishMovement(m_commandPrefix + needle + "\n");
enterCommandMode();
highlightMatches(needle);
updateMiniBuffer();
} else if (input.isKey(Key_Up) || input.isKey(Key_PageUp)) {
// FIXME: This and the three cases below are wrong as vim
// takes only matching entries in the history into account.
if (g.searchHistoryIndex > 0) {
--g.searchHistoryIndex;
showBlackMessage(g.searchHistory.at(g.searchHistoryIndex));
}
g.searchHistory.up();
showBlackMessage(g.searchHistory.current());
} else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) {
if (g.searchHistoryIndex < g.searchHistory.size() - 1) {
++g.searchHistoryIndex;
showBlackMessage(g.searchHistory.at(g.searchHistoryIndex));
}
g.searchHistory.down();
showBlackMessage(g.searchHistory.current());
} else if (input.isKey(Key_Tab)) {
m_commandBuffer += QChar(9);
updateMiniBuffer();
} else if (input.isReturn() && hasConfig(ConfigIncSearch)) {
QString needle = m_commandBuffer;
enterCommandMode();
highlightMatches(needle);
updateMiniBuffer();
} else if (hasConfig(ConfigIncSearch)) {
QString needle = m_commandBuffer + input.text();
search(needle, m_lastSearchForward, true);
updateMiniBuffer();
recordJump();
} else if (!input.text().isEmpty()) {
m_commandBuffer += input.text();
updateMiniBuffer();
} else {
qDebug() << "IGNORED IN SEARCH MODE: " << input.key() << input.text();
return EventUnhandled;
}
if (hasConfig(ConfigIncSearch) && !input.isReturn() && !input.isEscape()) {
SearchData sd;
sd.needle = m_commandBuffer;
sd.forward = m_lastSearchForward;
sd.mustMove = false;
sd.highlightCursor = true;
sd.highlightMatches = false;
search(sd);
}
//else {
// qDebug() << "IGNORED IN SEARCH MODE: " << input.key() << input.text();
// return EventUnhandled;
//}
return EventHandled;
}
@@ -2967,7 +3019,7 @@ bool FakeVimHandler::Private::handleExHistoryCommand(const ExCommand &cmd)
QString info;
info += "# command history\n";
int i = 0;
foreach (const QString &item, g.commandHistory) {
foreach (const QString &item, g.commandHistory.items()) {
++i;
info += QString("%1 %2\n").arg(i, -8).arg(item);
}
@@ -3313,69 +3365,67 @@ static void vimPatternToQtPattern(QString *needle, QTextDocument::FindFlags *fla
//qDebug() << "NEEDLE " << needle0 << needle;
}
void FakeVimHandler::Private::search(const QString &needle0, bool forward,
bool incSearch)
void FakeVimHandler::Private::search(const SearchData &sd)
{
//showBlackMessage(QLatin1Char(forward ? '/' : '?') + needle0);
showBlackMessage(needle0);
CursorPosition origPosition = cursorPosition();
if (sd.needle.isEmpty())
return;
const bool incSearch = hasConfig(ConfigIncSearch);
QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
if (!forward)
if (!sd.forward)
flags |= QTextDocument::FindBackward;
QString needle = needle0;
QString needle = sd.needle;
vimPatternToQtPattern(&needle, &flags);
if (forward)
m_tc.movePosition(Right, MoveAnchor, 1);
const int oldLine = cursorLineInDocument() - cursorLineOnScreen();
int oldLine = cursorLineInDocument() - cursorLineOnScreen();
int startPos = position();
if (sd.mustMove)
sd.forward ? ++startPos : --startPos;
EDITOR(setTextCursor(m_tc));
if (EDITOR(find(needle, flags))) {
m_tc = EDITOR(textCursor());
m_tc.setPosition(m_tc.anchor());
// making this unconditional feels better, but is not "vim like"
if (oldLine != cursorLineInDocument() - cursorLineOnScreen())
scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2);
if (!incSearch)
highlightMatches(needle);
} else {
m_tc.setPosition(forward ? 0 : lastPositionInDocument());
EDITOR(setTextCursor(m_tc));
if (EDITOR(find(needle, flags))) {
m_tc = EDITOR(textCursor());
m_tc.setPosition(m_tc.anchor());
if (oldLine != cursorLineInDocument() - cursorLineOnScreen())
scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2);
m_searchCursor = QTextCursor();
QTextCursor tc = m_tc.document()->find(needle, startPos, flags);
if (tc.isNull()) {
int startPos = sd.forward ? 0 : lastPositionInDocument();
tc = m_tc.document()->find(needle, startPos, flags);
if (tc.isNull()) {
if (!incSearch) {
if (forward)
showRedMessage(FakeVimHandler::tr("search hit BOTTOM, continuing at TOP"));
else
showRedMessage(FakeVimHandler::tr("search hit TOP, continuing at BOTTOM"));
highlightMatches(needle);
}
} else {
if (!incSearch)
highlightMatches(QString());
setCursorPosition(origPosition);
if (!incSearch)
showRedMessage(FakeVimHandler::tr("Pattern not found: ") + needle);
}
updateSelection();
return;
}
if (!incSearch) {
QString msg = sd.forward
? FakeVimHandler::tr("search hit BOTTOM, continuing at TOP")
: FakeVimHandler::tr("search hit TOP, continuing at BOTTOM");
showRedMessage(msg);
}
}
if (incSearch) {
QTextEdit::ExtraSelection sel;
sel.cursor = m_tc;
sel.format = m_tc.blockCharFormat();
sel.format.setForeground(Qt::white);
sel.format.setBackground(Qt::black);
sel.cursor.setPosition(m_tc.position(), MoveAnchor);
sel.cursor.setPosition(m_tc.position() + needle.size(), KeepAnchor);
QList<QTextEdit::ExtraSelection> selections = m_searchSelections;
selections.append(sel);
emit q->selectionChanged(selections);
// Set Cursor.
tc.setPosition(qMin(tc.position(), tc.anchor()), MoveAnchor);
tc.clearSelection();
m_tc = tc;
EDITOR(setTextCursor(m_tc));
// Making this unconditional feels better, but is not "vim like".
if (oldLine != cursorLineInDocument() - cursorLineOnScreen())
scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2);
if (incSearch && sd.highlightCursor) {
m_searchCursor = m_tc;
m_searchCursor.setPosition(m_tc.position(), MoveAnchor);
m_searchCursor.setPosition(m_tc.position() + needle.size(), KeepAnchor);
}
setTargetColumn();
if (sd.highlightMatches)
highlightMatches(needle);
updateSelection();
recordJump();
}
void FakeVimHandler::Private::highlightMatches(const QString &needle0)
@@ -3386,7 +3436,6 @@ void FakeVimHandler::Private::highlightMatches(const QString &needle0)
return;
m_oldNeedle = needle0;
m_searchSelections.clear();
if (!needle0.isEmpty()) {
QTextCursor tc = m_tc;
tc.movePosition(StartOfDocument, MoveAnchor);
@@ -3394,17 +3443,16 @@ void FakeVimHandler::Private::highlightMatches(const QString &needle0)
QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
QString needle = needle0;
vimPatternToQtPattern(&needle, &flags);
EDITOR(setTextCursor(tc));
while (EDITOR(find(needle, flags))) {
tc = EDITOR(textCursor());
while (1) {
tc = tc.document()->find(needle, tc.position(), flags);
if (tc.isNull())
break;
QTextEdit::ExtraSelection sel;
sel.cursor = tc;
sel.format = tc.blockCharFormat();
sel.format.setBackground(QColor(177, 177, 0));
m_searchSelections.append(sel);
tc.movePosition(Right, MoveAnchor);
EDITOR(setTextCursor(tc));
}
}
updateSelection();
@@ -3819,11 +3867,6 @@ int FakeVimHandler::Private::lastPositionInDocument() const
return block.position() + block.length() - 1;
}
QString FakeVimHandler::Private::lastSearchString() const
{
return g.searchHistory.empty() ? QString() : g.searchHistory.back();
}
QString FakeVimHandler::Private::selectText(const Range &range) const
{
if (range.rangemode == RangeCharMode) {
@@ -4235,7 +4278,7 @@ void FakeVimHandler::Private::redo()
void FakeVimHandler::Private::updateCursor()
{
if (m_mode == ExMode) {
if (m_mode == ExMode || m_subsubmode == SearchSubSubMode) {
EDITOR(setCursorWidth(0));
EDITOR(setOverwriteMode(false));
} else if (m_mode == InsertMode) {
@@ -4252,6 +4295,7 @@ void FakeVimHandler::Private::enterReplaceMode()
m_mode = ReplaceMode;
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
m_commandPrefix.clear();
m_lastInsertion.clear();
m_lastDeletion.clear();
setUndoPosition(position());
@@ -4264,7 +4308,9 @@ void FakeVimHandler::Private::enterInsertMode()
m_mode = InsertMode;
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
m_commandPrefix.clear();
m_lastInsertion.clear();
m_lastDeletion.clear();
setUndoPosition(position());
breakEditBlock();
updateCursor();
@@ -4277,6 +4323,7 @@ void FakeVimHandler::Private::enterCommandMode()
m_mode = CommandMode;
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
m_commandPrefix.clear();
updateCursor();
}
@@ -4285,6 +4332,7 @@ void FakeVimHandler::Private::enterExMode()
m_mode = ExMode;
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
m_commandPrefix = ":";
updateCursor();
}