From b128e5ba1c3ad00dffc857db686e531b4360863f Mon Sep 17 00:00:00 2001 From: Morny Date: Thu, 3 Mar 2022 11:47:40 +0000 Subject: [PATCH] Issue #32 - First attempt at implementing ci{, etc Can now change a region demarked by the char after ci. Should work for just about anything; though probably best not to use it for things other than brackets and quotes. Known minor bug; an empty string ci" will not work depending on which side you are in (i.e. you won't be dropped into insert mode). The code tries to maintain indent, with reasonable success for multiple line curly brackets, etc. i.e for the following command in the inner brackets: { { dsdf sdf } } Results in: { { | } } Even though Zep doesn't really support auto intent, this is convenient for now. ci(, ci), ci{, ci}, ci[, ci], ci", ci' are of course very useful... No unit tests yet. --- include/zep/buffer.h | 1 + include/zep/keymap.h | 1 + src/buffer.cpp | 112 +++++++++++++++++++++++++++++++++++++++++-- src/mode.cpp | 78 ++++++++++++++++++++++++------ src/mode_vim.cpp | 1 + 5 files changed, 173 insertions(+), 20 deletions(-) diff --git a/include/zep/buffer.h b/include/zep/buffer.h index 31b759103..c0a582962 100644 --- a/include/zep/buffer.h +++ b/include/zep/buffer.h @@ -140,6 +140,7 @@ class ZepBuffer : public ZepComponent GlyphIterator Find(GlyphIterator start, const uint8_t* pBegin, const uint8_t* pEnd) const; GlyphIterator FindFirstCharOf(GlyphIterator& start, const std::string& chars, int32_t& foundIndex, Direction dir) const; GlyphIterator FindOnLineMotion(GlyphIterator start, const uint8_t* pCh, Direction dir) const; + std::pair FindMatchingPair(GlyphIterator start, const uint8_t ch) const; GlyphIterator WordMotion(GlyphIterator start, uint32_t searchType, Direction dir) const; GlyphIterator EndWordMotion(GlyphIterator start, uint32_t searchType, Direction dir) const; GlyphIterator ChangeWordMotion(GlyphIterator start, uint32_t searchType, Direction dir) const; diff --git a/include/zep/keymap.h b/include/zep/keymap.h index 107ca9983..c63f60bee 100644 --- a/include/zep/keymap.h +++ b/include/zep/keymap.h @@ -73,6 +73,7 @@ DECLARE_COMMANDID(ChangeAWord) DECLARE_COMMANDID(ChangeAWORD) DECLARE_COMMANDID(ChangeInnerWord) DECLARE_COMMANDID(ChangeInnerWORD) +DECLARE_COMMANDID(ChangeIn) DECLARE_COMMANDID(ChangeToChar) DECLARE_COMMANDID(Replace) diff --git a/src/buffer.cpp b/src/buffer.cpp index 6f24ae0c7..16e3d6b48 100644 --- a/src/buffer.cpp +++ b/src/buffer.cpp @@ -406,6 +406,108 @@ GlyphIterator ZepBuffer::FindOnLineMotion(GlyphIterator start, const uint8_t* pC return entry; } +std::pair ZepBuffer::FindMatchingPair(GlyphIterator start, const uint8_t ch) const +{ + std::string delims; + switch (ch) + { + case '(': + case ')': + delims = "()"; + break; + case '[': + case ']': + delims = "[]"; + break; + case '{': + case '}': + delims = "{}"; + break; + default: + // Matching same char at both ends + delims = std::string(2, ch); + break; + } + + std::pair ret; + Direction dir = Direction::Backward; + + auto search = [&](GlyphIterator loc, Direction dir) { + int openCount = 1; + for (;;) + { + // Find the previous open for the current delim type + int newIndex; + loc = FindFirstCharOf(loc, delims, newIndex, dir); + + // Fell off beginning, no find + if (newIndex < 0) + { + return GlyphIterator(); + } + + if (dir == Direction::Forward) + { + newIndex = 1 - newIndex; + } + + // Match immediately for "" + if (delims[0] == delims[1]) + { + newIndex = 0; + } + + // Found another opener + if (newIndex == 0) + { + openCount--; + if (openCount == 0) + { + // Found opening + return loc; + } + } + // Found a closer + else if (newIndex == 1) + { + openCount++; + } + + if (dir == Direction::Forward) + { + if (loc == End()) + { + return GlyphIterator(); + } + loc++; + } + else + { + if (loc == Begin()) + { + return GlyphIterator(); + } + loc--; + } + } + }; + + // If on the end bracket, start before it. + if (start.Char() == delims[1] && delims[0] != delims[1]) + { + start--; + } + + // Search for the begin + ret.first = search(start, Direction::Backward); + if (ret.first.Valid() && ret.first != End()) + { + // Search for the end + ret.second = search(ret.first + 1, Direction::Forward); + } + return ret; +} + // Only works on searches of ascii characters (but navigates unicode buffer); useful for some vim operations // Returns the index of the first found char and its location GlyphIterator ZepBuffer::FindFirstCharOf(GlyphIterator& start, const std::string& chars, int32_t& found_index, Direction dir) const @@ -414,7 +516,7 @@ GlyphIterator ZepBuffer::FindFirstCharOf(GlyphIterator& start, const std::string if (!itr.Valid()) { found_index = -1; - return itr; + return itr; } for (;;) @@ -440,7 +542,7 @@ GlyphIterator ZepBuffer::FindFirstCharOf(GlyphIterator& start, const std::string { if (itr == Begin()) { - break; + break; } itr--; } @@ -1206,7 +1308,7 @@ void ZepBuffer::ClearRangeMarker(std::shared_ptr spMarker) m_rangeMarkers.erase(spMarker->GetRange().first); } } - + // TODO: Why is this necessary; marks the whole buffer GetEditor().Broadcast(std::make_shared(this, BufferMessageType::MarkersChanged, Begin(), End())); } @@ -1606,11 +1708,11 @@ void ZepBuffer::BeginFlash(float seconds, FlashType flashType, const GlyphRange& { return; } - + auto spMarker = std::make_shared(*this); spMarker->SetRange(ByteRange(range.first.Index(), range.second.Index())); spMarker->SetBackgroundColor(ThemeColor::FlashColor); - spMarker->displayType = RangeMarkerDisplayType::Timed |RangeMarkerDisplayType::Background; + spMarker->displayType = RangeMarkerDisplayType::Timed | RangeMarkerDisplayType::Background; spMarker->markerType = RangeMarkerType::Mark; spMarker->duration = seconds; spMarker->flashType = flashType; diff --git a/src/mode.cpp b/src/mode.cpp index f3a5be3f1..73d742dff 100644 --- a/src/mode.cpp +++ b/src/mode.cpp @@ -126,18 +126,18 @@ void CommandContext::GetCommandRegisters() else { registers.push(keymap.RegisterName()); - char reg = keymap.RegisterName(); + char reg = keymap.RegisterName(); - // Demote capitals to lower registers when pasting (all both) - if (reg >= 'A' && reg <= 'Z') - { - reg = (char)std::tolower((char)reg); - } + // Demote capitals to lower registers when pasting (all both) + if (reg >= 'A' && reg <= 'Z') + { + reg = (char)std::tolower((char)reg); + } - if (owner.GetEditor().GetRegisters().find(std::string({ reg })) != owner.GetEditor().GetRegisters().end()) - { - pRegister = &owner.GetEditor().GetRegister(reg); - } + if (owner.GetEditor().GetRegisters().find(std::string({ reg })) != owner.GetEditor().GetRegisters().end()) + { + pRegister = &owner.GetEditor().GetRegister(reg); + } } } @@ -691,7 +691,7 @@ bool ZepMode::GetCommand(CommandContext& context) // too specialized? if (HandleExCommand(context.fullCommand)) { - //buffer.GetMode()->Begin(GetCurrentWindow()); + // buffer.GetMode()->Begin(GetCurrentWindow()); SwitchMode(DefaultMode()); ResetCommand(); return true; @@ -1629,6 +1629,54 @@ bool ZepMode::GetCommand(CommandContext& context) context.commandResult.modeSwitch = EditorMode::Insert; } } + else if (mappedCommand == id_ChangeIn) + { + if (!context.keymap.captureChars.empty()) + { + auto range = buffer.FindMatchingPair(bufferCursor, context.keymap.captureChars[0]); + if (range.first.Valid() && range.second.Valid()) + { + if ((range.first + 1) == range.second) + { + // A closed pair (); so insert between them + GetCurrentWindow()->SetBufferCursor(range.first + 1); + context.commandResult.modeSwitch = EditorMode::Insert; + return true; + } + else + { + GlyphIterator lineEnd = context.buffer.GetLinePos(range.first, LineLocation::LineCRBegin); + if (lineEnd.Valid() && lineEnd < range.second) + { + GlyphIterator lineStart = context.buffer.GetLinePos(range.first, LineLocation::LineBegin); + auto offsetStart = (range.first.Index() - lineStart.Index()); + + // If change in a pair of delimeters that are on seperate lines, then + // we remove everything and replace with 2 CRs and an indent based on the start bracket + // Since Zep doesn't auto indent, this is the best we can do for now. + context.replaceRangeMode = ReplaceRangeMode::Replace; + context.op = CommandOperation::Replace; + + auto offsetText = std::string(offsetStart + 4, ' '); + auto offsetBracket = std::string(offsetStart, ' '); + context.tempReg.text = std::string("\n") + offsetText + "\n" + offsetBracket; + context.pRegister = &context.tempReg; + context.beginRange = range.first + 1; + context.endRange = range.second; + context.cursorAfterOverride = range.first + (long)offsetText.length() + 2; + context.commandResult.modeSwitch = EditorMode::Insert; + } + else + { + context.beginRange = range.first + 1; // returned range is inclusive + context.endRange = range.second; + context.op = CommandOperation::Delete; + context.commandResult.modeSwitch = EditorMode::Insert; + } + } + } + } + } else if (mappedCommand == id_SubstituteLine) { // Delete whole line and go to insert mode @@ -1693,7 +1741,7 @@ bool ZepMode::GetCommand(CommandContext& context) { // Make a new end location auto end_loc = loc; - + std::string closing; std::string opening = std::string(1, delims[findIndex]); @@ -1841,7 +1889,7 @@ bool ZepMode::GetCommand(CommandContext& context) // If not a single char, then we are trying to input a special, which isn't allowed // TOOD: Cleaner detection of this? // Special case for 'j + another character' which is an insert - if (true) //context.keymap.commandWithoutGroups.size() == 1 || ((context.keymap.commandWithoutGroups.size() == 2) && context.keymap.commandWithoutGroups[0] == 'j')) + if (true) // context.keymap.commandWithoutGroups.size() == 1 || ((context.keymap.commandWithoutGroups.size() == 2) && context.keymap.commandWithoutGroups[0] == 'j')) { context.beginRange = context.bufferCursor; context.tempReg.text = context.keymap.commandWithoutGroups; @@ -2239,7 +2287,7 @@ bool ZepMode::HandleExCommand(std::string strCommand) } else if (strCommand.find(":ZTestFloatSlider") == 0) { - //auto line = buffer.GetBufferLine(bufferCursor); + // auto line = buffer.GetBufferLine(bufferCursor); auto pSlider = std::make_shared(GetEditor(), 4); auto spMarker = std::make_shared(buffer); @@ -2250,7 +2298,7 @@ bool ZepMode::HandleExCommand(std::string strCommand) } else if (strCommand.find(":ZTestColorPicker") == 0) { - //auto line = buffer.GetBufferLine(bufferCursor); + // auto line = buffer.GetBufferLine(bufferCursor); auto pPicker = std::make_shared(GetEditor()); auto spMarker = std::make_shared(buffer); spMarker->SetRange(ByteRange(bufferCursor.Index(), bufferCursor.Peek(1).Index())); diff --git a/src/mode_vim.cpp b/src/mode_vim.cpp index 0eb90053c..95d4e0c91 100644 --- a/src/mode_vim.cpp +++ b/src/mode_vim.cpp @@ -165,6 +165,7 @@ void ZepMode_Vim::SetupKeyMaps() AddKeyMapWithCountRegisters({ &m_normalMap }, { "cW" }, id_ChangeWORD); AddKeyMapWithCountRegisters({ &m_normalMap }, { "ciw" }, id_ChangeInnerWord); AddKeyMapWithCountRegisters({ &m_normalMap }, { "ciW" }, id_ChangeInnerWORD); + AddKeyMapWithCountRegisters({ &m_normalMap }, { "ci<.>" }, id_ChangeIn); AddKeyMapWithCountRegisters({ &m_normalMap }, { "caw" }, id_ChangeAWord); AddKeyMapWithCountRegisters({ &m_normalMap }, { "caW" }, id_ChangeAWORD); AddKeyMapWithCountRegisters({ &m_normalMap }, { "C", "c$" }, id_ChangeToLineEnd);