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.
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