redcar 0.10 → 0.11.0dev

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/CHANGES +14 -0
  2. data/Rakefile +68 -29
  3. data/lib/redcar.rb +2 -2
  4. data/plugins/application/lib/application/dialogs/filter_list_dialog.rb +2 -1
  5. data/plugins/application_swt/spec/application_swt/gradient_spec.rb +3 -12
  6. data/plugins/core/lib/core/resource.rb +13 -5
  7. data/plugins/document_search/features/find.feature +366 -214
  8. data/plugins/document_search/features/incremental_search.feature +351 -0
  9. data/plugins/document_search/features/replace.feature +16 -16
  10. data/plugins/document_search/features/step_definitions/find_steps.rb +16 -0
  11. data/plugins/document_search/features/support/env.rb +11 -0
  12. data/plugins/document_search/lib/document_search.rb +149 -109
  13. data/plugins/document_search/lib/document_search/commands.rb +251 -202
  14. data/plugins/document_search/lib/document_search/find_speedbar.rb +138 -81
  15. data/plugins/document_search/lib/document_search/incremental_search_speedbar.rb +70 -0
  16. data/plugins/document_search/lib/document_search/query_options.rb +15 -39
  17. data/plugins/document_search/plugin.rb +1 -1
  18. data/plugins/edit_view/features/step_definitions/editing_steps.rb +6 -2
  19. data/plugins/edit_view_swt/lib/edit_view_swt.rb +2 -2
  20. data/plugins/file_parser/lib/file_parser.rb +6 -1
  21. data/plugins/html_view/features/step_definitions/web_view_steps.rb +12 -0
  22. data/plugins/html_view/features/support/env.rb +16 -0
  23. data/plugins/html_view/lib/html_view.rb +5 -1
  24. data/plugins/line_tools/lib/line_tools.rb +16 -0
  25. data/plugins/project/features/find_file.feature +28 -0
  26. data/plugins/project/features/open_and_save_files.feature +11 -0
  27. data/plugins/project/features/step_definitions/file_steps.rb +6 -1
  28. data/plugins/project/features/support/env.rb +2 -0
  29. data/plugins/project/lib/project.rb +49 -6
  30. data/plugins/project/lib/project/commands.rb +18 -6
  31. data/plugins/project/lib/project/find_file_dialog.rb +19 -8
  32. data/plugins/project/lib/project/find_recent_dialog.rb +30 -0
  33. data/plugins/project/lib/project/manager.rb +41 -10
  34. data/plugins/project/lib/project/recent.rb +64 -0
  35. data/plugins/project/spec/fixtures/myproject/vendor/bar.rb +0 -0
  36. data/plugins/project/spec/fixtures/myproject/vendor/plugins/bar.rb +0 -0
  37. data/plugins/{find-in-project → project_search}/TODO.md +3 -3
  38. data/plugins/project_search/features/support/env.rb +6 -0
  39. data/plugins/project_search/features/word_search.feature +34 -0
  40. data/plugins/project_search/lib/project_search.rb +73 -0
  41. data/plugins/project_search/lib/project_search/binary_data_detector.rb +46 -0
  42. data/plugins/project_search/lib/project_search/commands.rb +62 -0
  43. data/plugins/project_search/lib/project_search/hit.rb +17 -0
  44. data/plugins/{find-in-project/lib/find_in_project → project_search/lib/project_search}/images/collapsed.png +0 -0
  45. data/plugins/{find-in-project/lib/find_in_project → project_search/lib/project_search}/images/expanded.png +0 -0
  46. data/plugins/{find-in-project/lib/find_in_project → project_search/lib/project_search}/images/spinner.gif +0 -0
  47. data/plugins/project_search/lib/project_search/lucene_index.rb +64 -0
  48. data/plugins/project_search/lib/project_search/lucene_refresh.rb +22 -0
  49. data/plugins/project_search/lib/project_search/project.rb +14 -0
  50. data/plugins/project_search/lib/project_search/query.rb +29 -0
  51. data/plugins/{find-in-project/lib/find_in_project → project_search/lib/project_search}/stylesheets/style.css +14 -3
  52. data/plugins/project_search/lib/project_search/views/_file.html.erb +60 -0
  53. data/plugins/{find-in-project/lib/find_in_project → project_search/lib/project_search}/views/index.html.erb +12 -9
  54. data/plugins/project_search/lib/project_search/word_search.rb +105 -0
  55. data/plugins/project_search/lib/project_search/word_search_controller.rb +207 -0
  56. data/plugins/project_search/plugin.rb +8 -0
  57. data/plugins/project_search/spec/fixtures/project/binary_file.bin +0 -0
  58. data/plugins/project_search/spec/fixtures/project/foo.txt +43 -0
  59. data/plugins/project_search/spec/fixtures/project/qux.rb +3 -0
  60. data/plugins/project_search/spec/project_search/binary_data_detector_spec.rb +24 -0
  61. data/plugins/project_search/spec/project_search/word_search_spec.rb +157 -0
  62. data/plugins/project_search/spec/spec_helper.rb +27 -0
  63. data/plugins/redcar/redcar.rb +77 -71
  64. data/plugins/ruby/lib/ruby/syntax_checker.rb +1 -1
  65. data/plugins/snippets/features/snippets.feature +12 -0
  66. data/plugins/snippets/lib/snippets/tab_handler.rb +1 -1
  67. metadata +46 -25
  68. data/plugins/document_search/features/find_and_replace.feature +0 -723
  69. data/plugins/document_search/lib/document_search/find_and_replace_speedbar.rb +0 -142
  70. data/plugins/find-in-project/lib/find_in_project.rb +0 -35
  71. data/plugins/find-in-project/lib/find_in_project/commands.rb +0 -30
  72. data/plugins/find-in-project/lib/find_in_project/controllers.rb +0 -170
  73. data/plugins/find-in-project/lib/find_in_project/views/_divider.html.erb +0 -4
  74. data/plugins/find-in-project/lib/find_in_project/views/_file_heading.html.erb +0 -10
  75. data/plugins/find-in-project/lib/find_in_project/views/_file_line.html.erb +0 -6
  76. data/plugins/find-in-project/plugin.rb +0 -11
  77. data/plugins/project/lib/project/recent_directories.rb +0 -54
