diff --git a/changelog.d/20231029_220423_GitHub_Actions_replace-all.rst b/changelog.d/20231029_220423_GitHub_Actions_replace-all.rst new file mode 100644 index 000000000..c6edd1270 --- /dev/null +++ b/changelog.d/20231029_220423_GitHub_Actions_replace-all.rst @@ -0,0 +1,7 @@ +.. _#1910: https://github.com/fox0430/moe/pull/1910 + +Added +..... + +- `#1910`_ enhance: Add replace all command to Ex mode + diff --git a/src/moepkg/editor.nim b/src/moepkg/editor.nim index f55a67b98..a54743a63 100644 --- a/src/moepkg/editor.nim +++ b/src/moepkg/editor.nim @@ -20,7 +20,8 @@ import std/[strutils, sequtils, strformat, options] import syntax/highlite import editorstatus, ui, gapbuffer, unicodeext, windownode, bufferstatus, - movement, messages, settings, registers, commandline, independentutils + movement, messages, settings, registers, commandline, independentutils, + searchutils proc correspondingCloseParen(c: char): char = case c @@ -871,13 +872,13 @@ proc deleteCharacter*( colmun, currentChar) -# Delete characters in the line proc deleteCharacters*( bufStatus: var BufferStatus, registers: var Registers, registerName: string, line, colmun, loop: int, settings: EditorSettings) = + ## Delete characters in the line if line >= bufStatus.buffer.high and colmun > bufStatus.buffer[line].high: return @@ -921,11 +922,11 @@ proc deleteCharacters*( inc(bufStatus.countChange) bufStatus.isUpdate = true -# No yank buffer proc deleteCharacters*( bufStatus: var BufferStatus, autoDeleteParen: bool, line, colmun, loop: int) = + ## No yank buffer if line >= bufStatus.buffer.high and colmun > bufStatus.buffer[line].high: return @@ -960,11 +961,11 @@ proc deleteCharacters*( inc(bufStatus.countChange) bufStatus.isUpdate = true -# Add the new line and insert indent in Nim proc insertIndentNimForOpenBlankLine( bufStatus: var BufferStatus, windowNode: var WindowNode, tabStop: int) = + ## Add the new line and insert indent in Nim proc splitWhitespace(runes: Runes): seq[Runes] {.inline.} = ## Remove empty entries @@ -1049,11 +1050,11 @@ proc insertIndentInPythonForOpenBlankLine( if oldLine != newLine: bufStatus.buffer[currentLineNum] = newLine -# Add the new line and insert indent in the plain text proc insertIndentPlainTextForOpenBlankLine( bufStatus: var BufferStatus, windowNode: var WindowNode, tabStop: int) = + ## Add the new line and insert indent in the plain text let currentLineNum = windowNode.currentLine @@ -1116,7 +1117,6 @@ proc openBlankLineAbove*( inc(bufStatus.countChange) bufStatus.isUpdate = true -# Delete lines and store lines to the register proc deleteLines*( bufStatus: var BufferStatus, registers: var Registers, @@ -1124,6 +1124,7 @@ proc deleteLines*( registerName: string, startLine, loop: int, settings: EditorSettings) = + ## Delete lines and store lines to the register let endLine = min(startLine + loop, bufStatus.buffer.high) @@ -1199,13 +1200,13 @@ proc deleteCharacterBeginningOfLine*( windowNode.currentColumn = 0 windowNode.expandedColumn = 0 -# Delete characters after blank in the current line proc deleteCharactersAfterBlankInLine*( bufStatus: var BufferStatus, registers: var Registers, windowNode: var WindowNode, registerName: string, settings: EditorSettings) = + ## Delete characters after blank in the current line let currentLine = windowNode.currentLine @@ -1225,13 +1226,13 @@ proc deleteCharactersAfterBlankInLine*( windowNode.currentColumn = firstNonBlankCol windowNode.expandedColumn = firstNonBlankCol -# Delete from the previous blank line to the current line proc deleteTillPreviousBlankLine*( bufStatus: var BufferStatus, registers: var Registers, windowNode: WindowNode, registerName: string, settings: EditorSettings) = + ## Delete from the previous blank line to the current line var deletedBuffer: seq[Runes] @@ -1277,13 +1278,13 @@ proc deleteTillPreviousBlankLine*( inc(bufStatus.countChange) bufStatus.isUpdate = true -# Delete from the current line to the next blank line proc deleteTillNextBlankLine*( bufStatus: var BufferStatus, registers: var Registers, windowNode: WindowNode, registerName: string, settings: EditorSettings) = + ## Delete from the current line to the next blank line let currentLine = windowNode.currentLine @@ -1328,7 +1329,6 @@ proc deleteTillNextBlankLine*( inc(bufStatus.countChange) bufStatus.isUpdate = true -# name is the register name proc yankLines*( bufStatus: BufferStatus, registers: var Registers, @@ -1338,6 +1338,7 @@ proc yankLines*( name: string, isDelete: bool, settings: EditorSettings) = + ## name is the register name var yankedBuffer: seq[Runes] for i in first .. last: @@ -1684,13 +1685,14 @@ proc pasteBeforeCursor*( if register.isSome: bufStatus.pasteBeforeCursor(windowNode, register.get) -# Replace characters and move to the right proc replaceCharacters*( bufStatus: var BufferStatus, windowNode: WindowNode, autoIndent, autoDeleteParen: bool, tabStop, loop: int, character: Rune) = + ## Replace characters and move to the right + ## For 'r' command. if isEnterKey(character): let @@ -1718,6 +1720,77 @@ proc replaceCharacters*( inc(bufStatus.countChange) bufStatus.isUpdate = true +proc replaceAll*(b: var BufferStatus, lineRange: Range, sub, by: Runes) = + ## Replaces all words. + + var isChanged = false + + if sub.contains(ru'\n') or by.contains(ru'\n'): + let r = b.buffer.toRunes.searchAll(sub, false, false) + if r.len > 0: + let oldBuffer = b.buffer.toRunes + + let diff = by.high - sub.high + # if including Newline, Convert the buffer to `Runes` and replace runes. + var newBuffer = b.buffer.toRunes + for i, position in r: + let start = position + (diff * i) + newBuffer.delete(start .. start + sub.high) + newBuffer.insert(by, start) + + if oldBuffer != newBuffer: + b.buffer = newBuffer.splitLines.toGapBuffer + if not isChanged: isChanged = true + else: + for i in lineRange.first .. lineRange.last: + let r = b.buffer[i].searchAll(sub, false, false) + if r.len > 0: + let oldLine = b.buffer[i] + + let diff = by.high - sub.high + var newLine = b.buffer[i] + for i, position in r: + let start = position + (diff * i) + newLine.delete(start .. start + sub.high) + newLine.insert(by, start) + + if oldLine != newLine: + b.buffer[i] = newLine + if not isChanged: isChanged = true + + if isChanged and not b.isUpdate: + b.isUpdate = true + b.countChange.inc + +proc replaceOnlyFirstWordInLines*( + b: var BufferStatus, + lineRange: Range, + sub, by: Runes) = + ## Replaces words in only first positions in lines. + ## If contains NewLine in `sub` or `by`, change to `replaceAll`. + + if sub.contains(ru'\n') or by.contains(ru'\n'): + b.replaceAll(lineRange, sub, by) + else: + var isChanged = false + + for i in lineRange.first .. lineRange.last: + let r = b.buffer[i].search(sub, false, false) + if r.isSome: + let oldLine = b.buffer[i] + + var newLine = b.buffer[i] + newLine.delete(r.get .. r.get + sub.high) + newLine.insert(by, r.get) + + if oldLine != newLine: + b.buffer[i] = newLine + if not isChanged: isChanged = true + + if isChanged and not b.isUpdate: + b.isUpdate = true + b.countChange.inc + proc toggleCharacters*( bufStatus: var BufferStatus, windowNode: var WindowNode, @@ -1834,7 +1907,6 @@ proc redo*(bufStatus: var BufferStatus, windowNode: WindowNode) = inc(bufStatus.countChange) bufStatus.isUpdate = true -# If cursor is inside of paren, delete inside paren in the current line proc deleteInsideOfParen*( bufStatus: var BufferStatus, windowNode: var WindowNode, @@ -1842,6 +1914,7 @@ proc deleteInsideOfParen*( registerName: string, rune: Rune, settings: EditorSettings) = + ## If cursor is inside of paren, delete inside paren in the current line let currentLine = windowNode.currentLine @@ -1891,13 +1964,13 @@ proc deleteInsideOfParen*( bufStatus.buffer[currentLine] = newLine windowNode.currentColumn = openParenPosition -# If cursor is inside of paren, delete inside paren in the current line proc deleteInsideOfParen*( bufStatus: var BufferStatus, windowNode: var WindowNode, registers: var Registers, rune: Rune, settings: EditorSettings) = + ## If cursor is inside of paren, delete inside paren in the current line const RegisterName = "" bufStatus.deleteInsideOfParen( @@ -1907,10 +1980,10 @@ proc deleteInsideOfParen*( rune, settings) -# Return the colmn and word proc getWordUnderCursor*( bufStatus: BufferStatus, windowNode: WindowNode): (int, Runes) = + ## Return the colmn and word let line = bufStatus.buffer[windowNode.currentLine] if line.len <= windowNode.currentColumn: @@ -1946,11 +2019,11 @@ proc getCharacterUnderCursor*( line[windowNode.currentColumn] -# Increment/Decrement the number string under the cursor proc modifyNumberTextUnderCurosr*( bufStatus: var BufferStatus, windowNode: var WindowNode, amount: int) = + ## Increment/Decrement the number string under the cursor let currentLine = windowNode.currentLine diff --git a/src/moepkg/exmode.nim b/src/moepkg/exmode.nim index 5073d7bff..eb7d51f6a 100644 --- a/src/moepkg/exmode.nim +++ b/src/moepkg/exmode.nim @@ -17,15 +17,15 @@ # # #[############################################################################]# -import std/[sequtils, strutils, os, times, options, strformat] +import std/[strutils, os, times, options, strformat] import pkg/results import syntax/highlite import editorstatus, ui, normalmode, gapbuffer, fileutils, editorview, - unicodeext, independentutils, searchutils, highlight, windownode, - movement, build, bufferstatus, editor, settings, quickrunutils, messages, - commandline, debugmodeutils, platform, commandlineutils, recentfilemode, - buffermanager, viewhighlight, messagelog, configmode, git, syntaxcheck, - theme, exmodeutils + unicodeext, independentutils, highlight, windownode, movement, build, + bufferstatus, editor, settings, quickrunutils, messages, commandline, + debugmodeutils, platform, commandlineutils, recentfilemode, messagelog, + buffermanager, viewhighlight, configmode, git, syntaxcheck, theme, + exmodeutils proc startDebugMode(status: var EditorStatus) = status.changeMode(currentBufStatus.prevMode) @@ -1004,49 +1004,23 @@ proc listAllBufferCommand(status: var EditorStatus) = currentBufStatus.isUpdate = true proc replaceBuffer(status: var EditorStatus, command: Runes) = - let replaceInfo = parseReplaceCommand(command) + ## Repace runes in the current buffer. + ## "%s/abc/xyz" or "%s/abc/xyz/g" command. - if replaceInfo.searhWord == ru"'\n'" and currentBufStatus.buffer.len > 1: - let startLine = 0 - - for i in 0 .. currentBufStatus.buffer.high - 2: - let oldLine = currentBufStatus.buffer[startLine] - var newLine = currentBufStatus.buffer[startLine] - newLine.insert( - replaceInfo.replaceWord, - currentBufStatus.buffer[startLine].len) - for j in 0 .. currentBufStatus.buffer[startLine + 1].high: - newLine.insert( - currentBufStatus.buffer[startLine + 1][j], - currentBufStatus.buffer[startLine].len) - if oldLine != newLine: - currentBufStatus.buffer[startLine] = newLine - - currentBufStatus.buffer.delete(startLine + 1, startLine + 1) + let replaceInfo = parseReplaceCommand(command) + if replaceInfo.isGlobal: + # Replace all + currentBufStatus.replaceAll( + Range(first: 0, last: currentBufStatus.buffer.high), + replaceInfo.sub, + replaceInfo.by) else: - let - isIgnorecase = status.settings.ignorecase - isSmartcase = status.settings.smartcase - for i in 0 .. currentBufStatus.buffer.high: - let searchResult = currentBufStatus.buffer[i].searchLine( - replaceInfo.searhWord, isIgnorecase, isSmartcase) - if searchResult.isSome: - let oldLine = currentBufStatus.buffer[i] - var newLine = currentBufStatus.buffer[i] - - let - first = searchResult.get - last = searchResult.get + replaceInfo.searhWord.high - for _ in first .. last: - newLine.delete(searchResult.get) - - newLine.insert(replaceInfo.replaceWord, searchResult.get) - if oldLine != newLine: - currentBufStatus.buffer[i] = newLine - - if not currentBufStatus.isUpdate: currentBufStatus.isUpdate = true + # Replace only the first words in lines. + currentBufStatus.replaceOnlyFirstWordInLines( + Range(first: 0, last: currentBufStatus.buffer.high), + replaceInfo.sub, + replaceInfo.by) - inc(currentBufStatus.countChange) status.commandLine.clear status.changeMode(currentBufStatus.prevMode) diff --git a/src/moepkg/exmodeutils.nim b/src/moepkg/exmodeutils.nim index df27c312f..6a19d8828 100644 --- a/src/moepkg/exmodeutils.nim +++ b/src/moepkg/exmodeutils.nim @@ -35,7 +35,7 @@ type description*: string argsType*: ArgsType - ReplaceCommandInfo* = tuple[searhWord, replaceWord: Runes] + ReplaceCommandInfo* = tuple[sub, by: Runes, isGlobal: bool] const ExCommandInfoList* = [ @@ -843,4 +843,7 @@ proc parseReplaceCommand*(command: Runes): ReplaceCommandInfo = let commandSplit = command.split(ru'/', RemoveEmptyEntries) if commandSplit.len < 2: return - return (searhWord: commandSplit[0], replaceWord: commandSplit[1]) + return ( + sub: commandSplit[0].replaceToNewLines, + by: commandSplit[1].replaceToNewLines, + isGlobal: commandSplit.len == 3 and commandSplit[2] == ru"g") diff --git a/src/moepkg/searchutils.nim b/src/moepkg/searchutils.nim index 47350dd9d..bdd1c3299 100644 --- a/src/moepkg/searchutils.nim +++ b/src/moepkg/searchutils.nim @@ -27,34 +27,54 @@ type forward = 0 backward = 1 -proc compare(rune, sub: Runes, isIgnorecase, isSmartcase: bool): bool = - ## Return true If the text matches. +proc compare*( + rune, sub: Runes, + isIgnorecase, isSmartcase: bool): bool {.inline.} = + ## Return true If the text matches. - if isIgnorecase and not isSmartcase: - if cmpIgnoreCase($rune, $sub) == 0: return true - elif isSmartcase and isIgnorecase: - if isContainUpper(sub): - return rune == sub - else: + if isIgnorecase and not isSmartcase: if cmpIgnoreCase($rune, $sub) == 0: return true - else: - return rune == sub + elif isSmartcase and isIgnorecase: + if isContainUpper(sub): + return rune == sub + else: + if cmpIgnoreCase($rune, $sub) == 0: return true + else: + return rune == sub -proc searchLine*( - line: Runes, +proc search*( + buffer: Runes, keyword: Runes, isIgnorecase, isSmartcase: bool): Option[int] = - ## Return a position in a line if a keyword matches. - - for startPostion in 0 .. (line.len - keyword.len): - let - endPosition = startPostion + keyword.len - runes = line[startPostion ..< endPosition] - - if compare(runes, keyword, isIgnorecase, isSmartcase): - return startPostion.some - -proc searchLineReversely( + ## Return a position if keyword matches. + + for position in 0 .. buffer.high - keyword.high: + if compare( + buffer[position .. position + keyword.high], + keyword, + isIgnorecase, + isSmartcase): + return some(position) + +proc searchAll*( + buffer: Runes, + keyword: Runes, + isIgnorecase, isSmartcase: bool): seq[int] = + ## Return positions if keyword matches. + + var position = 0 + while position + keyword.high < buffer.len: + if compare( + buffer[position .. position + keyword.high], + keyword, + isIgnorecase, + isSmartcase): + result.add position + position = position + keyword.len + else: + position.inc + +proc searchReversely( line: Runes, keyword: Runes, isIgnorecase, isSmartcase: bool): Option[int] = @@ -87,7 +107,7 @@ proc searchBuffer*( line = bufStatus.buffer[lineNumber] - position = searchLine( + position = search( line[first ..< last], keyword, isIgnorecase, @@ -116,7 +136,7 @@ proc searchBufferReversely*( else: buffer[lineNumber].len - position = searchLineReversely( + position = searchReversely( buffer[lineNumber][0 ..< endPosition], keyword, isIgnorecase, @@ -139,7 +159,7 @@ proc searchAllOccurrence*( let last = buffer[lineNumber].len line = buffer[lineNumber] - position = searchLine( + position = search( line[first ..< last], keyword, isIgnorecase, diff --git a/src/moepkg/unicodeext.nim b/src/moepkg/unicodeext.nim index 5ea4b6507..a6cbe1a22 100644 --- a/src/moepkg/unicodeext.nim +++ b/src/moepkg/unicodeext.nim @@ -603,6 +603,28 @@ proc stripLineEnd*(runes: Runes): Runes = proc replace*(runes1, sub: Runes, by: Runes = ru""): Runes {.inline.} = replace($runes1, $sub, $by).toRunes +proc replaceToNewLines*(runes: Runes): Runes = + ## Replaces "\n" to '\n' in `runes`. + ## Ignore "\\n". + + var + i = 0 + isEscape = false + while i < runes.len: + if runes[i] == ru'\\': + if not isEscape: + isEscape = true + elif runes[i - 1] == ru'\\': + isEscape = false + result.add runes[i] + elif isEscape and runes[i] == ru'n': + result.add ru'\n' + isEscape = false + else: + result.add runes[i] + + i.inc + proc maxLen*(lines: seq[Runes]): int {.inline.} = lines.mapIt(it.len).max diff --git a/tests/teditor.nim b/tests/teditor.nim index 934fdaad8..4961f1d33 100644 --- a/tests/teditor.nim +++ b/tests/teditor.nim @@ -2926,3 +2926,125 @@ suite "Editor: Unindent": for l in SourceLanguage: unindentTestCase5(l) + +suite "Editor: replaceAll": + test "Basic": + var b = initBufferStatus(Mode.normal).get + b.buffer = @["abc abc xyz", "abc"].toSeqRunes.toGapBuffer + + let lineRange = Range(first: 0, last: 1) + const + Sub = ru"abc" + By = ru"xyz" + b.replaceAll(lineRange, Sub, By) + + check b.buffer.toSeqRunes == @[ru"xyz xyz xyz", ru"xyz"] + + test "Basic 2": + var b = initBufferStatus(Mode.normal).get + b.buffer = @["abcabc", ""].toSeqRunes.toGapBuffer + + let lineRange = Range(first: 0, last: 1) + const + Sub = ru"abc" + By = ru"xyz" + b.replaceAll(lineRange, Sub, By) + + check b.buffer.toSeqRunes == @[ru"xyzxyz", ru""] + + test "Basic 3": + var b = initBufferStatus(Mode.normal).get + b.buffer = @["abcAbc", "ABC"].toSeqRunes.toGapBuffer + + let lineRange = Range(first: 0, last: 1) + const + Sub = ru"Abc" + By = ru"xyz" + b.replaceAll(lineRange, Sub, By) + + check b.buffer.toSeqRunes == @[ru"abcxyz", ru"ABC"] + + test "With NewLine": + var b = initBufferStatus(Mode.normal).get + b.buffer = @["abc", "abc", "xyz", "abc", "abc"].toSeqRunes.toGapBuffer + + let lineRange = Range(first: 0, last: 4) + const + Sub = "abc\nabc".toRunes + By = ru"xyz" + b.replaceAll(lineRange, Sub, By) + + check b.buffer.toSeqRunes == @[ru"xyz", ru"xyz", ru"xyz"] + + test "With NewLine 2": + var b = initBufferStatus(Mode.normal).get + b.buffer = @["abc", "abc", "xyz", "abc", "abc"].toSeqRunes.toGapBuffer + + let lineRange = Range(first: 0, last: 4) + const + Sub = "abc\nabc".toRunes + By = "xyz\nxyz".toRunes + b.replaceAll(lineRange, Sub, By) + + check b.buffer.toSeqRunes == @["xyz", "xyz", "xyz", "xyz", "xyz"].toSeqRunes + +suite "Editor: replaceOnlyFirstWordInLines": + test "Basic": + var b = initBufferStatus(Mode.normal).get + b.buffer = @["abc abc xyz", "abc"].toSeqRunes.toGapBuffer + + let lineRange = Range(first: 0, last: 1) + const + Sub = ru"abc" + By = ru"xyz" + b.replaceOnlyFirstWordInLines(lineRange, Sub, By) + + check b.buffer.toSeqRunes == @[ru"xyz abc xyz", ru"xyz"] + + test "Basic 2": + var b = initBufferStatus(Mode.normal).get + b.buffer = @["abcabc", ""].toSeqRunes.toGapBuffer + + let lineRange = Range(first: 0, last: 1) + const + Sub = ru"abc" + By = ru"xyz" + b.replaceOnlyFirstWordInLines(lineRange, Sub, By) + + check b.buffer.toSeqRunes == @[ru"xyzabc", ru""] + + test "Basic 3": + var b = initBufferStatus(Mode.normal).get + b.buffer = @["abcAbc", "ABC"].toSeqRunes.toGapBuffer + + let lineRange = Range(first: 0, last: 1) + const + Sub = ru"Abc" + By = ru"xyz" + b.replaceOnlyFirstWordInLines(lineRange, Sub, By) + + check b.buffer.toSeqRunes == @[ru"abcxyz", ru"ABC"] + + test "With NewLine": + var b = initBufferStatus(Mode.normal).get + b.buffer = @["abc", "abc", "xyz", "abc", "abc"].toSeqRunes.toGapBuffer + + let lineRange = Range(first: 0, last: 4) + const + Sub = "abc\nabc".toRunes + By = ru"xyz" + b.replaceOnlyFirstWordInLines(lineRange, Sub, By) + + check b.buffer.toSeqRunes == @[ru"xyz", ru"xyz", ru"xyz"] + + test "With NewLine 2": + var b = initBufferStatus(Mode.normal).get + b.buffer = @["abc", "abc", "xyz", "abc", "abc"].toSeqRunes.toGapBuffer + + let lineRange = Range(first: 0, last: 4) + const + Sub = "abc\nabc".toRunes + By = "xyz\nxyz".toRunes + b.replaceOnlyFirstWordInLines(lineRange, Sub, By) + + check b.buffer.toSeqRunes == @["xyz", "xyz", "xyz", "xyz", "xyz"].toSeqRunes diff --git a/tests/texmode.nim b/tests/texmode.nim index a18f15937..153f6f318 100644 --- a/tests/texmode.nim +++ b/tests/texmode.nim @@ -248,7 +248,7 @@ suite "Ex mode: Change to last buffer command": check(currentMainWindowNode.bufferIndex == 2) suite "Ex mode: Replace buffer command": - test "Replace buffer command": + test "Basic": var status = initEditorStatus() discard status.addNewBufferInCurrentWin.get @@ -261,6 +261,32 @@ suite "Ex mode: Replace buffer command": check status.bufStatus[0].buffer.toSeqRunes == @["xyz", "abcdzzzzzzhijk", "Hello"].toSeqRunes + test "Replace all": + var status = initEditorStatus() + discard status.addNewBufferInCurrentWin.get + + status.bufStatus[0].buffer = @[ + "xyzabc", + "abcxyz", + "abc", + "abcxyzabc", + "", + "xyzabcxyz"] + .toSeqRunes + .initGapBuffer + + const Command = @[ru"%s/abc/iii/g"] + status.exModeCommand(Command) + + check status.bufStatus[0].buffer.toSeqRunes == @[ + "xyziii", + "iiixyz", + "iii", + "iiixyziii", + "", + "xyziiixyz"] + .toSeqRunes + suite "Ex mode: Turn off highlighting command": test "Turn off highlighting command": var status = initEditorStatus() diff --git a/tests/texmodeutils.nim b/tests/texmodeutils.nim index b17e047e9..152101d89 100644 --- a/tests/texmodeutils.nim +++ b/tests/texmodeutils.nim @@ -695,3 +695,20 @@ suite "exmodeutils: isValidFileOpenCommand": test "Invalid 5": check not isValidFileOpenCommand(ru"moe.nimble") + +suite "exmodeutils: parseReplaceCommand": + test "Basic": + check parseReplaceCommand(ru"/abc/xyz") == + (sub: ru"abc", by: ru"xyz", isGlobal: false) + + test "Basic 2": + check parseReplaceCommand(ru"/abc/xyz/") == + (sub: ru"abc", by: ru"xyz", isGlobal: false) + + test "Global": + check parseReplaceCommand(ru"/abc/xyz/g") == + (sub: ru"abc", by: ru"xyz", isGlobal: true) + + test "With newlines": + check parseReplaceCommand(ru"/\nabc/xyz\n/") == + (sub: "\nabc".toRunes, by: "xyz\n".toRunes, isGlobal: false) diff --git a/tests/tsearch.nim b/tests/tsearch.nim index 4414668a8..73e0a41c2 100644 --- a/tests/tsearch.nim +++ b/tests/tsearch.nim @@ -23,41 +23,41 @@ import moepkg/[unicodeext, editorstatus, gapbuffer, independentutils] import moepkg/searchutils {.all.} -suite "search: searchLine": - test "searchLine": +suite "search: search": + test "Basic": let line = ru"abc efg hijkl" isIgnorecase = true isSmartcase = true - position = line.searchLine(ru"ijk", isIgnorecase, isSmartcase) + position = line.search(ru"ijk", isIgnorecase, isSmartcase) check position.get == 9 - test "searchLine 2": + test "Basic 2": let line = ru"abc efg hijkl" isIgnorecase = true isSmartcase = true - position = line.searchLine(ru"xyz", isIgnorecase, isSmartcase) + position = line.search(ru"xyz", isIgnorecase, isSmartcase) check position.isNone - test "Enable ignorecase, disable smartcase": + test "Enable ignorecase": let line = ru"Editor editor" isIgnorecase = true isSmartcase = true - position = line.searchLine(ru"editor", isIgnorecase, isSmartcase) + position = line.search(ru"editor", isIgnorecase, isSmartcase) check position.get == 0 - test "Enable ignorecase and smartcase": + test "Enable all": block: let line = ru"editor Editor" isIgnorecase = true isSmartcase = true - position = line.searchLine(ru"Editor", isIgnorecase, isSmartcase) + position = line.search(ru"Editor", isIgnorecase, isSmartcase) check position.get == 7 @@ -66,36 +66,55 @@ suite "search: searchLine": line = ru"editor Editor" isIgnorecase = true isSmartcase = true - position = line.searchLine(ru"editor", isIgnorecase, isSmartcase) + position = line.search(ru"editor", isIgnorecase, isSmartcase) check position.get == 0 - test "Disable ignorecase": + test "Disable all": let line = ru"Editor" isIgnorecase = false isSmartcase = false - position = line.searchLine(ru"editor", isIgnorecase, isSmartcase) + position = line.search(ru"editor", isIgnorecase, isSmartcase) check position.isNone -suite "search: searchLineReversely": - test "searchLineReversely": +suite "search: searchAll": + test "Basic": + let + line = ru"abc def abc" + isIgnorecase = true + isSmartcase = true + + let positions = line.searchAll(ru"abc", isIgnorecase, isSmartcase) + check positions == @[0, 8] + + test "Basic 2": + let + line = ru"abcdefabcabcdefabc" + isIgnorecase = true + isSmartcase = true + + let positions = line.searchAll(ru"abc", isIgnorecase, isSmartcase) + check positions == @[0, 6, 9, 15] + +suite "search: searchReversely": + test "searchReversely": let line = ru"abc efg hijkl" isIgnorecase = true isSmartcase = true - position = line.searchLineReversely(ru"ijk", isIgnorecase, isSmartcase) + position = line.searchReversely(ru"ijk", isIgnorecase, isSmartcase) check position.get == 9 - test "searchLineReversely 2": + test "searchReversely 2": let line = ru"abc efg hijkl" keyword = ru"xyz" isIgnorecase = true isSmartcase = true - position = line.searchLineReversely(keyword, isIgnorecase, isSmartcase) + position = line.searchReversely(keyword, isIgnorecase, isSmartcase) check position.isNone diff --git a/tests/tunicodeext.nim b/tests/tunicodeext.nim index 4ee724c7f..c652b6b70 100644 --- a/tests/tunicodeext.nim +++ b/tests/tunicodeext.nim @@ -18,6 +18,7 @@ #[############################################################################]# import std/[strutils, unittest, encodings, sequtils, sugar] +from std/os import `/` import moepkg/gapbuffer import moepkg/unicodeext @@ -416,7 +417,6 @@ test "join 1": test "join 2": check @[ru"a", ru"b", ru"c"].join(ru" ") == ru"a b c" -from os import `/` test "/": proc checkJoinPath(head, tail: string) = check head.ru / tail.ru == (head / tail).ru @@ -427,3 +427,16 @@ test "/": checkJoinPath("usr", "/lib") checkJoinPath("usr/", "/lib/") check ru"usr" / ru"lib" / ru"../bin" == ru"usr/bin" + +suite "unicodeext: replaceToNewLines": + test "Basic": + check replaceToNewLines("\\nabc\\n".toRunes) == "\nabc\n".toRunes + + test "Escape": + check replaceToNewLines("\\\\nabc\\\\n".toRunes) == "\\nabc\\n".toRunes + + test "Without Newline": + check replaceToNewLines(ru"abc") == ru"abc" + + test "Empty": + check replaceToNewLines(ru"") == ru""