ruber 0.0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. data/COPYING +339 -0
  2. data/INSTALL +137 -0
  3. data/LICENSE +8 -0
  4. data/bin/ruber +65 -0
  5. data/data/share/apps/ruber/core_components.yaml +31 -0
  6. data/data/share/apps/ruber/ruberui.rc +109 -0
  7. data/data/share/icons/ruber.png +0 -0
  8. data/data/share/pixmaps/ruby.png +0 -0
  9. data/icons/ruber-16.png +0 -0
  10. data/icons/ruber-32.png +0 -0
  11. data/icons/ruber-48.png +0 -0
  12. data/icons/ruber-8.png +0 -0
  13. data/lib/ruber/application/application.rb +288 -0
  14. data/lib/ruber/application/plugin.yaml +11 -0
  15. data/lib/ruber/component_manager.rb +899 -0
  16. data/lib/ruber/config/config.rb +82 -0
  17. data/lib/ruber/config/plugin.yaml +3 -0
  18. data/lib/ruber/document_project.rb +209 -0
  19. data/lib/ruber/documents/document_list.rb +416 -0
  20. data/lib/ruber/documents/plugin.yaml +4 -0
  21. data/lib/ruber/editor/document.rb +506 -0
  22. data/lib/ruber/editor/editor_view.rb +167 -0
  23. data/lib/ruber/editor/ktexteditor_wrapper.rb +202 -0
  24. data/lib/ruber/exception_widgets.rb +245 -0
  25. data/lib/ruber/external_program_plugin.rb +397 -0
  26. data/lib/ruber/filtered_output_widget.rb +342 -0
  27. data/lib/ruber/gui_states_handler.rb +231 -0
  28. data/lib/ruber/kde_config_option_backend.rb +167 -0
  29. data/lib/ruber/kde_sugar.rb +249 -0
  30. data/lib/ruber/main_window/choose_plugins_dlg.rb +353 -0
  31. data/lib/ruber/main_window/main_window.rb +524 -0
  32. data/lib/ruber/main_window/main_window_actions.rb +537 -0
  33. data/lib/ruber/main_window/main_window_internal.rb +239 -0
  34. data/lib/ruber/main_window/open_file_in_project_dlg.rb +212 -0
  35. data/lib/ruber/main_window/output_color_widget.rb +35 -0
  36. data/lib/ruber/main_window/plugin.yaml +58 -0
  37. data/lib/ruber/main_window/save_modified_files_dlg.rb +89 -0
  38. data/lib/ruber/main_window/status_bar.rb +156 -0
  39. data/lib/ruber/main_window/ui/choose_plugins_widget.rb +90 -0
  40. data/lib/ruber/main_window/ui/choose_plugins_widget.ui +77 -0
  41. data/lib/ruber/main_window/ui/main_window_settings_widget.rb +108 -0
  42. data/lib/ruber/main_window/ui/main_window_settings_widget.ui +89 -0
  43. data/lib/ruber/main_window/ui/new_project_widget.rb +119 -0
  44. data/lib/ruber/main_window/ui/new_project_widget.ui +178 -0
  45. data/lib/ruber/main_window/ui/open_file_in_project_dlg.rb +109 -0
  46. data/lib/ruber/main_window/ui/open_file_in_project_dlg.ui +168 -0
  47. data/lib/ruber/main_window/ui/output_color_widget.rb +241 -0
  48. data/lib/ruber/main_window/ui/output_color_widget.ui +204 -0
  49. data/lib/ruber/main_window/workspace.rb +442 -0
  50. data/lib/ruber/output_widget.rb +1093 -0
  51. data/lib/ruber/plugin.rb +264 -0
  52. data/lib/ruber/plugin_like.rb +589 -0
  53. data/lib/ruber/plugin_specification.rb +106 -0
  54. data/lib/ruber/plugin_specification_reader.rb +451 -0
  55. data/lib/ruber/project.rb +493 -0
  56. data/lib/ruber/project_backend.rb +105 -0
  57. data/lib/ruber/projects/plugin.yaml +11 -0
  58. data/lib/ruber/projects/project_files_list.rb +314 -0
  59. data/lib/ruber/projects/project_files_widget.rb +301 -0
  60. data/lib/ruber/projects/project_list.rb +314 -0
  61. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.rb +74 -0
  62. data/lib/ruber/projects/ui/project_files_rule_chooser_widget.ui +61 -0
  63. data/lib/ruber/projects/ui/project_files_widget.rb +117 -0
  64. data/lib/ruber/projects/ui/project_files_widget.ui +123 -0
  65. data/lib/ruber/qt_sugar.rb +673 -0
  66. data/lib/ruber/settings_container.rb +515 -0
  67. data/lib/ruber/settings_dialog.rb +244 -0
  68. data/lib/ruber/settings_dialog_manager.rb +503 -0
  69. data/lib/ruber/utils.rb +414 -0
  70. data/lib/ruber/yaml_option_backend.rb +159 -0
  71. data/outsider_files +15 -0
  72. data/plugins/autosave/autosave.rb +404 -0
  73. data/plugins/autosave/plugin.yaml +16 -0
  74. data/plugins/autosave/ui/autosave_config_widget.rb +83 -0
  75. data/plugins/autosave/ui/autosave_config_widget.ui +68 -0
  76. data/plugins/command/command.png +0 -0
  77. data/plugins/command/command.rb +74 -0
  78. data/plugins/command/plugin.yaml +11 -0
  79. data/plugins/find_in_files/find_in_files.rb +337 -0
  80. data/plugins/find_in_files/find_in_files_dlg.rb +411 -0
  81. data/plugins/find_in_files/find_in_files_ui.rc +11 -0
  82. data/plugins/find_in_files/find_in_files_widgets.rb +485 -0
  83. data/plugins/find_in_files/plugin.yaml +23 -0
  84. data/plugins/find_in_files/ui/config_widget.rb +58 -0
  85. data/plugins/find_in_files/ui/config_widget.ui +41 -0
  86. data/plugins/find_in_files/ui/find_in_files_widget.rb +260 -0
  87. data/plugins/find_in_files/ui/find_in_files_widget.ui +324 -0
  88. data/plugins/project_browser/plugin.yaml +10 -0
  89. data/plugins/project_browser/project_browser.rb +245 -0
  90. data/plugins/rake/plugin.yaml +39 -0
  91. data/plugins/rake/rake.png +0 -0
  92. data/plugins/rake/rake.rb +567 -0
  93. data/plugins/rake/rake_extension.rb +153 -0
  94. data/plugins/rake/rake_widgets.rb +615 -0
  95. data/plugins/rake/rakeui.rc +27 -0
  96. data/plugins/rake/ui/add_quick_task_widget.rb +71 -0
  97. data/plugins/rake/ui/add_quick_task_widget.ui +59 -0
  98. data/plugins/rake/ui/choose_task_widget.rb +77 -0
  99. data/plugins/rake/ui/choose_task_widget.ui +72 -0
  100. data/plugins/rake/ui/config_widget.rb +127 -0
  101. data/plugins/rake/ui/config_widget.ui +123 -0
  102. data/plugins/rake/ui/project_widget.rb +217 -0
  103. data/plugins/rake/ui/project_widget.ui +246 -0
  104. data/plugins/rspec/plugin.yaml +30 -0
  105. data/plugins/rspec/rspec.png +0 -0
  106. data/plugins/rspec/rspec.rb +945 -0
  107. data/plugins/rspec/rspec.svg +90 -0
  108. data/plugins/rspec/rspecui.rc +20 -0
  109. data/plugins/rspec/ruber_rspec_formatter.rb +312 -0
  110. data/plugins/rspec/ui/rspec_project_widget.rb +170 -0
  111. data/plugins/rspec/ui/rspec_project_widget.ui +193 -0
  112. data/plugins/ruby_development/plugin.yaml +27 -0
  113. data/plugins/ruby_development/ruby_development.png +0 -0
  114. data/plugins/ruby_development/ruby_development.rb +453 -0
  115. data/plugins/ruby_development/ruby_developmentui.rc +19 -0
  116. data/plugins/ruby_development/ui/project_widget.rb +112 -0
  117. data/plugins/ruby_development/ui/project_widget.ui +108 -0
  118. data/plugins/ruby_runner/config_widget.rb +116 -0
  119. data/plugins/ruby_runner/plugin.yaml +26 -0
  120. data/plugins/ruby_runner/project_widget.rb +62 -0
  121. data/plugins/ruby_runner/ruby.png +0 -0
  122. data/plugins/ruby_runner/ruby_interpretersui.rc +26 -0
  123. data/plugins/ruby_runner/ruby_runner.rb +411 -0
  124. data/plugins/ruby_runner/ui/config_widget.rb +92 -0
  125. data/plugins/ruby_runner/ui/config_widget.ui +91 -0
  126. data/plugins/ruby_runner/ui/project_widget.rb +60 -0
  127. data/plugins/ruby_runner/ui/project_widget.ui +48 -0
  128. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.rb +59 -0
  129. data/plugins/ruby_runner/ui/ruby_runnner_plugin_option_widget.ui +44 -0
  130. data/plugins/state/plugin.yaml +28 -0
  131. data/plugins/state/state.rb +520 -0
  132. data/plugins/state/ui/config_widget.rb +92 -0
  133. data/plugins/state/ui/config_widget.ui +89 -0
  134. data/plugins/syntax_checker/plugin.yaml +18 -0
  135. data/plugins/syntax_checker/syntax_checker.rb +662 -0
  136. data/ruber.desktop +10 -0
  137. data/spec/annotation_model_spec.rb +174 -0
  138. data/spec/common.rb +119 -0
  139. data/spec/component_manager_spec.rb +1259 -0
  140. data/spec/document_list_spec.rb +626 -0
  141. data/spec/document_project_spec.rb +373 -0
  142. data/spec/document_spec.rb +779 -0
  143. data/spec/editor_view_spec.rb +167 -0
  144. data/spec/external_program_plugin_spec.rb +676 -0
  145. data/spec/filtered_output_widget_spec.rb +642 -0
  146. data/spec/gui_states_handler_spec.rb +304 -0
  147. data/spec/kde_config_option_backend_spec.rb +214 -0
  148. data/spec/kde_sugar_spec.rb +101 -0
  149. data/spec/ktexteditor_wrapper_spec.rb +305 -0
  150. data/spec/output_widget_spec.rb +1703 -0
  151. data/spec/plugin_spec.rb +1393 -0
  152. data/spec/plugin_specification_reader_spec.rb +1765 -0
  153. data/spec/plugin_specification_spec.rb +401 -0
  154. data/spec/project_backend_spec.rb +172 -0
  155. data/spec/project_files_list_spec.rb +401 -0
  156. data/spec/project_list_spec.rb +511 -0
  157. data/spec/project_spec.rb +990 -0
  158. data/spec/qt_sugar_spec.rb +328 -0
  159. data/spec/settings_container_spec.rb +617 -0
  160. data/spec/settings_dialog_manager_spec.rb +773 -0
  161. data/spec/settings_dialog_spec.rb +419 -0
  162. data/spec/state_spec.rb +991 -0
  163. data/spec/utils_spec.rb +406 -0
  164. data/spec/workspace_spec.rb +869 -0
  165. data/spec/yaml_option_backend_spec.rb +246 -0
  166. metadata +284 -0