@@ -3,6 +3,7 @@
3
3
  padding: 0;
4
4
  font-weight: normal;
5
5
  font-size: 14px;
6
+ font-family: sans-serif;
6
7
  }
7
8
 
8
9
  #search_form_container {
@@ -34,6 +35,7 @@
34
35
  #search_form_container .input .field input {
35
36
  width: 100%;
36
37
  height: 20px;
38
+ font-family: monospace;
37
39
  }
38
40
 
39
41
  #search_form_container .input .expand {
@@ -122,7 +124,7 @@
122
124
  }
123
125
 
124
126
  #results .even {
125
- background-color: #EDEDFD;
127
+ background-color: #F4F4F4;
126
128
  }
127
129
 
128
130
  #results .odd {
@@ -141,6 +143,7 @@
141
143
 
142
144
  #results .file_heading a {
143
145
  font-weight: bold;
146
+ font-family: sans-serif;
144
147
  }
145
148
 
146
149
  #results .expand_collapse img {
@@ -155,6 +158,7 @@
155
158
  cursor: hand;
156
159
  font-size: 12px;
157
160
  line-height: 12px;
161
+ font-family: sans-serif;
158
162
  }
159
163
 
160
164
  #results .divider .line_num {
@@ -168,17 +172,22 @@
168
172
  padding: 3px 10px;
169
173
  width: 100%;
170
174
  cursor: hand;
175
+ }
176
+
177
+ #results .text pre {
171
178
  font-size: 14px;
172
179
  line-height: 17px;
180
+ font-family: monospace;
173
181
  }
174
182
 
175
183
  #results .divider .text {
176
184
  cursor: default;
177
185
  }
178
186
 
179
- #results .text span {
180
- background-color: yellow;
187
+ #results .text pre span {
188
+ background-color: #FFCCCC;
181
189
  font-weight: bold;
190
+ font-family: monospace;
182
191
  }
183
192
 
184
193
  #results .break {
@@ -190,6 +199,8 @@
190
199
  bottom: 0;
191
200
  left: 0;
192
201
  right: 0;
202
+ font-size: 1.5em;
203
+ font-family: sans-serif;
193
204
  }
194
205
 
195
206
  #results_summary .text {
