aboutsummaryrefslogtreecommitdiffhomepage
path: root/core/ext/find.lua
blob: 3b57185786b2e074e4c6f98564a6abfaad1349c6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
-- Copyright 2007 Mitchell mitchell<att>caladbolg.net. See LICENSE.

local find = textadept.find

---
-- [Local table] Text escape sequences with their associated characters.
-- @class table
-- @name escapes
local escapes = {
  ['\\a'] = '\a', ['\\b'] = '\b', ['\\f'] = '\f', ['\\n'] = '\n',
  ['\\r'] = '\r', ['\\t'] = '\t', ['\\v'] = '\v', ['\\\\'] = '\\'
}

---
-- Finds and selects text in the current buffer.
-- This is used by the find dialog. It is recommended to use the buffer:find()
-- function for scripting.
-- @param text The text to find.
-- @param flags Search flags. This is a number mask of 3 flags: match case (2),
--   whole word (4), and Lua pattern (8) joined with binary AND.
-- @param next Flag indicating whether or not the search direction is forward.
-- @param nowrap Flag indicating whether or not the search won't wrap.
-- @param wrapped Utility flag indicating whether or not the search has wrapped
--   for displaying useful statusbar information. This flag is used and set
--   internally, and should not be set otherwise.
function find.find(text, flags, next, nowrap, wrapped)
  local buffer = buffer
  local increment, result
  text = text:gsub('\\[abfnrtv\\]', escapes)
  find.captures = nil
  if buffer.current_pos == buffer.anchor then
    increment = 0
  elseif not wrapped then
    increment = next and 1 or -1
  end
  if flags < 8 then
    buffer:goto_pos( buffer[next and 'current_pos' or 'anchor'] + increment )
    buffer:search_anchor()
    if next then
      result = buffer:search_next(flags, text)
    else
      result = buffer:search_prev(flags, text)
    end
    if result then buffer:scroll_caret() end
  else -- lua pattern search (forward search only)
    local buffer_text = buffer:get_text(buffer.length)
    local results = { buffer_text:find(text, buffer.anchor + increment) }
    if #results > 0 then
      result = results[1]
      find.captures = { unpack(results, 3) }
      buffer:set_sel(results[2], result - 1)
    else
      result = -1
    end
  end
  if result == -1 and not nowrap and not wrapped then -- wrap the search
    local anchor, pos = buffer.anchor, buffer.current_pos
    if next or flags >= 8 then
      buffer:goto_pos(0)
    else
      buffer:goto_pos(buffer.length)
    end
    textadept.statusbar_text = 'Search wrapped'
    result = find.find(text, flags, next, true, true)
    if not result then
      textadept.statusbar_text = 'No results found'
      buffer:goto_pos(anchor)
    end
    return result
  elseif result ~= -1 and not wrapped then
    textadept.statusbar_text = ''
  end
  return result ~= -1
end

---
-- Replaces found text.
-- This function is used by the find dialog. It is not recommended to call it
-- via scripts.
-- textadept.find.find is called first, to select any found text. The selected
-- text is then replaced by the specified replacement text.
-- @param rtext The text to replace found text with. It can contain both Lua
--   capture items (%n where 1 <= n <= 9) for Lua pattern searches and %()
--   sequences for embedding Lua code for any search.
function find.replace(rtext)
  if #buffer:get_sel_text() == 0 then return end
  local buffer = buffer
  buffer:target_from_selection()
  rtext = rtext:gsub('%%%%', '\\037') -- escape '%%'
  if find.captures then
    for i, v in ipairs(find.captures) do
      rtext = rtext:gsub('%%'..i, v)
    end
  end
  local ret, rtext = pcall( rtext.gsub, rtext, '%%(%b())',
    function(code)
      local ret, val = pcall( loadstring('return '..code) )
      if not ret then
        cocoa_dialog( 'msgbox', {
          title = 'Error',
          text = 'An error occured:',
          ['informative-text'] = val:gsub('"', '\\"')
        } )
        error()
      end
      return val
    end )
  if ret then
    rtext = rtext:gsub('\\037', '%%') -- unescape '%'
    buffer:replace_target( rtext:gsub('\\[abfnrtv\\]', escapes) )
    buffer:goto_pos(buffer.target_end + 1) -- 'find' text after this replacement
  else
    -- Since find is called after replace returns, have it 'find' the current
    -- text again, rather than the next occurance so the user can fix the error.
    buffer:goto_pos(buffer.current_pos)
  end
end

---
-- Replaces all found text.
-- This function is used by the find dialog. It is not recommended to call it
-- via scripts.
-- @param ftext The text to find.
-- @param rtext The text to replace found text with.
-- @param flags The number mask identical to the one in 'find'.
-- @see find.find
function find.replace_all(ftext, rtext, flags)
  buffer:goto_pos(0)
  local count = 0
  while( find.find(ftext, flags, true, true) ) do
    find.replace(rtext)
    count = count + 1
  end
  textadept.statusbar_text = tostring(count)..' replacement(s) made'
end