Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: Add replace all command to Ex mode #1910

Merged
merged 4 commits into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog.d/20231029_220423_GitHub_Actions_replace-all.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.. _#1910: https://github.com/fox0430/moe/pull/1910

Added
.....

- `#1910`_ enhance: Add replace all command to Ex mode

103 changes: 88 additions & 15 deletions src/moepkg/editor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1116,14 +1117,14 @@ proc openBlankLineAbove*(
inc(bufStatus.countChange)
bufStatus.isUpdate = true

# Delete lines and store lines to the register
proc deleteLines*(
bufStatus: var BufferStatus,
registers: var Registers,
windowNode: WindowNode,
registerName: string,
startLine, loop: int,
settings: EditorSettings) =
## Delete lines and store lines to the register

let endLine = min(startLine + loop, bufStatus.buffer.high)

Expand Down Expand Up @@ -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
Expand All @@ -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]

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1328,7 +1329,6 @@ proc deleteTillNextBlankLine*(
inc(bufStatus.countChange)
bufStatus.isUpdate = true

# name is the register name
proc yankLines*(
bufStatus: BufferStatus,
registers: var Registers,
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1834,14 +1907,14 @@ 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,
registers: var Registers,
registerName: string,
rune: Rune,
settings: EditorSettings) =
## If cursor is inside of paren, delete inside paren in the current line

let
currentLine = windowNode.currentLine
Expand Down Expand Up @@ -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(
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
66 changes: 20 additions & 46 deletions src/moepkg/exmode.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
Loading