@@ -0,0 +1,60 @@
1
+
2
+ <tr><td class='break' colspan='2'></td></tr>
3
+ <tr>
4
+ <td class="file_heading" colspan="2">
5
+ <a class="expand_collapse" data-file_num="<%= file %>">
6
+ <img src="<%= image_path %>/expanded.png" />
7
+ <%= CGI::escapeHTML(file.gsub(@word_search.project.path + "/", "")) %>
8
+ </a>
9
+ </td>
10
+ </tr>
11
+
12
+ <% line_index = 0 %>
13
+ <% last_matching_line_num = nil %>
14
+ <% last_displayed_line_num = -1 %>
15
+ <% hits.each_with_index do |hit, i| %>
16
+ <% next_hit = hits[i + 1] %>
17
+ <% next_hit_line_num = next_hit ? next_hit.line_num : 100_000_000 %>
18
+
19
+ <% if @word_search.context? and last_matching_line_num and (hit.line_num - last_matching_line_num) > (@word_search.context_size * 2) %>
20
+ <tr class="divider file_<%= file_num %>">
21
+ <td class="line_num">. . .</td>
22
+ <td class="text">&nbsp;</td> <!-- extra space to ensure line-height is respected -->
23
+ </tr>
24
+ <% end %>
25
+
26
+ <% hit.pre_context.each_with_index do |context_line, i| %>
27
+ <% this_line_num = hit.line_num - @word_search.context_size + i %>
28
+ <% unless this_line_num <= last_displayed_line_num %>
29
+ <% line_index += 1 %>
30
+ <tr class="result file_<%= file_num %> line_<%= this_line_num %> <%= (line_index % 2 == 0 ? 'even' : 'odd') %>" data-href='<%= CGI::escapeHTML(file) %>' data-line_num='<%= this_line_num %>'>
31
+ <td class='line_num'><%= this_line_num %></td>
32
+ <% text = CGI.escapeHTML(context_line).gsub(@word_search.regex) { "<span>#{$&}</span>" } %>
33
+ <td class='text'><pre><%= text.chomp + " " %></pre></td> <!-- extra space to ensure line-height is respected -->
34
+ </tr>
35
+ <% end %>
36
+ <% end %>
37
+
38
+ <% line_index += 1 %>
39
+ <% last_displayed_line_num = hit.line_num %>
40
+ <tr class="result file_<%= file_num %> line_<%= hit.line_num %> <%= (line_index % 2 == 0 ? 'even' : 'odd') %>" data-href='<%= CGI::escapeHTML(file) %>' data-line_num='<%= hit.line_num %>'>
41
+ <td class='line_num'><%= hit.line_num %></td>
42
+ <% text = CGI.escapeHTML(hit.line).gsub(@word_search.regex) { "<span>#{$&}</span>" } %>
43
+ <td class='text'><pre><%= text.chomp + " " %></pre></td> <!-- extra space to ensure line-height is respected -->
44
+ </tr>
45
+
46
+ <% hit.post_context.each_with_index do |context_line, i| %>
47
+ <% this_line_num = hit.line_num + i + 1%>
48
+ <% unless this_line_num >= next_hit_line_num %>
49
+ <% last_displayed_line_num = this_line_num %>
50
+ <% line_index += 1 %>
51
+ <tr class="result file_<%= file_num %> line_<%= this_line_num %> <%= (line_index % 2 == 0 ? 'even' : 'odd') %>" data-href='<%= CGI::escapeHTML(file) %>' data-line_num='<%= this_line_num %>'>
52
+ <td class='line_num'><%= this_line_num %></td>
53
+ <% text = CGI.escapeHTML(context_line).gsub(@word_search.regex) { "<span>#{$&}</span>" } %>
54
+ <td class='text'><pre><%= text.chomp + " " %></pre></td> <!-- extra space to ensure line-height is respected -->
55
+ </tr>
56
+ <% end %>
57
+ <% end %>
58
+
59
+ <% last_matching_line_num = hit.line_num %>
60
+ <% end %>
@@ -3,10 +3,10 @@
3
3
  <% json_path = File.expand_path(File.join(Redcar.root, %w(plugins html_view assets json2.js))) %>
4
4
  <script type="text/javascript" src="file://<%= json_path %>"></script>
5
5
 
6
- <% plugin_css = File.expand_path(File.join(@plugin_root, %w(lib find_in_project stylesheets style.css))) %>
6
+ <% plugin_css = File.expand_path(File.join(plugin_root, %w(lib project_search stylesheets style.css))) %>
7
7
  <link rel="stylesheet" href="file://<%= plugin_css %>" type="text/css" media="screen">
8
8
 
