grimoir/search.go
2026-05-07 09:52:06 +03:00

284 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"strings"
)
// === ПОИСК В СТРОКЕ (f/t команды) ===
func (m *EditorModel) findCharInLine(char rune, forward bool) {
line := []rune(m.lines[m.cursorY])
if forward {
for i := m.cursorX + 1; i < len(line); i++ {
if line[i] == char {
m.cursorX = i
m.lastFindChar = char
m.lastFindIsForward = true
return
}
}
} else {
for i := m.cursorX - 1; i >= 0; i-- {
if line[i] == char {
m.cursorX = i
m.lastFindChar = char
m.lastFindIsForward = false
return
}
}
}
m.SetStatusMsg("Character not found")
}
func (m *EditorModel) findCharToLine(char rune, forward bool) {
line := []rune(m.lines[m.cursorY])
if forward {
for i := m.cursorX + 1; i < len(line); i++ {
if line[i] == char {
if i > 0 {
m.cursorX = i - 1
}
m.lastFindChar = char
m.lastFindIsForward = true
return
}
}
} else {
for i := m.cursorX - 1; i >= 0; i-- {
if line[i] == char {
if i < len(line)-1 {
m.cursorX = i + 1
}
m.lastFindChar = char
m.lastFindIsForward = false
return
}
}
}
m.SetStatusMsg("Character not found")
}
func (m *EditorModel) repeatLastFind(reverse bool) {
if m.lastFindChar == 0 {
m.SetStatusMsg("No previous search")
return
}
forward := m.lastFindIsForward
if reverse {
forward = !forward
}
m.findCharInLine(m.lastFindChar, forward)
}
// === ГЛОБАЛЬНЫЙ ПОИСК (/pattern) ===
func (m *EditorModel) startSearch() {
m.mode = SEARCH
m.searchBuffer = ""
m.searchMatches = nil
m.searchIdx = 0
m.SetStatusMsg("/")
}
func (m *EditorModel) performSearch(pattern string) {
m.lastSearchTerm = pattern
m.searchMatches = nil
m.searchIdx = 0
if pattern == "" {
return
}
for lineNum, line := range m.lines {
// Ищем в рунах чтобы позиции были корректными
lineRunes := []rune(line)
patRunes := []rune(pattern)
for i := 0; i <= len(lineRunes)-len(patRunes); i++ {
match := true
for j, r := range patRunes {
if lineRunes[i+j] != r {
match = false
break
}
}
if match {
m.searchMatches = append(m.searchMatches, struct{ line, col int }{lineNum, i})
}
}
}
if len(m.searchMatches) > 0 {
match := m.searchMatches[0]
m.cursorY = match.line
m.cursorX = match.col
m.searchIdx = 0
m.SetStatusMsg("Found: " + pattern)
} else {
m.SetStatusMsg("Pattern not found: " + pattern)
}
}
func (m *EditorModel) nextMatch() {
if len(m.searchMatches) == 0 {
return
}
m.searchIdx = (m.searchIdx + 1) % len(m.searchMatches)
match := m.searchMatches[m.searchIdx]
m.cursorY = match.line
m.cursorX = match.col
}
func (m *EditorModel) prevMatch() {
if len(m.searchMatches) == 0 {
return
}
m.searchIdx--
if m.searchIdx < 0 {
m.searchIdx = len(m.searchMatches) - 1
}
match := m.searchMatches[m.searchIdx]
m.cursorY = match.line
m.cursorX = match.col
}
// === ЗАМЕНА ===
func (m *EditorModel) replaceAll(old, new string) int {
if old == "" {
return 0
}
count := 0
for i, line := range m.lines {
newLine := strings.ReplaceAll(line, old, new)
if newLine != line {
count += strings.Count(line, old)
m.lines[i] = newLine
m.MarkDirty()
}
}
return count
}
func (m *EditorModel) replaceInLine(old, new string) int {
if old == "" || m.cursorY >= len(m.lines) {
return 0
}
line := m.lines[m.cursorY]
newLine := strings.Replace(line, old, new, 1)
if newLine != line {
m.lines[m.cursorY] = newLine
m.MarkDirty()
return 1
}
return 0
}
// === ЧИСЛОВЫЕ ПРЕФИКСЫ ===
func (m *EditorModel) parseNumberPrefix() int {
if m.numPrefix == "" {
return 1
}
result := 0
for _, ch := range m.numPrefix {
result = result*10 + int(ch-'0')
}
m.numPrefix = ""
return result
}
// === НАВИГАЦИЯ ===
func (m *EditorModel) moveByWords(count int, forward bool) {
for i := 0; i < count; i++ {
if forward {
m.moveNextWord()
} else {
m.movePrevWord()
}
}
}
func (m *EditorModel) moveByLines(count int, direction int) {
m.cursorY += count * direction
m.CursorWithinBounds()
}
// findMatchingBracket — поиск парной скобки (%)
func (m *EditorModel) findMatchingBracket() {
if m.cursorY >= len(m.lines) {
return
}
line := []rune(m.lines[m.cursorY])
if m.cursorX >= len(line) {
return
}
char := line[m.cursorX]
var opening, closing rune
var isOpening bool
switch char {
case '(':
opening, closing, isOpening = '(', ')', true
case ')':
opening, closing, isOpening = '(', ')', false
case '[':
opening, closing, isOpening = '[', ']', true
case ']':
opening, closing, isOpening = '[', ']', false
case '{':
opening, closing, isOpening = '{', '}', true
case '}':
opening, closing, isOpening = '{', '}', false
default:
return
}
if isOpening {
count := 1
for y := m.cursorY; y < len(m.lines); y++ {
lr := []rune(m.lines[y])
startX := 0
if y == m.cursorY {
startX = m.cursorX + 1
}
for x := startX; x < len(lr); x++ {
if lr[x] == opening {
count++
} else if lr[x] == closing {
count--
if count == 0 {
m.cursorY = y
m.cursorX = x
return
}
}
}
}
} else {
count := 1
for y := m.cursorY; y >= 0; y-- {
lr := []rune(m.lines[y])
endX := len(lr)
if y == m.cursorY {
endX = m.cursorX
}
for x := endX - 1; x >= 0; x-- {
if lr[x] == closing {
count++
} else if lr[x] == opening {
count--
if count == 0 {
m.cursorY = y
m.cursorX = x
return
}
}
}
}
}
m.SetStatusMsg("No matching bracket")
}