grimoir/search.go
2026-07-04 13:04:33 +03:00

302 lines
No EOL
6 KiB
Go
Executable file
Raw Permalink 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 (
"fmt"
"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("/")
}
// maxSearchMatches — максимум совпадений при поиске
// Предотвращает зависание на файлах с сотнями тысяч строк
const maxSearchMatches = 1000
func (m *EditorModel) performSearch(pattern string) {
m.lastSearchTerm = pattern
m.searchMatches = nil
m.searchIdx = 0
if pattern == "" {
return
}
patRunes := []rune(pattern)
truncated := false
for lineNum, line := range m.lines {
lineRunes := []rune(line)
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) >= maxSearchMatches {
truncated = true
break
}
}
}
if truncated {
break
}
}
if len(m.searchMatches) > 0 {
match := m.searchMatches[0]
m.cursorY = match.line
m.cursorX = match.col
m.searchIdx = 0
if truncated {
m.SetStatusMsg(fmt.Sprintf("Found %d+ matches (showing first %d): %s",
maxSearchMatches, maxSearchMatches, pattern))
} else {
m.SetStatusMsg(fmt.Sprintf("Found %d match(es): %s", len(m.searchMatches), 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")
}