123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
- package diff
- import (
- "fmt"
- "regexp"
- "strings"
- "sync"
- "kitty/tools/tui"
- "kitty/tools/utils"
- "kitty/tools/utils/images"
- "kitty/tools/wcswidth"
- "golang.org/x/exp/slices"
- )
- var _ = fmt.Print
- type Search struct {
- pat *regexp.Regexp
- matches map[ScrollPos][]Span
- }
- func (self *Search) Len() int { return len(self.matches) }
- func (self *Search) find_matches_in_lines(clean_lines []string, origin int, send_result func(screen_line, offset, size int)) {
- lengths := utils.Map(func(x string) int { return len(x) }, clean_lines)
- offsets := make([]int, len(clean_lines))
- cell_lengths := utils.Map(wcswidth.Stringwidth, clean_lines)
- cell_offsets := make([]int, len(clean_lines))
- for i := range clean_lines {
- if i > 0 {
- offsets[i] = offsets[i-1] + lengths[i-1]
- cell_offsets[i] = cell_offsets[i-1] + cell_lengths[i-1]
- }
- }
- joined_text := strings.Join(clean_lines, "")
- matches := self.pat.FindAllStringIndex(joined_text, -1)
- pos := 0
- find_pos := func(start int) int {
- for i := pos; i < len(clean_lines); i++ {
- if start < offsets[i]+lengths[i] {
- pos = i
- return pos
- }
- }
- return -1
- }
- for _, m := range matches {
- start, end := m[0], m[1]
- total_size := end - start
- if total_size < 1 {
- continue
- }
- start_line := find_pos(start)
- if start_line > -1 {
- end_line := find_pos(end)
- if end_line > -1 {
- for i := start_line; i <= end_line; i++ {
- cell_start := 0
- if i == start_line {
- byte_offset := start - offsets[i]
- cell_start = wcswidth.Stringwidth(clean_lines[i][:byte_offset])
- }
- cell_end := cell_lengths[i]
- if i == end_line {
- byte_offset := end - offsets[i]
- cell_end = wcswidth.Stringwidth(clean_lines[i][:byte_offset])
- }
- send_result(i, origin+cell_start, cell_end-cell_start)
- }
- }
- }
- }
- }
- func (self *Search) find_matches_in_line(line *LogicalLine, margin_size, cols int, send_result func(screen_line, offset, size int)) {
- half_width := cols / 2
- right_offset := half_width + margin_size
- left_clean_lines, right_clean_lines := make([]string, len(line.screen_lines)), make([]string, len(line.screen_lines))
- for i, sl := range line.screen_lines {
- if line.is_full_width {
- left_clean_lines[i] = wcswidth.StripEscapeCodes(sl.left.marked_up_text)
- } else {
- left_clean_lines[i] = wcswidth.StripEscapeCodes(sl.left.marked_up_text)
- right_clean_lines[i] = wcswidth.StripEscapeCodes(sl.right.marked_up_text)
- }
- }
- self.find_matches_in_lines(left_clean_lines, margin_size, send_result)
- self.find_matches_in_lines(right_clean_lines, right_offset, send_result)
- }
- func (self *Search) Has(pos ScrollPos) bool {
- return len(self.matches[pos]) > 0
- }
- type Span struct{ start, end int }
- func (self *Search) search(logical_lines *LogicalLines) {
- margin_size := logical_lines.margin_size
- cols := logical_lines.columns
- self.matches = make(map[ScrollPos][]Span)
- ctx := images.Context{}
- mutex := sync.Mutex{}
- ctx.Parallel(0, logical_lines.Len(), func(nums <-chan int) {
- for i := range nums {
- line := logical_lines.At(i)
- if line.line_type == EMPTY_LINE || line.line_type == IMAGE_LINE {
- continue
- }
- self.find_matches_in_line(line, margin_size, cols, func(screen_line, offset, size int) {
- if size > 0 {
- mutex.Lock()
- defer mutex.Unlock()
- pos := ScrollPos{i, screen_line}
- self.matches[pos] = append(self.matches[pos], Span{offset, offset + size - 1})
- }
- })
- }
- })
- for _, spans := range self.matches {
- slices.SortFunc(spans, func(a, b Span) int { return a.start - b.start })
- }
- }
- func (self *Search) markup_line(pos ScrollPos, y int) string {
- spans := self.matches[pos]
- if spans == nil {
- return ""
- }
- sgr := format_as_sgr.search[2:]
- sgr = sgr[:len(sgr)-1]
- ans := make([]byte, 0, 32)
- for _, span := range spans {
- ans = append(ans, tui.FormatPartOfLine(sgr, span.start, span.end, y)...)
- }
- return utils.UnsafeBytesToString(ans)
- }
- func do_search(pat *regexp.Regexp, logical_lines *LogicalLines) *Search {
- ans := &Search{pat: pat, matches: make(map[ScrollPos][]Span)}
- ans.search(logical_lines)
- return ans
- }
|