9
- <% image_path = File.expand_path(File.join(@plugin_root, %w(lib find_in_project images))) %>
9
+ <% image_path = File.expand_path(File.join(plugin_root, %w(lib project_search images))) %>
10
10
 
11
11
  <div id="search_form_container">
12
12
  <form id="search_form">
@@ -14,14 +14,14 @@
14
14
  <tr class="input">
15
15
  <td class="label"><label for="query">Search Term</label></td>
16
16
  <td class="field">
17
- <input id="query" type="text" value="<%= @query %>" />
18
-
17
+ <input id="query" type="text" value="<%= default_query %>" />
18
+
19
19
  <!-- keeps a cache of the current query so if the user changes the query field, this remains the same as the results -->
20
20
  <input id="cached_query" type="hidden" />
21
21
 
22
22
  <div id="recent_queries" style="display: none;">
23
23
  <ul>
24
- <% (@settings['recent_queries'] || []).each do |query| %>
24
+ <% (settings['recent_queries'] || []).each do |query| %>
25
25
  <li><%= CGI::escapeHTML(query) %></li>
26
26
  <% end %>
27
27
  </ul>
@@ -36,12 +36,15 @@
36
36
  <td class="controls">
37
37
  <input id="search" type="submit" value="Find In Project" />
38
38
  <img id="spinner" src="<%= image_path %>/spinner.gif" style="display:none;" />
39
+ <% if show_literal_match_option? %>
39
40
  <input type="checkbox" id="literal_match" <%="checked=checked" if @literal_match %>>
40
- <label for="literal_match">Literal Match</label>
41
- <input type="checkbox" id="match_case" <%="checked=checked" if @match_case %>>
41
+ <label for="literal_match">Literal Match</label>
42
+ <% end %>
43
+ <input type="checkbox" id="match_case" <%="checked=checked" if match_case? %>>
42
44
  <label for="match_case">Match case</label>
43
- <input type="checkbox" id="with_context" <%="checked=checked" if @with_context %>>
45
+ <input type="checkbox" id="with_context" <%="checked=checked" if context? %>>
44
46
  <label for="with_context">With context</label>
47
+ <%= search_copy %>
45
48
  <a href="#" title="Edit Preferences" id="edit_preferences">Edit Preferences...</a>
46
49
  </td>
47
50
  <td></td>
@@ -99,7 +102,7 @@
99
102
  });
100
103
 
101
104
  $('#edit_preferences').click(function(ev) {
102
- Controller.preferences();
105
+ Controller.editPreferences();
103
106
  });
104
107
 
105
108
  // in the case the user wants to search for things like <table>, we need to unescape the entities
