ruber 0.0.1.1

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