@@ -0,0 +1,411 @@
1
+ =begin
2
+ Copyright (C) 2010 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ require_relative 'ui/find_in_files_widget'
22
+
23
+ module Ruber
24
+
25
+ module FindInFiles
26
+
27
+ =begin rdoc
28
+ Dialog to set options for search/replace in files.
29
+
30
+ A single dialog is used for both search and replace. The action performed depends on whether the user clicks the Find or the Replace button.
31
+ This dialog can be used either as a modal or modeless dialog: if it is modal, use the value returned by #action to see whether the user chose find or replace; in modeless mode rely on the #find and #replace signals.
32
+ =end
33
+ class FindReplaceInFilesDlg < KDE::Dialog
34
+
35
+ =begin rdoc
36
+ Signal emitted when the user presses the Find button.
37
+ =end
38
+ signals :find
39
+
40
+ =begin rdoc
41
+ Signal emitted when the user presses the Replace button.
42
+ =end
43
+ signals :replace
44
+
45
+ =begin rdoc
46
+ A list of possible matching modes
47
+ =end
48
+ MODES = [:regexp, :plain]
49
+
50
+ =begin rdoc
51
+ A list of possible files where to search files
52
+ =end
53
+ PLACES = [:project_files, :project_dir, :open_files, :custom_dir]
54
+
55
+ =begin rdoc
56
+ A map between the text shown in the file type widget and rak options
57
+ =end
58
+ FILE_TYPES = {
59
+ 'C++ files' => :cpp,
60
+ 'C files' => :c,
61
+ 'C# files' => :csharp,
62
+ 'CSS files' => :css,
63
+ 'Elisp files' => :elisp,
64
+ 'Erlang files' => :erlang,
65
+ 'Fortran files' => :fortran,
66
+ 'Haskell files' => :haskell,
67
+ 'hh files' => :hh,
68
+ 'HTML files' => :html,
69
+ 'Java files' => :java,
70
+ 'Javascript file' => :js,
71
+ 'jsp files' => :jsp,
72
+ 'Lisp files' => :lisp,
73
+ 'Makefiles' => :make,
74
+ 'Mason files' => :mason,
75
+ 'OCaml files' => :ocaml,
76
+ 'Parrot files' => :parrot,
77
+ 'Perl files' => :perl,
78
+ 'PHP files' => :php,
79
+ 'Prolog files' => :prolog,
80
+ 'Python files' => :python,
81
+ 'Ruby files' => :ruby,
82
+ 'Scheme files' => :scheme,
83
+ 'Shell files' => :shell,
84
+ 'SQL files' => :sql,
85
+ 'TCL files' => :tcl,
86
+ 'TeX files' => :tex,
87
+ 'Text files' => :text,
88
+ 'tt files' => :tt,
89
+ 'Visual Basic files' => :vb,
90
+ 'Vim files' => :vim,
91
+ 'XML files' => :xml,
92
+ 'YAML files' => :yaml
93
+ }
94
+
95
+ =begin rdoc
96
+ A specialized completer for use with the line edits in the dialog.
97
+
98
+ The only difference from the standard @Qt::Completer@ is that it creates its model by itself
99
+ =end
100
+ class Completer < Qt::Completer
101
+
102
+ =begin rdoc
103
+ @param [Qt::Widget, nil] parent the completer's parent object
104
+ =end
105
+ def initialize parent = nil
106
+ super
107
+ self.model = Qt::StringListModel.new self
108
+ end
109
+
110
+ =begin rdoc
111
+ Adds a new string to the completer
112
+
113
+ @param [String] str the string to add
114
+ @return [nil]
115
+ =end
116
+ def add_entry str
117
+ row = model.row_count
118
+ model.insert_rows row, 1
119
+ model.set_data model.index(row), Qt::Variant.new(str), Qt::DisplayRole
120
+ end
121
+
122
+ end
123
+
124
+ =begin rdoc
125
+ The action chosen by the user when closing the dialog
126
+ @return [Symbol, nil] @:find@ if the user pressed the Find button, @:replace@ if
127
+ he chose the Replace button and *nil* if he pressed the Cancel button or if the
128
+ dialog hasn’t been closed as yet
129
+ =end
130
+ attr_reader :action
131
+
132
+ =begin rdoc
133
+ @param [Qt::Widget, nil] parent the parent widget
134
+ =end
135
+ def initialize parent = Ruber[:main_window]
136
+ @operation = nil
137
+ super
138
+ set_buttons User1|User2|Cancel
139
+ set_button_text User1, KDE.i18n('Replace')
140
+ set_button_text User2, KDE.i18n('Find')
141
+ set_default_button User2
142
+ enable_button User1, false
143
+ enable_button User2, false
144
+
145
+ @ui = ::Ui::FindReplaceInFilesWidget.new
146
+ @ui.setupUi main_widget
147
+
148
+ @ui.find_text.completer = Completer.new @ui.find_text
149
+ @ui.replace_text.completer = Completer.new @ui.replace_text
150
+ @ui.custom_filter.completer = Completer.new @ui.custom_filter
151
+
152
+ @ui.directory.mode = KDE::File::Directory
153
+
154
+
155
+ @ui.find_text.connect SIGNAL('textChanged(QString)') do
156
+ enable = !@ui.find_text.text.empty?
157
+ enable_button User1, enable
158
+ enable_button User2, enable
159
+ end
160
+
161
+ @ui.replace_text.connect(SIGNAL('textChanged(QString)')) do
162
+ self.default_button = @ui.replace_text.text.empty? ? User2 : User1
163
+ end
164
+
165
+ @ui.places.connect(SIGNAL('currentIndexChanged(int)')) do |i|
166
+ @ui.directory.enabled = (i == @ui.places.count - 1)
167
+ end
168
+
169
+ button(User1).connect SIGNAL(:clicked) do
170
+ @action = :replace
171
+ add_completions
172
+ accept
173
+ emit replace
174
+ end
175
+
176
+ button(User2).connect SIGNAL(:clicked) do
177
+ @action = :find
178
+ add_completions
179
+ accept
180
+ emit find
181
+ end
182
+
183
+ @ui.use_predefined_filters.connect SIGNAL('toggled(bool)') do |b|
184
+ @ui.types.enabled = b
185
+ end
186
+
187
+ @ui.all_files.connect SIGNAL('toggled(bool)') do |b|
188
+ @ui.types.enabled = !b
189
+ end
190
+
191
+ @ui.types.line_edit.read_only = true
192
+ @ui.types.view.install_event_filter Filter.new(self)
193
+ @ui.types.view.viewport.install_event_filter Filter.new(self)
194
+ @ui.types.add_items FILE_TYPES.keys.sort
195
+ @ui.types.model.each do |i|
196
+ i.checkable = true
197
+ i.checked = (i.text == 'Ruby files' || i.text == 'YAML files')
198
+ end
199
+ create_filter_text
200
+
201
+ end
202
+
203
+ =begin rdoc
204
+ Whether the "Project files" and "Project directory" entries should be shown
205
+ in the _Search in_ widget or not.
206
+ @param [Boolean] val whether to show the entries or not
207
+ @return [Boolean] _val_
208
+ =end
209
+ def allow_project= val
210
+ if @ui.places.count == 4 and !val
211
+ 2.times{@ui.places.remove_item 0}
212
+ elsif @ui.places.count == 2 and val
213
+ @ui.places.insert_items 0, ['Project files', 'Project directory']
214
+ end
215
+ end
216
+
217
+ =begin rdoc
218
+ The text in the Find widget
219
+ @return [String] the text in the Find widget
220
+ =end
221
+ def find_text
222
+ @ui.find_text.text
223
+ end
224
+
225
+ =begin rdoc
226
+ The text in the Replace widget
227
+ @return [String] the text in the Replace widget
228
+ =end
229
+ def replacement_text
230
+ @ui.replace_text.text
231
+ end
232
+
233
+ =begin rdoc
234
+ The mode chosen by the user.
235
+ @return [Symbol] the mode chosen by the user. It can be either @:regexp@ or @:plain@
236
+ =end
237
+ def mode
238
+ MODES[@ui.mode.current_index]
239
+ end
240
+
241
+ =begin rdoc
242
+ Whether the search should be performed only in whole words or not
243
+
244
+ @return [Boolean] whether the user chose to perform a search only on whole words
245
+ or also in the middle of a word
246
+ =end
247
+ def whole_words?
248
+ @ui.whole_words.checked?
249
+ end
250
+
251
+ =begin rdoc
252
+ Whether the search should be case sensitive or not
253
+
254
+ @return [Boolean] whether or not the user chose to perform a case sensitive search
255
+ =end
256
+ def case_sensitive?
257
+ @ui.case_sensitive.checked?
258
+ end
259
+
260
+ =begin rdoc
261
+ The file types the search should be restricted to
262
+ @return [<Symbol>, nil] an array containing the file types the user chose in the
263
+ types combo box (converted so they match the name of the options rak accepts) or
264
+ nil if the user checked the _All files_ radio button
265
+ =end
266
+ def filters
267
+ if @ui.use_predefined_filters.checked?
268
+ @ui.types.model.map{|i| i.checked? ? FILE_TYPES[i.text] : nil}.compact
269
+ else nil
270
+ end
271
+ end
272
+
273
+ =begin rdoc
274
+ The regexp to pass to rak -g option
275
+
276
+ @return [String, nil] the source of the regexp or *nil* if the user didn’t fill
277
+ the _Custom filter_ widget
278
+ =end
279
+ def custom_filter
280
+ text = @ui.custom_filter.text
281
+ text.empty? ? nil : text
282
+ end
283
+
284
+ =begin rdoc
285
+ Whether the search should be made on all files or only on some file types
286
+
287
+ @return [Boolean] whether the search should be performed on all files or only in
288
+ those with file type matching the entries selected in the File type combo box
289
+ =end
290
+ def all_files?
291
+ @ui.all_files.checked?
292
+ end
293
+
294
+ =begin rdoc
295
+ Where to find the files to search:
296
+
297
+ @return [Symbol] one of the following symbols, according to what the user chose
298
+ in the _Search in_ combo box:
299
+ * @:project_files@ perform the search only among the files belonging to the current project
300
+ * @:project_directory@ perform the search among all the files in the current proejct’s directory
301
+ * @:open_files@ perform the search only in files corresponding to open documents (note that the search will be performed in the files, not in the text in the documents)
302
+ * @:custom_dir@ perform the search among all the files in the directory selected by the user in the Directory widget
303
+ =end
304
+ def places
305
+ idx = @ui.places.current_index
306
+ idx +=2 if @ui.places.count == 2
307
+ PLACES[idx]
308
+ end
309
+
310
+ =begin rdoc
311
+ The contents of the Directory widget
312
+ @return [String, nil] the contents of the Directory widget or nil if the wigdet
313
+ is disabled (because the user chose something else than _Custom directory_ in the
314
+ _Search in_ widget)
315
+ =end
316
+ def directory
317
+ @ui.directory.enabled? ? @ui.directory.text : nil
318
+ end
319
+
320
+ =begin rdoc
321
+ Sets the contents of the dialog’s widgets to their default values
322
+
323
+ This method doesn't erase the completers and is usually called before showing the dialog.
324
+ *Note:* this also sets allow_project to true
325
+ =end
326
+ def clear
327
+ self.allow_project = true
328
+ @action = nil
329
+ @ui.find_text.text=''
330
+ @ui.replace_text.clear
331
+ @ui.mode.current_index = 0
332
+ @ui.whole_words.checked = false
333
+ @ui.case_sensitive.checked = true
334
+ @ui.use_predefined_filters.checked = true
335
+ @ui.custom_filter.clear
336
+ @ui.places.current_index = 0
337
+ @ui.find_text.set_focus
338
+ @ui.directory.enabled = false
339
+ end
340
+
341
+ =begin rdoc
342
+ Event filter to make the Predefined filters combo box be checkable.
343
+
344
+ @return [Boolean] *true* if the event should be blocked and *false* if it should be propagated
345
+ =end
346
+ def eventFilter obj, e
347
+ if e.type == Qt::Event::MouseButtonRelease
348
+ obj=obj.parent unless obj.is_a?(Qt::ListView)
349
+ idx = obj.index_at e.pos
350
+ if idx.valid?
351
+ op = Qt::StyleOption.new
352
+ op.initFrom obj
353
+ op.rect = obj.visual_rect(idx)
354
+ r = obj.style.sub_element_rect(Qt::Style::SE_ViewItemCheckIndicator, op)
355
+ if r.contains(e.pos)
356
+ it = @ui.types.model.item_from_index idx
357
+ it.checked = !it.checked?
358
+ create_filter_text
359
+ return true
360
+ end
361
+ end
362
+ end
363
+ false
364
+ end
365
+
366
+ private
367
+
368
+ =begin rdoc
369
+ Adds the values in the various line edit widgets to the respective completers
370
+
371
+ @return [nil]
372
+ =end
373
+ def add_completions
374
+ @ui.find_text.completer.add_entry @ui.find_text.text
375
+ @ui.replace_text.completer.add_entry @ui.replace_text.text unless @ui.replace_text.text.empty?
376
+ @ui.custom_filter.completer.add_entry @ui.custom_filter.text unless @ui.custom_filter.text.empty?
377
+ end
378
+
379
+ =begin rdoc
380
+ Fills the text of the File type widget with a string according to the selected entries
381
+
382
+ @return [nil]
383
+ =end
384
+ def create_filter_text
385
+ text = @ui.types.model.select{|i| i.checked?}.map{|i| i.text}.join '; '
386
+ text = 'All files' if text.empty?
387
+ @ui.types.edit_text = text
388
+ end
389
+
390
+ =begin rdoc
391
+ @private
392
+
393
+ Helper class whose only task is to have an eventFilter method which calls its parent’s eventFilter.
394
+
395
+ It’s needed because setting the dialog as event filter object doesn’t seem to work
396
+ =end
397
+ class Filter < Qt::Object
398
+ =begin rdoc
399
+ Calls the parent object's @eventFilter@ method
400
+ @return [Boolean] what the parent object's @eventFilter@ method returned
401
+ =end
402
+ def eventFilter o, e
403
+ parent.eventFilter o, e
404
+ end
405
+ end
406
+
407
+ end
408
+
409
+ end
410
+
411
+ end
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE kpartgui SYSTEM 'kpartgui.dtd'>
2
+ <kpartplugin version="1" name="ruber-find_in_files" >
3
+ <MenuBar>
4
+ <Menu name="edit" >
5
+ <text>&amp;Edit</text>
6
+ <Action group="edit_find_plugins" name="find_in_files-find_replace" />
7
+ <Separator group="edit_find_plugins" />
8
+ </Menu>
9
+ </MenuBar>
10
+ <ActionProperties/>
11
+ </kpartplugin>
@@ -0,0 +1,485 @@
1
+ =begin
2
+ Copyright (C) 2010 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ require 'tempfile'
22
+
23
+ require 'ruber/filtered_output_widget'
24
+
25
+ require_relative 'ui/config_widget'
26
+
27
+ module Ruber
28
+
29
+ module FindInFiles
30
+
31
+ =begin rdoc
32
+ Tool widget to display the output of a search.
33
+
34
+ It adds the "Filter on file names" toggle action to the RBM menu: when its on,
35
+ any filter will be applied to the rows containing the file names; when it's off
36
+ (the default) the filter will only be applied to rows containing the found text.
37
+ =end
38
+ class FindWidget < FilteredOutputWidget
39
+
40
+ slots 'filter_on_filename_changed(bool)'
41
+
42
+ =begin rdoc
43
+ @private
44
+ The number of the role used to store the kind of find information stored in an
45
+ index
46
+ =end
47
+ FIND_ROLE = IsTitleRole + 1
48
+
49
+ =begin rdoc
50
+ Creates a new instance
51
+ @param [Qt::Widget, nil] parent the parent widget
52
+ =end
53
+ def initialize parent = nil
54
+ super parent, :view => :tree, :filter => Filter.new, :use_default_font => true
55
+ filter_model.filter_key_column = 1
56
+ filter_model.exclude = :toplevel
57
+ model.append_column []
58
+ @current_file_item = Qt::StandardItem.new
59
+ filter_model.connect(SIGNAL('rowsInserted(QModelIndex, int, int)')) do |par, st, en|
60
+ if !par.valid?
61
+ st.upto(en) do |i|
62
+ view.set_first_column_spanned i, par, true
63
+ view.expand filter_model.index(i, 0, par)
64
+ end
65
+ end
66
+ end
67
+ self.connect(SIGNAL(:about_to_fill_menu)) do
68
+ actions.delete 'copy'
69
+ actions.delete 'copy_selected'
70
+ action_list.delete 'copy'
71
+ action_list.delete 'copy_selected'
72
+ end
73
+ view.all_columns_show_focus = true
74
+ view.header_hidden = true
75
+ setup_actions
76
+ end
77
+
78
+ =begin rdoc
79
+ Displays the output of rak in the widget
80
+
81
+ Results in the same file are put under a single parent in the tree
82
+
83
+ @param [<String>] lines the output from rak, divided into lines. Each line should
84
+ have the format @filename line|text@. Lines which don't have this format are ignored
85
+ @return [nil]
86
+ =end
87
+ def display_output lines
88
+ lines.each do |l|
89
+ match = l.match(/^(.+)\s+(\d+)\|(.*)$/)
90
+ next unless match
91
+ file, line, text = match.to_a[1..-1].map{|i| i.strip}
92
+ if @current_file_item.text != file
93
+ @current_file_item = model.insert(file, :message, nil)[0]
94
+ @current_file_item.set_data Qt::Variant.new('file'), FIND_ROLE
95
+ end
96
+ it_line, it_text = model.insert [line.to_s, text], [:output1, :output],
97
+ nil, :parent => @current_file_item
98
+ it_line.set_data Qt::Variant.new('line'), FIND_ROLE
99
+ it_text.set_data Qt::Variant.new('text'), FIND_ROLE
100
+ end
101
+ nil
102
+ end
103
+
104
+ =begin rdoc
105
+ Remove the contents from the widget
106
+
107
+ @return *nil*
108
+ =end
109
+ def clear_output
110
+ @current_file_item = Qt::StandardItem.new
111
+ super
112
+ nil
113
+ end
114
+
115
+ private
116
+
117
+ =begin rdoc
118
+ Adds the custom actions to the RMB menu
119
+
120
+ @return [nil]
121
+ =end
122
+ def setup_actions
123
+ a = KDE::ToggleAction.new 'Filter on File Names', self
124
+ actions['find_in_files-filter_on_files'] = a
125
+ action_list.insert_after 'clear_filter', nil, 'find_in_files-filter_on_files'
126
+ connect a, SIGNAL('toggled(bool)'), self, SLOT('filter_on_filename_changed(bool)')
127
+ nil
128
+ end
129
+
130
+ =begin rdoc
131
+ Override of {Ruber::OutputWidget#find_filename_in_index}
132
+
133
+ If the index corresponds to a file name, retrieves the line from the
134
+ first child, while if it corresponds to a another entry, retrieves the file name
135
+ from its parent and the line from the appropriate column on the same line
136
+
137
+ @return [String, Integer] see {Ruber::OutputWidget#find_filename_in_index} for
138
+ more information
139
+ =end
140
+ def find_filename_in_index idx
141
+ it = model.item_from_index idx
142
+ if it.data(FIND_ROLE).to_string == 'file'
143
+ line = it.row_count > 0 ? it.child(0,0).text.to_i : 0
144
+ [it.text, line]
145
+ else
146
+ file = it.parent.text
147
+ line = (it.data(FIND_ROLE).to_string == 'line' ? it : it.parent.child(it.row, 0)).text.to_i
148
+ [file, line]
149
+ end
150
+ end
151
+
152
+ =begin rdoc
153
+ Slot called when the user toggles the "Filter on file names" action
154
+
155
+ @param [Boolean] on whether the user switched the action on or off
156
+ =end
157
+ def filter_on_filename_changed b
158
+ filter_model.filter_key_column = b ? 0 : 1
159
+ filter_model.exclude = b ? :children : :toplevel
160
+ end
161
+
162
+ =begin rdoc
163
+ @private
164
+ Filter model used by the view in FindWidget
165
+ =end
166
+ class Filter < FilterModel
167
+
168
+ =begin rdoc
169
+ Filters a row
170
+
171
+ If the filter column is the first, always returns *true* for child rows, while
172
+ always returns *true* for top level rows for all other values of the filter column
173
+
174
+ @return [Boolean]
175
+ =end
176
+ def filterAcceptsRow row, parent
177
+ if filter_key_column == 0
178
+ parent.valid? ? true : super
179
+ else parent.valid? ? super : true
180
+ end
181
+ end
182
+
183
+ end
184
+
185
+ end
186
+
187
+ =begin rdoc
188
+ Tool widget which displays the results of a replace operation
189
+
190
+ The widget consists of a checkable tree view with two columns. The first column
191
+ contains the text which will be replaced, while the second contains the text
192
+ after the replacement. The user can uncheck the replacements he doesn't want,
193
+ both linewise or filewise. The replacement is only carried out when the user
194
+ presses the Replace button in the tool widget. A Clear button empties the tool
195
+ widget.
196
+ =end
197
+ class ReplaceWidget < OutputWidget
198
+
199
+ =begin rdoc
200
+ @private
201
+
202
+ Model used in the ReplaceWidget
203
+
204
+ It only differs from #{OutputWidget::Model} in the flags it gives to items
205
+ =end
206
+ class Model < OutputWidget::Model
207
+
208
+ def flags idx
209
+ default_flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled
210
+ if !idx.parent.valid? or idx.column == 0
211
+ default_flags | Qt::ItemIsUserCheckable
212
+ else default_flags
213
+ end
214
+
215
+ end
216
+
217
+ end
218
+
219
+ =begin rdoc
220
+ Signal emitted when a new file is added to the model.
221
+
222
+ @param [String] str the name of the file
223
+ =end
224
+ signals 'file_added(QString)'
225
+
226
+ slots :replace
227
+
228
+ slots 'file_modified(QString)'
229
+
230
+ =begin rdoc
231
+ Creates a new instance
232
+
233
+ @param [Qt::Object, nil] parent the parent object
234
+ =end
235
+ def initialize parent = nil
236
+ super parent, :view => :tree, :use_default_font => true
237
+ self.auto_scroll = false
238
+ model.global_flags |= Qt::ItemIsUserCheckable.to_i
239
+ def model.flags idx
240
+ if idx.column == 0 then super idx
241
+ else (Qt::ItemIsEnabled | Qt::ItemIsSelectable).to_i
242
+ end
243
+ end
244
+ @replace_button = Qt::PushButton.new( 'Replace', self){self.enabled = false}
245
+ @clear_button = Qt::PushButton.new('Clear', self){self.enabled = false}
246
+ layout.remove_widget view
247
+ layout.add_widget view, 0,0,1,0
248
+ layout.add_widget @replace_button, 1, 0
249
+ layout.add_widget @clear_button, 1, 1
250
+ model.horizontal_header_labels = ['Line', 'Original text', 'Replacement text']
251
+ @file_items = {}
252
+ @watcher = KDE::DirWatch.new self
253
+ connect @watcher, SIGNAL('dirty(QString)'), self, SLOT('file_modified(QString)')
254
+ connect @replace_button, SIGNAL(:clicked), self, SLOT(:replace)
255
+ connect @clear_button, SIGNAL(:clicked) , self, SLOT(:clear_output)
256
+ self.connect(SIGNAL(:about_to_fill_menu)) do
257
+ actions.delete 'copy'
258
+ actions.delete 'copy_selected'
259
+ action_list.delete 'copy'
260
+ action_list.delete 'copy_selected'
261
+ end
262
+ model.connect(SIGNAL('rowsInserted(QModelIndex, int, int)')) do |par, st, en|
263
+ if !par.valid?
264
+ st.upto(en) do |i|
265
+ view.set_first_column_spanned i, par, true
266
+ view.expand model.index(i, 0, par)
267
+ end
268
+ end
269
+ end
270
+ Ruber[:find_in_files].connect SIGNAL(:replace_search_started) do
271
+ @replace_button.enabled = false
272
+ @clear_button.enabled = false
273
+ view.header.resize_mode = Qt::HeaderView::Fixed
274
+ self.cursor = Qt::Cursor.new Qt::WaitCursor
275
+ end
276
+ Ruber[:find_in_files].connect SIGNAL(:replace_search_finished) do
277
+ @watcher.start_scan
278
+ @replace_button.enabled = true
279
+ @clear_button.enabled = true
280
+ h = view.header
281
+ view.resize_column_to_contents 0
282
+ view.header.resize_mode = Qt::HeaderView::Fixed
283
+ av_size = h.rect.width - h.section_size( 0)
284
+ view.set_column_width 1, av_size / 2.0
285
+ view.set_column_width 2, av_size / 2.0
286
+ unset_cursor
287
+ end
288
+ view.all_columns_show_focus = true
289
+ end
290
+
291
+ =begin rdoc
292
+ Inserts the name of the files in the output widget
293
+
294
+ Each file is added to the file watcher, so that replacing in it can be disabled
295
+ if it changes.
296
+
297
+ @param [<String>] lines an array containing the name of the files
298
+ @return [nil]
299
+ =end
300
+ def display_output lines
301
+ lines.each do |l|
302
+ it = model.insert([l, nil, nil], :message, nil)[0]
303
+ it.checked = true
304
+ @watcher.add_file l
305
+ @watcher.stop_scan
306
+ @file_items[l] = it
307
+ emit file_added(l)
308
+ end
309
+ nil
310
+ end
311
+
312
+ =begin rdoc
313
+ Adds a replacement line to the widget
314
+
315
+ A replacement line is made of three columns: the line number in the file,
316
+ the original text and the text after the replacement.
317
+
318
+ @param [String] file the file where the line is
319
+ @param [Integer] line the line number (0 based)
320
+ @param [String] orig the original text of the line
321
+ @param [String] repl the text of the line after the replacement
322
+ @return [nil]
323
+ =end
324
+ def add_line file, line, orig, repl
325
+ parent = @file_items[file]
326
+ row = model.insert [(line+1).to_s, orig, repl], [:output1, :output, :output], nil, :parent => parent
327
+ row[0].checked = true
328
+ view.expand parent.index
329
+ nil
330
+ end
331
+
332
+ =begin rdoc
333
+ Empties the widget
334
+
335
+ @return [nil]
336
+ =end
337
+ def clear_output
338
+ @file_items.clear
339
+ @file_items.each_key{|k| @watcher.remove_file k}
340
+ @replace_button.enabled = false
341
+ @clear_button.enabled = false
342
+ super
343
+ end
344
+
345
+ private
346
+
347
+ =begin rdoc
348
+ Performs the replacements chosen by the user
349
+
350
+ Calling this method applies replaces the original text with the replacement
351
+ texts for all the lines chosen by the user (that is, the checked lines which
352
+ belong to a checked file). A message box is shown if some replacements cannot be
353
+ carrried out.
354
+
355
+ Items corresponding to successful replacements are removed from the view.
356
+
357
+ @return [nil]
358
+ =end
359
+ def replace
360
+ failed = {}
361
+ success = []
362
+ docs = Ruber[:docs].documents_with_file.map{|d| [d.path, d]}.to_h
363
+ model.each_row.each_with_index do |r, i|
364
+ if r[0].checked?
365
+ res = replace_file r[0], docs[r[0].text]
366
+ if res then failed[r[0].text] = res
367
+ else success << r[0]
368
+ end
369
+ end
370
+ end
371
+ success.reverse_each do |i|
372
+ @file_items.delete i.text
373
+ model.remove_row i.row
374
+ end
375
+ create_error_message = Proc.new do |f, err|
376
+ if err == :doc_modified then "#{f}: modified in editor"
377
+ else "#{f}: #{err.message}"
378
+ end
379
+ end
380
+ unless failed.empty?
381
+ failed_text = failed.map{|f, err| create_error_message.call f, err}.join "\n"
382
+ KDE::MessageBox.sorry Ruber[:main_window], "The following files couldn't be modified:\n#{failed_text}"
383
+ end
384
+ nil
385
+ end
386
+
387
+ =begin rdoc
388
+ Carries out the replacements for a file
389
+
390
+ If the file is associated with a document and the document isn't modified, the
391
+ text in the editor is changed to reflect the modifications in the file. If the
392
+ document is modified (which means its contents differ from the contents of the
393
+ file), instead, nothing will be done.
394
+
395
+ @param [Qt::StandardItem] it the item corresponding to the file in the model
396
+ @param [Document, nil] doc the document associated with the file, if any
397
+ @return [Symbol, SystemCallError, nil] *nil* if the replacement was carried out
398
+ correctly; an exception derived from @SystemCallError@ if it wasn't possible to
399
+ write the file and the symbol @:doc_modified@ if the replacement wasn't attempted
400
+ because the document corresponding to the file was modified
401
+ =end
402
+ def replace_file it, doc
403
+ file = it.text
404
+ lines_to_replace = {}
405
+ it.each_row do |line, _, repl|
406
+ lines_to_replace[line.text.to_i] = repl.text if line.checked?
407
+ end
408
+ # TODO see what the line below did. In my opinion, it is the remainder of some line
409
+ # I added for testing and forgot to remove
410
+ # path = file.sub( '/home/stefano/tmp/ruber', '').gsub('/', '_')
411
+ lines = File.readlines(file)
412
+ lines_to_replace.each_pair{|idx, text| lines[idx - 1] = text + "\n"}
413
+ new_text = lines.join ''
414
+ if doc
415
+ pos = doc.view.cursor_position if doc.view
416
+ return :doc_modified if doc.modified?
417
+ text = nil
418
+ doc.editing do
419
+ text = doc.text
420
+ doc.clear
421
+ doc.text = new_text
422
+ end
423
+ doc.save
424
+ doc.view.go_to pos.line, pos.column if pos
425
+ else
426
+ Tempfile.open(File.basename(file)) do |f|
427
+ f.write new_text
428
+ f.flush
429
+ begin
430
+ FileUtils.cp f.path, file
431
+ rescue SystemCallError => e
432
+ return e
433
+ end
434
+ end
435
+ end
436
+ nil
437
+ end
438
+
439
+ =begin rdoc
440
+ Slot called when a file among those listed for replacement is modified on disk
441
+
442
+ When this happens, the file is marked as modified in the view and it is no longer
443
+ checkable (the same happens for its children)
444
+
445
+ @return [nil]
446
+ =end
447
+ def file_modified file
448
+ @watcher.remove_file file
449
+ it = @file_items[file]
450
+ if it
451
+ it.text = it.text + "\t [MODIFIED]"
452
+ it.checked = false
453
+ view.collapse it.index
454
+ model.set_data it.index, Qt::Variant.new, Qt::ForegroundRole
455
+ it.flags = Qt::ItemIsSelectable
456
+ it.each_row do |r|
457
+ r[0].checked = false
458
+ r.each{|i| i.flags = Qt::ItemIsSelectable}
459
+ end
460
+ end
461
+ nil
462
+ end
463
+
464
+ end
465
+
466
+ =begin rdoc
467
+ The configuration widget for the plugin
468
+ =end
469
+ class ConfigWidget < Qt::Widget
470
+
471
+ =begin rdoc
472
+ @param [Qt::Widget, nil] parent the parent widget
473
+ =end
474
+ def initialize parent = nil
475
+ super
476
+ @ui = ::Ui::FindInFilesConfigWidget.new
477
+ @ui.setupUi self
478
+ end
479
+
480
+ end
481
+
482
+
483
+ end
484
+
485
+ end