redcar 0.10 → 0.11.0dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +14 -0
- data/Rakefile +68 -29
- data/lib/redcar.rb +2 -2
- data/plugins/application/lib/application/dialogs/filter_list_dialog.rb +2 -1
- data/plugins/application_swt/spec/application_swt/gradient_spec.rb +3 -12
- data/plugins/core/lib/core/resource.rb +13 -5
- data/plugins/document_search/features/find.feature +366 -214
- data/plugins/document_search/features/incremental_search.feature +351 -0
- data/plugins/document_search/features/replace.feature +16 -16
- data/plugins/document_search/features/step_definitions/find_steps.rb +16 -0
- data/plugins/document_search/features/support/env.rb +11 -0
- data/plugins/document_search/lib/document_search.rb +149 -109
- data/plugins/document_search/lib/document_search/commands.rb +251 -202
- data/plugins/document_search/lib/document_search/find_speedbar.rb +138 -81
- data/plugins/document_search/lib/document_search/incremental_search_speedbar.rb +70 -0
- data/plugins/document_search/lib/document_search/query_options.rb +15 -39
- data/plugins/document_search/plugin.rb +1 -1
- data/plugins/edit_view/features/step_definitions/editing_steps.rb +6 -2
- data/plugins/edit_view_swt/lib/edit_view_swt.rb +2 -2
- data/plugins/file_parser/lib/file_parser.rb +6 -1
- data/plugins/html_view/features/step_definitions/web_view_steps.rb +12 -0
- data/plugins/html_view/features/support/env.rb +16 -0
- data/plugins/html_view/lib/html_view.rb +5 -1
- data/plugins/line_tools/lib/line_tools.rb +16 -0
- data/plugins/project/features/find_file.feature +28 -0
- data/plugins/project/features/open_and_save_files.feature +11 -0
- data/plugins/project/features/step_definitions/file_steps.rb +6 -1
- data/plugins/project/features/support/env.rb +2 -0
- data/plugins/project/lib/project.rb +49 -6
- data/plugins/project/lib/project/commands.rb +18 -6
- data/plugins/project/lib/project/find_file_dialog.rb +19 -8
- data/plugins/project/lib/project/find_recent_dialog.rb +30 -0
- data/plugins/project/lib/project/manager.rb +41 -10
- data/plugins/project/lib/project/recent.rb +64 -0
- data/plugins/project/spec/fixtures/myproject/vendor/bar.rb +0 -0
- data/plugins/project/spec/fixtures/myproject/vendor/plugins/bar.rb +0 -0
- data/plugins/{find-in-project → project_search}/TODO.md +3 -3
- data/plugins/project_search/features/support/env.rb +6 -0
- data/plugins/project_search/features/word_search.feature +34 -0
- data/plugins/project_search/lib/project_search.rb +73 -0
- data/plugins/project_search/lib/project_search/binary_data_detector.rb +46 -0
- data/plugins/project_search/lib/project_search/commands.rb +62 -0
- data/plugins/project_search/lib/project_search/hit.rb +17 -0
- data/plugins/{find-in-project/lib/find_in_project → project_search/lib/project_search}/images/collapsed.png +0 -0
- data/plugins/{find-in-project/lib/find_in_project → project_search/lib/project_search}/images/expanded.png +0 -0
- data/plugins/{find-in-project/lib/find_in_project → project_search/lib/project_search}/images/spinner.gif +0 -0
- data/plugins/project_search/lib/project_search/lucene_index.rb +64 -0
- data/plugins/project_search/lib/project_search/lucene_refresh.rb +22 -0
- data/plugins/project_search/lib/project_search/project.rb +14 -0
- data/plugins/project_search/lib/project_search/query.rb +29 -0
- data/plugins/{find-in-project/lib/find_in_project → project_search/lib/project_search}/stylesheets/style.css +14 -3
- data/plugins/project_search/lib/project_search/views/_file.html.erb +60 -0
- data/plugins/{find-in-project/lib/find_in_project → project_search/lib/project_search}/views/index.html.erb +12 -9
- data/plugins/project_search/lib/project_search/word_search.rb +105 -0
- data/plugins/project_search/lib/project_search/word_search_controller.rb +207 -0
- data/plugins/project_search/plugin.rb +8 -0
- data/plugins/project_search/spec/fixtures/project/binary_file.bin +0 -0
- data/plugins/project_search/spec/fixtures/project/foo.txt +43 -0
- data/plugins/project_search/spec/fixtures/project/qux.rb +3 -0
- data/plugins/project_search/spec/project_search/binary_data_detector_spec.rb +24 -0
- data/plugins/project_search/spec/project_search/word_search_spec.rb +157 -0
- data/plugins/project_search/spec/spec_helper.rb +27 -0
- data/plugins/redcar/redcar.rb +77 -71
- data/plugins/ruby/lib/ruby/syntax_checker.rb +1 -1
- data/plugins/snippets/features/snippets.feature +12 -0
- data/plugins/snippets/lib/snippets/tab_handler.rb +1 -1
- metadata +46 -25
- data/plugins/document_search/features/find_and_replace.feature +0 -723
- data/plugins/document_search/lib/document_search/find_and_replace_speedbar.rb +0 -142
- data/plugins/find-in-project/lib/find_in_project.rb +0 -35
- data/plugins/find-in-project/lib/find_in_project/commands.rb +0 -30
- data/plugins/find-in-project/lib/find_in_project/controllers.rb +0 -170
- data/plugins/find-in-project/lib/find_in_project/views/_divider.html.erb +0 -4
- data/plugins/find-in-project/lib/find_in_project/views/_file_heading.html.erb +0 -10
- data/plugins/find-in-project/lib/find_in_project/views/_file_line.html.erb +0 -6
- data/plugins/find-in-project/plugin.rb +0 -11
- data/plugins/project/lib/project/recent_directories.rb +0 -54
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
When /^I open the incremental search speedbar$/ do
|
3
|
+
Redcar::DocumentSearch::OpenIncrementalSearchSpeedbarCommand.new.run
|
4
|
+
end
|
5
|
+
|
6
|
+
When /^I open the find speedbar$/ do
|
7
|
+
Redcar::DocumentSearch::OpenFindSpeedbarCommand.new.run
|
8
|
+
end
|
9
|
+
|
10
|
+
Then /^I should see the incremental search speedbar$/ do
|
11
|
+
Then "the Redcar::DocumentSearch::IncrementalSearchSpeedbar speedbar should be open"
|
12
|
+
end
|
13
|
+
|
14
|
+
Then /^I should see the find speedbar$/ do
|
15
|
+
Then "the Redcar::DocumentSearch::FindSpeedbar speedbar should be open"
|
16
|
+
end
|
@@ -1,3 +1,14 @@
|
|
1
1
|
|
2
|
+
def reset_search_settings(options)
|
3
|
+
if options
|
4
|
+
options.is_regex = Redcar::DocumentSearch::QueryOptions::DEFAULT_IS_REGEX
|
5
|
+
options.match_case = Redcar::DocumentSearch::QueryOptions::DEFAULT_MATCH_CASE
|
6
|
+
options.wrap_around = Redcar::DocumentSearch::QueryOptions::DEFAULT_WRAP_AROUND
|
7
|
+
end
|
8
|
+
end
|
2
9
|
|
10
|
+
Before do
|
11
|
+
reset_search_settings(Redcar::DocumentSearch::IncrementalSearchSpeedbar.previous_options)
|
12
|
+
reset_search_settings(Redcar::DocumentSearch::FindSpeedbar.previous_options)
|
13
|
+
end
|
3
14
|
|
@@ -2,150 +2,190 @@ require 'strscan'
|
|
2
2
|
require "document_search/query_options"
|
3
3
|
require "document_search/commands"
|
4
4
|
require "document_search/find_speedbar"
|
5
|
-
require "document_search/
|
6
|
-
|
7
|
-
module
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
sub_menu "
|
12
|
-
|
13
|
-
|
5
|
+
require "document_search/incremental_search_speedbar"
|
6
|
+
|
7
|
+
module Redcar
|
8
|
+
module DocumentSearch
|
9
|
+
def self.menus
|
10
|
+
Redcar::Menu::Builder.build do
|
11
|
+
sub_menu "Edit" do
|
12
|
+
sub_menu "Find", :priority => 50 do
|
13
|
+
item "Incremental Search", OpenIncrementalSearchSpeedbarCommand
|
14
|
+
item "Find...", OpenFindSpeedbarCommand
|
15
|
+
separator
|
16
|
+
item "Find Next", DoFindNextCommand
|
17
|
+
item "Find Previous", DoFindPreviousCommand
|
18
|
+
separator
|
19
|
+
item "Replace All", DoReplaceAllCommand
|
20
|
+
item "Replace All in Selection", DoReplaceAllInSelectionCommand
|
21
|
+
item "Replace and Find", DoReplaceAndFindCommand
|
22
|
+
separator
|
23
|
+
item "Use Selection for Find", DoUseSelectionForFindCommand
|
24
|
+
item "Use Selection for Replace", DoUseSelectionForReplaceCommand
|
25
|
+
end
|
14
26
|
separator
|
15
|
-
item "Find Next", FindNextMenuCommand
|
16
|
-
item "Find Previous", FindPreviousMenuCommand
|
17
|
-
item "Replace and Find", ReplaceAndFindMenuCommand
|
18
|
-
separator
|
19
|
-
item "Use Selection for Find", UseSelectionForFindMenuCommand
|
20
|
-
item "Use Selection for Replace", UseSelectionForReplaceMenuCommand
|
21
27
|
end
|
22
|
-
separator
|
23
28
|
end
|
24
29
|
end
|
25
|
-
end
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
def self.keymaps
|
32
|
+
osx = Redcar::Keymap.build("main", :osx) do
|
33
|
+
link "Ctrl+S", DocumentSearch::OpenIncrementalSearchSpeedbarCommand
|
34
|
+
link "Cmd+F", DocumentSearch::OpenFindSpeedbarCommand
|
35
|
+
link "Cmd+G", DocumentSearch::DoFindNextCommand
|
36
|
+
link "Cmd+Shift+G", DocumentSearch::DoFindPreviousCommand
|
37
|
+
link "Cmd+Ctrl+F", DocumentSearch::DoReplaceAllCommand
|
38
|
+
link "Cmd+Ctrl+Shift+F", DocumentSearch::DoReplaceAllInSelectionCommand
|
39
|
+
link "Cmd+Alt+F", DocumentSearch::DoReplaceAndFindCommand
|
40
|
+
link "Cmd+E", DocumentSearch::DoUseSelectionForFindCommand
|
41
|
+
link "Cmd+Shift+E", DocumentSearch::DoUseSelectionForReplaceCommand
|
42
|
+
end
|
37
43
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
linwin = Redcar::Keymap.build("main", [:linux, :windows]) do
|
45
|
+
link "Alt+S", DocumentSearch::OpenIncrementalSearchSpeedbarCommand
|
46
|
+
link "Ctrl+F", DocumentSearch::OpenFindSpeedbarCommand
|
47
|
+
link "Ctrl+G", DocumentSearch::DoFindNextCommand
|
48
|
+
link "Ctrl+Shift+G", DocumentSearch::DoFindPreviousCommand
|
49
|
+
link "Ctrl+Alt+F", DocumentSearch::DoReplaceAndFindCommand
|
50
|
+
link "Ctrl+E", DocumentSearch::DoUseSelectionForFindCommand
|
51
|
+
link "Alt+Shift+E", DocumentSearch::DoUseSelectionForReplaceCommand
|
52
|
+
end
|
46
53
|
|
47
|
-
|
48
|
-
|
54
|
+
[linwin, osx]
|
55
|
+
end
|
49
56
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
57
|
+
def self.toolbars
|
58
|
+
Redcar::ToolBar::Builder.build do
|
59
|
+
item "Find", :command => DocumentSearch::OpenIncrementalSearchSpeedbarCommand, :icon => File.join(Redcar::ICONS_DIRECTORY, "magnifier.png"), :barname => :edit
|
60
|
+
item "Find Next", :command => DocumentSearch::DoFindNextCommand, :icon => File.join(Redcar::ICONS_DIRECTORY, "magnifier--arrow.png"), :barname => :edit
|
61
|
+
end
|
54
62
|
end
|
55
|
-
end
|
56
63
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
64
|
+
class OpenIncrementalSearchSpeedbarCommand < Redcar::EditTabCommand
|
65
|
+
def execute
|
66
|
+
already_open = win.speedbar.is_a? IncrementalSearchSpeedbar
|
67
|
+
@speedbar = IncrementalSearchSpeedbar.new
|
68
|
+
unless already_open
|
69
|
+
# Clear out previous query for new speedbar.
|
70
|
+
IncrementalSearchSpeedbar.previous_query = ''
|
71
|
+
win.open_speedbar(@speedbar)
|
72
|
+
else
|
73
|
+
# If already open, find next match.
|
74
|
+
win.open_speedbar(@speedbar)
|
75
|
+
IncrementalSearchSpeedbar.find_next
|
76
|
+
end
|
62
77
|
end
|
63
|
-
win.open_speedbar(@speedbar)
|
64
78
|
end
|
65
|
-
end
|
66
79
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
80
|
+
class OpenFindSpeedbarCommand < Redcar::EditTabCommand
|
81
|
+
def execute
|
82
|
+
@speedbar = FindSpeedbar.new
|
83
|
+
if doc.selection?
|
84
|
+
@speedbar.initial_query = doc.selected_text
|
85
|
+
end
|
86
|
+
win.open_speedbar(@speedbar)
|
72
87
|
end
|
73
|
-
win.open_speedbar(@speedbar)
|
74
88
|
end
|
75
|
-
end
|
76
89
|
|
77
|
-
|
78
|
-
|
79
|
-
|
90
|
+
class DoFindNextCommand < Redcar::EditTabCommand
|
91
|
+
def execute
|
92
|
+
if win.speedbar.is_a? IncrementalSearchSpeedbar
|
93
|
+
IncrementalSearchSpeedbar.find_next
|
94
|
+
else
|
95
|
+
FindSpeedbar.find_next
|
96
|
+
end
|
97
|
+
end
|
80
98
|
end
|
81
|
-
end
|
82
99
|
|
83
|
-
|
84
|
-
|
85
|
-
|
100
|
+
class DoFindPreviousCommand < Redcar::EditTabCommand
|
101
|
+
def execute
|
102
|
+
if win.speedbar.is_a? IncrementalSearchSpeedbar
|
103
|
+
IncrementalSearchSpeedbar.find_previous
|
104
|
+
else
|
105
|
+
FindSpeedbar.find_previous
|
106
|
+
end
|
107
|
+
end
|
86
108
|
end
|
87
|
-
end
|
88
109
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
110
|
+
class DoReplaceAndFindCommand < Redcar::EditTabCommand
|
111
|
+
def execute
|
112
|
+
FindSpeedbar.replace_and_find(
|
113
|
+
FindSpeedbar.previous_query,
|
114
|
+
FindSpeedbar.previous_replace,
|
115
|
+
FindSpeedbar.previous_options)
|
116
|
+
end
|
95
117
|
end
|
96
|
-
end
|
97
118
|
|
98
|
-
|
99
|
-
|
100
|
-
|
119
|
+
class DoReplaceAllCommand < Redcar::EditTabCommand
|
120
|
+
def execute
|
121
|
+
FindSpeedbar.replace_all(
|
122
|
+
FindSpeedbar.previous_query,
|
123
|
+
FindSpeedbar.previous_replace,
|
124
|
+
FindSpeedbar.previous_options)
|
125
|
+
end
|
101
126
|
end
|
102
|
-
end
|
103
127
|
|
104
|
-
|
105
|
-
|
106
|
-
|
128
|
+
class DoReplaceAllInSelectionCommand < Redcar::EditTabCommand
|
129
|
+
def execute
|
130
|
+
FindSpeedbar.replace_all_in_selection(
|
131
|
+
FindSpeedbar.previous_query,
|
132
|
+
FindSpeedbar.previous_replace,
|
133
|
+
FindSpeedbar.previous_options)
|
134
|
+
end
|
107
135
|
end
|
108
|
-
end
|
109
136
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
@wrap = wrap
|
137
|
+
class DoUseSelectionForFindCommand < Redcar::EditTabCommand
|
138
|
+
def execute
|
139
|
+
FindSpeedbar.use_selection_for_find(doc, win.speedbar)
|
140
|
+
end
|
115
141
|
end
|
116
142
|
|
117
|
-
|
118
|
-
|
143
|
+
class DoUseSelectionForReplaceCommand < Redcar::EditTabCommand
|
144
|
+
def execute
|
145
|
+
FindSpeedbar.use_selection_for_replace(doc, win.speedbar)
|
146
|
+
end
|
119
147
|
end
|
120
148
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
149
|
+
# TODO(yozhipozhi): Figure out if this is still needed.
|
150
|
+
class FindNextRegex < Redcar::DocumentCommand
|
151
|
+
def initialize(re, wrap=nil)
|
152
|
+
@re = re
|
153
|
+
@wrap = wrap
|
154
|
+
end
|
126
155
|
|
127
|
-
|
128
|
-
|
129
|
-
sc.reset
|
130
|
-
sc.scan_until(@re)
|
156
|
+
def to_s
|
157
|
+
"<#{self.class}: @re:#{@re.inspect} wrap:#{!!@wrap}>"
|
131
158
|
end
|
132
159
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
160
|
+
def execute
|
161
|
+
position = doc.cursor_offset
|
162
|
+
sc = StringScanner.new(doc.get_all_text)
|
163
|
+
sc.pos = position
|
164
|
+
sc.scan_until(@re)
|
165
|
+
|
166
|
+
if @wrap and !sc.matched?
|
167
|
+
# No match was found in the remainder of the document, search from beginning
|
168
|
+
sc.reset
|
169
|
+
sc.scan_until(@re)
|
170
|
+
end
|
171
|
+
|
172
|
+
if sc.matched?
|
173
|
+
endoff = sc.pos
|
174
|
+
startoff = sc.pos - sc.matched_size
|
175
|
+
line = doc.line_at_offset(startoff)
|
176
|
+
lineoff = startoff - doc.offset_at_line(line)
|
177
|
+
if lineoff < doc.smallest_visible_horizontal_index
|
178
|
+
horiz = lineoff
|
179
|
+
else
|
180
|
+
horiz = endoff - doc.offset_at_line(line)
|
181
|
+
end
|
182
|
+
doc.set_selection_range(sc.pos, sc.pos - sc.matched_size)
|
183
|
+
doc.scroll_to_line(line)
|
184
|
+
doc.scroll_to_horizontal_offset(horiz) if horiz
|
185
|
+
return true
|
142
186
|
end
|
143
|
-
|
144
|
-
doc.scroll_to_line(line)
|
145
|
-
doc.scroll_to_horizontal_offset(horiz) if horiz
|
146
|
-
return true
|
187
|
+
false
|
147
188
|
end
|
148
|
-
false
|
149
189
|
end
|
150
190
|
end
|
151
191
|
end
|
@@ -1,257 +1,306 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# An instance of a search type method: Plain text search
|
12
|
-
def query_plain(query, options)
|
13
|
-
query_regex(Regexp.escape(query), options)
|
14
|
-
end
|
1
|
+
module Redcar
|
2
|
+
module DocumentSearch
|
3
|
+
# Utilities for extended search commands.
|
4
|
+
module FindCommandMixin
|
5
|
+
### QUERY PATTERNS ###
|
6
|
+
|
7
|
+
# Indicates if the query is valid.
|
8
|
+
def is_valid(query)
|
9
|
+
query.inspect != "//i"
|
10
|
+
end
|
15
11
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# convert the glob pattern to a regex pattern
|
20
|
-
new_query = ""
|
21
|
-
query.each_char do |c|
|
22
|
-
case c
|
23
|
-
when "*"
|
24
|
-
new_query << ".*"
|
25
|
-
when "?"
|
26
|
-
new_query << "."
|
27
|
-
else
|
28
|
-
new_query << Regexp.escape(c)
|
29
|
-
end
|
12
|
+
# An instance of a search type method: Regular expression
|
13
|
+
def make_regex_query(query, options)
|
14
|
+
Regexp.new(query, !options.match_case)
|
30
15
|
end
|
31
|
-
query_regex(new_query, options)
|
32
|
-
end
|
33
16
|
|
34
|
-
|
17
|
+
# An instance of a search type method: Plain text search
|
18
|
+
def make_literal_query(query, options)
|
19
|
+
make_regex_query(Regexp.escape(query), options)
|
20
|
+
end
|
35
21
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
22
|
+
### SELECTION ###
|
23
|
+
|
24
|
+
# Returns the document selection range as byte offsets, adjusting for multi-byte characters.
|
25
|
+
def selection_byte_offsets
|
26
|
+
char_offsets = [doc.cursor_offset, doc.selection_offset]
|
27
|
+
min_char_offset = char_offsets.min
|
28
|
+
max_char_offset = char_offsets.max
|
29
|
+
|
30
|
+
# For the min_byte_offset, get all document text before the selection, and count the bytes.
|
31
|
+
min_byte_offset = doc.get_range(0, min_char_offset).size
|
32
|
+
# If the selection is non-empty, count the bytes in the selection text, too.
|
33
|
+
max_byte_offset = (min_byte_offset +
|
34
|
+
(max_char_offset > min_char_offset ?
|
35
|
+
doc.get_slice(min_char_offset, max_char_offset).size :
|
36
|
+
0))
|
37
|
+
[min_byte_offset, max_byte_offset]
|
38
|
+
end
|
44
39
|
|
45
|
-
|
40
|
+
# Selects the first match of query, starting from the start_pos.
|
41
|
+
def select_next_match(doc, start_pos, query, wrap_around)
|
42
|
+
return false unless is_valid(query)
|
43
|
+
scanner = StringScanner.new(doc.get_all_text)
|
44
|
+
scanner.pos = start_pos
|
46
45
|
if not scanner.scan_until(query)
|
47
|
-
|
46
|
+
if not wrap_around
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
|
50
|
+
scanner.reset
|
51
|
+
if not scanner.scan_until(query)
|
52
|
+
return false
|
53
|
+
end
|
48
54
|
end
|
55
|
+
|
56
|
+
selection_pos = scanner.pos - scanner.matched_size
|
57
|
+
select_range_bytes(selection_pos, scanner.pos)
|
58
|
+
true
|
49
59
|
end
|
50
60
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
61
|
+
# Selects the match that first precedes the search position.
|
62
|
+
#
|
63
|
+
# The current implementation is brain-dead, but works: the document is scanned from the start
|
64
|
+
# up to the search position, retaining the most recent match. Many smarter, but more
|
65
|
+
# complicated strategies are possible; the best would be full reversal of the query regex, but
|
66
|
+
# that obviously has a lot of tricky aspects to it.
|
67
|
+
def select_previous_match(doc, search_pos, query, wrap_around)
|
68
|
+
return false unless is_valid(query)
|
69
|
+
previous_match = nil
|
70
|
+
scanner = StringScanner.new(doc.get_all_text)
|
71
|
+
scanner.pos = 0
|
72
|
+
while scanner.scan_until(query)
|
73
|
+
start_pos = scanner.pos - scanner.matched_size
|
74
|
+
if start_pos < search_pos
|
75
|
+
previous_match = [start_pos, scanner.pos]
|
76
|
+
elsif previous_match
|
77
|
+
select_range_bytes(*previous_match)
|
78
|
+
return true
|
79
|
+
elsif not wrap_around
|
80
|
+
return false
|
81
|
+
else
|
82
|
+
break
|
83
|
+
end
|
84
|
+
end
|
55
85
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# up to the search position, retaining the most recent match. Many smarter, but more
|
60
|
-
# complicated strategies are possible; the best would be full reversal of the query regex, but
|
61
|
-
# that obviously has a lot of tricky aspects to it.
|
62
|
-
def select_previous_match(doc, search_pos, query, wrap_around)
|
63
|
-
previous_match = nil
|
64
|
-
scanner = StringScanner.new(doc.get_all_text)
|
65
|
-
scanner.pos = 0
|
66
|
-
while scanner.scan_until(query)
|
67
|
-
start_pos = scanner.pos - scanner.matched_size
|
68
|
-
if start_pos < search_pos
|
86
|
+
# Find the last match in the document.
|
87
|
+
while scanner.scan_until(query)
|
88
|
+
start_pos = scanner.pos - scanner.matched_size
|
69
89
|
previous_match = [start_pos, scanner.pos]
|
70
|
-
|
71
|
-
|
90
|
+
end
|
91
|
+
|
92
|
+
if previous_match
|
93
|
+
select_range_bytes(*previous_match)
|
72
94
|
return true
|
73
|
-
elsif not wrap_around
|
74
|
-
return false
|
75
95
|
else
|
76
|
-
|
96
|
+
return false
|
77
97
|
end
|
78
98
|
end
|
79
99
|
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
100
|
+
# Replaces the current selection, if it matches the query completely.
|
101
|
+
def replace_selection_if_match(doc, start_pos, query, replace)
|
102
|
+
scanner = StringScanner.new(doc.selected_text)
|
103
|
+
scanner.check(query)
|
104
|
+
if (not scanner.matched?) || (scanner.matched_size != doc.selected_text.length)
|
105
|
+
return 0
|
106
|
+
end
|
107
|
+
matched_text = doc.get_range(start_pos, scanner.matched_size)
|
108
|
+
replacement_text = matched_text.gsub(query, replace)
|
109
|
+
doc.replace(start_pos, scanner.matched_size, replacement_text)
|
110
|
+
replacement_text.length
|
91
111
|
end
|
92
|
-
end
|
93
112
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
113
|
+
# Selects the specified range and scrolls to the start.
|
114
|
+
def select_range(start, stop)
|
115
|
+
line = doc.line_at_offset(start)
|
116
|
+
lineoff = start - doc.offset_at_line(line)
|
117
|
+
if lineoff < doc.smallest_visible_horizontal_index
|
118
|
+
horiz = lineoff
|
119
|
+
else
|
120
|
+
horiz = stop - doc.offset_at_line(line)
|
121
|
+
end
|
122
|
+
doc.set_selection_range(start, stop)
|
123
|
+
doc.scroll_to_line(line)
|
124
|
+
doc.scroll_to_horizontal_offset(horiz) if horiz
|
100
125
|
end
|
101
|
-
matched_text = doc.get_range(start_pos, scanner.matched_size)
|
102
|
-
replacement_text = matched_text.gsub(query, replace)
|
103
|
-
doc.replace(start_pos, scanner.matched_size, replacement_text)
|
104
|
-
replacement_text.length
|
105
|
-
end
|
106
126
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
127
|
+
# Selects the specified byte range, mapping to character indices first.
|
128
|
+
#
|
129
|
+
# This method is necessary, because Ruby (1.8) strings really work in terms of bytes, and thus
|
130
|
+
# our regex and scanning matches return byte ranges, while the editor view deals in terms of
|
131
|
+
# character ranges.
|
132
|
+
def select_range_bytes(start, stop)
|
133
|
+
text = doc.get_all_text
|
134
|
+
# Unpack span up to start into array of Unicode chars and count for start_chars.
|
135
|
+
start_chars = text.slice(0, start).unpack('U*').size
|
136
|
+
# Do the same for the span between start and stop, and then use to compute stop_chars.
|
137
|
+
char_span = text.slice(start, stop - start).unpack('U*').size
|
138
|
+
stop_chars = start_chars + char_span
|
139
|
+
select_range(start_chars, stop_chars)
|
115
140
|
end
|
116
|
-
doc.set_selection_range(start, stop)
|
117
|
-
doc.scroll_to_line(line)
|
118
|
-
doc.scroll_to_horizontal_offset(horiz) if horiz
|
119
141
|
end
|
120
|
-
end
|
121
142
|
|
122
143
|
|
123
|
-
|
124
|
-
|
125
|
-
|
144
|
+
# Base class for find commands.
|
145
|
+
class FindCommandBase < Redcar::DocumentCommand
|
146
|
+
include FindCommandMixin
|
126
147
|
|
127
|
-
|
148
|
+
attr_reader :query, :options, :always_start_within_selection
|
128
149
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
150
|
+
# description here
|
151
|
+
def initialize(q, opt)
|
152
|
+
@options = opt
|
153
|
+
@query = opt.is_regex ? make_regex_query(q, opt) : make_literal_query(q, opt)
|
154
|
+
end
|
133
155
|
end
|
134
|
-
end
|
135
156
|
|
136
157
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
if select_next_match(doc, start_pos, query, @options.wrap_around)
|
143
|
-
true
|
144
|
-
else
|
145
|
-
# Clear selection as visual feedback that search failed.
|
146
|
-
doc.set_selection_range(start_pos, start_pos)
|
147
|
-
false
|
158
|
+
# Finds the next match after the current location.
|
159
|
+
class FindNextCommand < FindCommandBase
|
160
|
+
def initialize(q, opt, always_start_within_selection=false)
|
161
|
+
super(q, opt)
|
162
|
+
@always_start_within_selection = always_start_within_selection
|
148
163
|
end
|
149
|
-
end
|
150
|
-
end
|
151
164
|
|
165
|
+
def execute
|
166
|
+
# We first determine where to start the search, either from the begin or end of the current
|
167
|
+
# selection.
|
168
|
+
#
|
169
|
+
# If always_start_within_selection is true, then we always start at the beginning; this is
|
170
|
+
# needed for incremental search.
|
171
|
+
#
|
172
|
+
# Otherwise, we check if the current selection matches the query:
|
173
|
+
# * If it does match, we start after the selection, assuming that the selection matches
|
174
|
+
# because of a prior search, and we want to move on to the next occurrence.
|
175
|
+
# * If it doesn't match, we start at the beginning of the selection, to handle cases where
|
176
|
+
# the selection is actually the start of a match, e.g. the "Foo" portion of "Foobar" is
|
177
|
+
# selected, and the user sets the query to "Foobar"; then we want Find Next to simply
|
178
|
+
# expand the selection to span "Foobar" as the next match.
|
179
|
+
#
|
180
|
+
# TODO(yozhipozhi): Test this behavior!
|
181
|
+
start_within_selection = true
|
182
|
+
if !@always_start_within_selection && (doc.selected_text.length > 0)
|
183
|
+
text = doc.selected_text
|
184
|
+
m = query.match(text)
|
185
|
+
if (m && (m[0].length == text.length))
|
186
|
+
start_within_selection = false
|
187
|
+
end
|
188
|
+
end
|
189
|
+
offsets = selection_byte_offsets
|
190
|
+
start_pos = start_within_selection ? offsets[0] : offsets[1]
|
152
191
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
# Clear selection as visual feedback that search failed.
|
162
|
-
doc.set_selection_range(start_pos, start_pos)
|
163
|
-
false
|
192
|
+
# Do selection.
|
193
|
+
if select_next_match(doc, start_pos, query, options.wrap_around)
|
194
|
+
true
|
195
|
+
else
|
196
|
+
# Clear selection as visual feedback that search failed.
|
197
|
+
select_range_bytes(start_pos, start_pos)
|
198
|
+
false
|
199
|
+
end
|
164
200
|
end
|
165
201
|
end
|
166
|
-
end
|
167
202
|
|
168
203
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
204
|
+
# Finds the previous match before the current location.
|
205
|
+
class FindPreviousCommand < FindCommandBase
|
206
|
+
def execute
|
207
|
+
offsets = selection_byte_offsets
|
208
|
+
start_pos = offsets.min
|
209
|
+
if select_previous_match(doc, start_pos, query, @options.wrap_around)
|
210
|
+
true
|
211
|
+
else
|
212
|
+
# Clear selection as visual feedback that search failed.
|
213
|
+
select_range_bytes(start_pos, start_pos)
|
214
|
+
false
|
215
|
+
end
|
180
216
|
end
|
181
217
|
end
|
182
|
-
end
|
183
218
|
|
184
219
|
|
185
|
-
|
186
|
-
|
187
|
-
|
220
|
+
# Base class for replace commands.
|
221
|
+
class ReplaceCommandBase < Redcar::DocumentCommand
|
222
|
+
include FindCommandMixin
|
188
223
|
|
189
|
-
|
224
|
+
attr_reader :query, :replace
|
190
225
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
226
|
+
# description here
|
227
|
+
def initialize(query, replace, options)
|
228
|
+
@options = options
|
229
|
+
@query =
|
230
|
+
options.is_regex ? make_regex_query(query, options) : make_literal_query(query, options)
|
231
|
+
@replace = replace
|
232
|
+
end
|
196
233
|
end
|
197
|
-
end
|
198
234
|
|
199
235
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
236
|
+
# Replaces the currently selected text, if it matches the search criteria, then finds and
|
237
|
+
# selects the next match in the document.
|
238
|
+
#
|
239
|
+
# This command maintains the invariant that no text is replaced without first being selected,
|
240
|
+
# so the user always knows exactly what change is about to be made. A ramification of this
|
241
|
+
# policy is that, if no text is selected beforehand, or the selected text does not match the
|
242
|
+
# query, then "replace" portion of "replace and find" is essentially skipped, so that two button
|
243
|
+
# presses are required.
|
244
|
+
class ReplaceAndFindCommand < ReplaceCommandBase
|
245
|
+
def execute
|
246
|
+
offsets = selection_byte_offsets
|
247
|
+
start_pos = offsets.min
|
248
|
+
if doc.selected_text.length > 0
|
249
|
+
chars_replaced = replace_selection_if_match(doc, start_pos, query, replace)
|
250
|
+
if chars_replaced > 0
|
251
|
+
start_pos += chars_replaced
|
252
|
+
else
|
253
|
+
start_pos = offsets.max
|
254
|
+
end
|
255
|
+
end
|
256
|
+
if select_next_match(doc, start_pos, query, @options.wrap_around)
|
257
|
+
true
|
216
258
|
else
|
217
|
-
|
259
|
+
# Clear selection as visual feedback that search failed.
|
260
|
+
select_range_bytes(start_pos, start_pos)
|
261
|
+
false
|
218
262
|
end
|
219
263
|
end
|
220
|
-
if select_next_match(doc, start_pos, query, @options.wrap_around)
|
221
|
-
true
|
222
|
-
else
|
223
|
-
# Clear selection as visual feedback that search failed.
|
224
|
-
doc.set_selection_range(start_pos, start_pos)
|
225
|
-
false
|
226
|
-
end
|
227
264
|
end
|
228
|
-
end
|
229
265
|
|
230
266
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
count = 0
|
237
|
-
sc = StringScanner.new(text)
|
238
|
-
while sc.scan_until(query)
|
239
|
-
count += 1
|
240
|
-
|
241
|
-
startoff = sc.pos - sc.matched_size
|
242
|
-
replacement_text = text.slice(startoff, sc.matched_size).gsub(query, replace)
|
243
|
-
endoff = startoff + replacement_text.length
|
244
|
-
|
245
|
-
text[startoff...sc.pos] = replacement_text
|
246
|
-
sc.string = text
|
247
|
-
sc.pos = startoff + replacement_text.length
|
267
|
+
# Replaces all query matches.
|
268
|
+
class ReplaceAllCommand < ReplaceCommandBase
|
269
|
+
def initialize(query, replace, options, selection_only)
|
270
|
+
super(query, replace, options)
|
271
|
+
@selection_only = selection_only
|
248
272
|
end
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
273
|
+
|
274
|
+
def execute
|
275
|
+
startoff, endoff = nil
|
276
|
+
text = @selection_only ? doc.selected_text : doc.get_all_text
|
277
|
+
count = 0
|
278
|
+
sc = StringScanner.new(text)
|
279
|
+
while sc.scan_until(query)
|
280
|
+
count += 1
|
281
|
+
|
282
|
+
startoff = sc.pos - sc.matched_size
|
283
|
+
replacement_text = text.slice(startoff, sc.matched_size).gsub(query, replace)
|
284
|
+
endoff = startoff + replacement_text.length
|
285
|
+
|
286
|
+
text[startoff...sc.pos] = replacement_text
|
287
|
+
sc.string = text
|
288
|
+
sc.pos = startoff + replacement_text.length
|
289
|
+
end
|
290
|
+
if count > 0
|
291
|
+
if @selection_only
|
292
|
+
offsets = selection_byte_offsets
|
293
|
+
startoff = offsets.min
|
294
|
+
doc.replace(startoff, doc.selected_text.length, text)
|
295
|
+
select_range_bytes(startoff, startoff + text.length)
|
296
|
+
else
|
297
|
+
doc.text = text
|
298
|
+
select_range_bytes(startoff, startoff + replacement_text.length)
|
299
|
+
end
|
300
|
+
true
|
301
|
+
else
|
302
|
+
false
|
303
|
+
end
|
255
304
|
end
|
256
305
|
end
|
257
306
|
end
|