@@ -0,0 +1,105 @@
1
+
2
+ class ProjectSearch
3
+ class WordSearch
4
+ attr_reader :query_string, :context_size, :project
5
+
6
+ def initialize(project, query_string, match_case, context_size)
7
+ @project = project
8
+ @query_string = query_string
9
+ @match_case = !!match_case
10
+ @context_size = context_size
11
+ end
12
+
13
+ def match_case?
14
+ @match_case
15
+ end
16
+
17
+ def context?
18
+ @context_size > 0
19
+ end
20
+
21
+ def matching_line?(line)
22
+ line =~ regex
23
+ end
24
+
25
+ def regex
26
+ @regex ||= begin
27
+ regexp_text = Regexp.escape(@query_string)
28
+ match_case? ? /#{regexp_text}/ : /#{regexp_text}/i
29
+ end
30
+ end
31
+
32
+ def on_file_results(&block)
33
+ @on_file_results_block = block
34
+ end
35
+
36
+ def generate_results
37
+ hits = []
38
+ doc_ids.each do |doc_id|
39
+ next unless File.exist?(doc_id)
40
+ contents = File.read(doc_id).split(/\n|\r/)
41
+ pre_context = []
42
+ hits_needing_post_context = []
43
+ remove_hits = []
44
+ file_hits = []
45
+ contents.each_with_index do |line, line_num|
46
+ hits_needing_post_context.each do |hit|
47
+ hit.post_context << line
48
+ if hit.post_context.length == context_size
49
+ remove_hits << hit
50
+ end
51
+ end
52
+ hits_needing_post_context -= remove_hits
53
+
54
+ if matching_line?(line)
55
+ hit = Hit.new(doc_id, line_num, line, regex, pre_context.dup, [])
56
+ hits << hit
57
+ file_hits << hit
58
+ if context_size > 0
59
+ hits_needing_post_context << hit
60
+ end
61
+ end
62
+
63
+ if pre_context.length == context_size
64
+ pre_context.shift
65
+ end
66
+ pre_context << line
67
+ end
68
+ send_file_results(file_hits)
69
+ end
70
+ hits
71
+ end
72
+
73
+ def send_file_results(hits)
74
+ if @on_file_results_block
75
+ @on_file_results_block.call(hits)
76
+ end
77
+ end
78
+
79
+ def results
80
+ @results ||= generate_results
81
+ end
82
+
83
+ def bits
84
+ query_string.
85
+ gsub(/[^\w]/, " ").
86
+ gsub("_", " ").
87
+ split(/\s/).
88
+ map {|b| b.strip}.
89
+ reject {|b| b == "" or org.apache.lucene.analysis.standard.StandardAnalyzer::STOP_WORDS_SET.to_a.include?(b)}
90
+ end
91
+
92
+ def doc_ids
93
+ @doc_ids ||= begin
94
+ index = ProjectSearch.indexes[project.path].lucene_index
95
+ doc_ids = nil
96
+ bits.each do |bit|
97
+ new_doc_ids = index.find(:contents => bit.downcase).map {|doc| doc.id }
98
+ doc_ids = doc_ids ? (doc_ids & new_doc_ids) : new_doc_ids
99
+ end
100
+ doc_ids
101
+ end
102
+ end
103
+ end
104
+ end
105
+
@@ -0,0 +1,207 @@
1
+
2
+ class ProjectSearch
3
+ class WordSearchController
4
+ include Redcar::HtmlController
5
+
6
+ TITLE = "Project Search"
7
+
8
+ def title
9
+ TITLE
10
+ end
11
+
12
+ def search_copy
13
+ "Search for complete words only"
14
+ end
15
+
16
+ def show_literal_match_option?
17
+ false
18
+ end
19
+
20
+ def num_context_lines
21
+ settings['context_lines']
22
+ end
23
+
24
+ def plugin_root
25
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
26
+ end
27
+
28
+ def settings
29
+ ProjectSearch.storage
30
+ end
31
+
32
+ def match_case?
33
+ ProjectSearch.storage['match_case']
34
+ end
35
+
36
+ def context?
37
+ ProjectSearch.storage['with_context']
38
+ end
39
+
40
+ def context_size
41
+ context? ? num_context_lines : 0
42
+ end
43
+
44
+ def default_query
45
+ doc.selected_text if doc && doc.selection?
46
+ end
47
+
48
+ def index
49
+ render('index')
50
+ end
51
+
52
+ def edit_preferences
53
+ Redcar.app.make_sure_at_least_one_window_open # open a new window if needed
54
+ Redcar::FindInProject.storage # populate the file if it isn't already
55
+ tab = Redcar.app.focussed_window.new_tab(Redcar::EditTab)
56
+ mirror = Project::FileMirror.new(File.join(Redcar.user_dir, "storage", "find_in_project.yaml"))
57
+ tab.edit_view.document.mirror = mirror
58
+ tab.edit_view.reset_undo
59
+ tab.focus
60
+ end
61
+
62
+ def open_file(file, line, query, literal_match, match_case)
63
+ Redcar::Project::Manager.open_file(File.expand_path(file, Redcar::Project::Manager.focussed_project.path))
64
+ doc.cursor_offset = doc.offset_at_line(line.to_i - 1)
65
+ regexp_text = literal_match ? Regexp.escape(query) : query
66
+ regex = match_case ? /(#{regexp_text})/ : /(#{regexp_text})/i
67
+ index = doc.get_line(line.to_i - 1).index(regex)
68
+ if index
69
+ length = doc.get_line(line.to_i - 1).match(regex)[1].length
70
+ doc.set_selection_range(doc.cursor_line_start_offset + index, doc.cursor_line_start_offset + index + length)
71
+ end
72
+ doc.scroll_to_line(line.to_i)
73
+ nil
74
+ end
75
+
76
+ def close
77
+ Thread.kill(@thread) if @thread
78
+ @thread = nil
79
+ super
80
+ end
81
+
82
+ def render(view, bg=nil)
83
+ erb(view).result(bg || binding)
84
+ end
85
+
86
+ def render_file(bg)
87
+ @erb ||= erb("_file")
88
+ @erb.result(bg)
89
+ end
90
+
91
+ def erb(view)
92
+ ERB.new(File.read(File.join(File.dirname(__FILE__), "views", "#{view.to_s}.html.erb")))
93
+ end
94
+
95
+ def doc
96
+ Redcar.app.focussed_window.focussed_notebook_tab.edit_view.document rescue false
97
+ end
98
+
99
+ def add_or_move_to_top(item, array)
100
+ return array if item.strip.empty?
101
+ array.delete_at(array.index(item)) if array.include?(item)
102
+ array.unshift(item)
103
+ end
104
+
105
+ def search(query, literal_match, match_case, with_context)
106
+ ProjectSearch.storage['recent_queries'] = add_or_move_to_top(query, ProjectSearch.storage['recent_queries'])
107
+ ProjectSearch.storage['match_case'] = (match_case == 'true')
108
+ ProjectSearch.storage['with_context'] = (with_context == 'true')
109
+
110
+ project = Redcar::Project::Manager.focussed_project
111
+
112
+ @word_search = WordSearch.new(project, query, match_case?, context_size)
113
+
114
+ # kill any existing running search to prevent memory bloat
115
+ Thread.kill(@thread) if @thread
116
+ @thread = nil
117
+ @thread = Thread.new do
118
+ begin
119
+ initialize_search_output
120
+ file_num = 0
121
+ line_num = 0
122
+ have_prepared_table = false
123
+ have_results = false
124
+
125
+ @word_search.on_file_results do |hits|
126
+ next unless hits.any?
127
+ have_results = true
128
+ file_num += 1
129
+ line_num += hits.length
130
+ set_file_count(file_num)
131
+ set_line_count(line_num)
132
+ file = hits.first.file
133
+
134
+ unless have_prepared_table
135
+ prepare_results_table
136
+ have_prepared_table = true
137
+ end
138
+
139
+ file_html = render_file(binding)
140
+ escaped_file_html = escape_javascript(file_html)
141
+ execute("$('#results table tr:last').after(\"#{escaped_file_html}\");")
142
+ end
143
+
144
+ @word_search.generate_results
145
+
146
+ if have_results
147
+ remove_initial_blank_tr
148
+ else
149
+ render_no_results
150
+ end
151
+
152
+ hide_spinner
153
+ Thread.kill(@thread) if @thread
154
+ @thread = nil
155
+ rescue => e
156
+ puts e.message
157
+ puts e.backtrace
158
+ end
159
+ end
160
+ nil
161
+ end
162
+
163
+ def initialize_search_output
164
+ execute("$('#cached_query').val(\"#{escape_javascript(@word_search.query_string)}\");")
165
+ execute("$('#results').html(\"<div id='no_results'>Searching...</div>\");")
166
+ execute("$('#spinner').show();")
167
+ execute("$('#results_summary').hide();")
168
+ execute("$('#file_results_count').html(0);")
169
+ execute("$('#line_results_count').html(0);")
170
+ end
171
+
172
+ def prepare_results_table
173
+ execute("if ($('#results table').size() == 0) { $('#results').html(\"<table><tr></tr></table>\"); }")
174
+ execute("if ($('#results_summary').first().is(':hidden')) { $('#results_summary').show(); }")
175
+ end
176
+
177
+ def set_file_count(value)
178
+ execute("$('#file_results_count').html(\"#{value}\");")
179
+ end
180
+
181
+ def set_line_count(value)
182
+ execute("$('#line_results_count').html(\"#{value}\");")
183
+ end
184
+
185
+ def remove_initial_blank_tr
186
+ execute("$('#results table tr:first').remove();")
187
+ end
188
+
189
+ def render_no_results
190
+ result = "<div id='no_results'>No results were found using the search terms you provided.</div>"
191
+ execute("$('#results').html(\"#{escape_javascript(result)}\");")
192
+ end
193
+
194
+ def hide_spinner
195
+ execute("$('#spinner').hide();")
196
+ end
197
+
198
+ def escape_javascript(text)
199
+ escape_map = { '\\' => '\\\\', '</' => '<\/', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', '"' => '\\"', "'" => "\\'" }
200
+ text.to_s.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { escape_map[$1] }
201
+ end
202
+
203
+ def image_path
204
+ File.expand_path(File.join(plugin_root, %w(lib project_search images)))
205
+ end
206
+ end
207
+ end