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,92 @@
1
+ =begin
2
+ ** Form generated from reading ui file 'config_widget.ui'
3
+ **
4
+ ** Created: ven ott 29 17:39:04 2010
5
+ ** by: Qt User Interface Compiler version 4.7.0
6
+ **
7
+ ** WARNING! All changes made in this file will be lost when recompiling ui file!
8
+ =end
9
+
10
+ class Ui_StateConfigWidget
11
+ attr_reader :verticalLayout
12
+ attr_reader :groupBox
13
+ attr_reader :gridLayout
14
+ attr_reader :_state__restore_cursor_position
15
+ attr_reader :_state__restore_project_files
16
+ attr_reader :horizontalLayout_2
17
+ attr_reader :label_2
18
+ attr_reader :_state__startup_behaviour
19
+
20
+ def setupUi(stateConfigWidget)
21
+ if stateConfigWidget.objectName.nil?
22
+ stateConfigWidget.objectName = "stateConfigWidget"
23
+ end
24
+ stateConfigWidget.resize(400, 128)
25
+ @verticalLayout = Qt::VBoxLayout.new(stateConfigWidget)
26
+ @verticalLayout.objectName = "verticalLayout"
27
+ @groupBox = Qt::GroupBox.new(stateConfigWidget)
28
+ @groupBox.objectName = "groupBox"
29
+ @gridLayout = Qt::GridLayout.new(@groupBox)
30
+ @gridLayout.objectName = "gridLayout"
31
+ @_state__restore_cursor_position = Qt::CheckBox.new(@groupBox)
32
+ @_state__restore_cursor_position.objectName = "_state__restore_cursor_position"
33
+
34
+ @gridLayout.addWidget(@_state__restore_cursor_position, 0, 0, 1, 1)
35
+
36
+ @_state__restore_project_files = Qt::CheckBox.new(@groupBox)
37
+ @_state__restore_project_files.objectName = "_state__restore_project_files"
38
+
39
+ @gridLayout.addWidget(@_state__restore_project_files, 1, 0, 1, 1)
40
+
41
+ @horizontalLayout_2 = Qt::HBoxLayout.new()
42
+ @horizontalLayout_2.objectName = "horizontalLayout_2"
43
+ @label_2 = Qt::Label.new(@groupBox)
44
+ @label_2.objectName = "label_2"
45
+
46
+ @horizontalLayout_2.addWidget(@label_2)
47
+
48
+ @_state__startup_behaviour = KDE::ComboBox.new(@groupBox)
49
+ @_state__startup_behaviour.objectName = "_state__startup_behaviour"
50
+
51
+ @horizontalLayout_2.addWidget(@_state__startup_behaviour)
52
+
53
+
54
+ @gridLayout.addLayout(@horizontalLayout_2, 2, 0, 1, 1)
55
+
56
+
57
+ @verticalLayout.addWidget(@groupBox)
58
+
59
+
60
+ retranslateUi(stateConfigWidget)
61
+
62
+ Qt::MetaObject.connectSlotsByName(stateConfigWidget)
63
+ end # setupUi
64
+
65
+ def setup_ui(stateConfigWidget)
66
+ setupUi(stateConfigWidget)
67
+ end
68
+
69
+ def retranslateUi(stateConfigWidget)
70
+ stateConfigWidget.windowTitle = Qt::Application.translate("StateConfigWidget", "Form", nil, Qt::Application::UnicodeUTF8)
71
+ @groupBox.title = Qt::Application.translate("StateConfigWidget", "State", nil, Qt::Application::UnicodeUTF8)
72
+ @_state__restore_cursor_position.text = Qt::Application.translate("StateConfigWidget", "Restore cursor position when opening documents", nil, Qt::Application::UnicodeUTF8)
73
+ @_state__restore_project_files.text = Qt::Application.translate("StateConfigWidget", "Restore open files when opening a project", nil, Qt::Application::UnicodeUTF8)
74
+ @label_2.text = Qt::Application.translate("StateConfigWidget", "On startup", nil, Qt::Application::UnicodeUTF8)
75
+ @_state__startup_behaviour.insertItems(0, [Qt::Application.translate("StateConfigWidget", "Restore open project and files", nil, Qt::Application::UnicodeUTF8),
76
+ Qt::Application.translate("StateConfigWidget", "Restore open files only", nil, Qt::Application::UnicodeUTF8),
77
+ Qt::Application.translate("StateConfigWidget", "Restore open project only", nil, Qt::Application::UnicodeUTF8),
78
+ Qt::Application.translate("StateConfigWidget", "Keep empty workspace", nil, Qt::Application::UnicodeUTF8)])
79
+ @_state__startup_behaviour.setProperty("access", Qt::Variant.new(Qt::Application.translate("StateConfigWidget", "$startup_behaviour", nil, Qt::Application::UnicodeUTF8)))
80
+ end # retranslateUi
81
+
82
+ def retranslate_ui(stateConfigWidget)
83
+ retranslateUi(stateConfigWidget)
84
+ end
85
+
86
+ end
87
+
88
+ module Ui
89
+ class StateConfigWidget < Ui_StateConfigWidget
90
+ end
91
+ end # module Ui
92
+
@@ -0,0 +1,89 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ui version="4.0">
3
+ <class>StateConfigWidget</class>
4
+ <widget class="QWidget" name="StateConfigWidget">
5
+ <property name="geometry">
6
+ <rect>
7
+ <x>0</x>
8
+ <y>0</y>
9
+ <width>400</width>
10
+ <height>128</height>
11
+ </rect>
12
+ </property>
13
+ <property name="windowTitle">
14
+ <string>Form</string>
15
+ </property>
16
+ <layout class="QVBoxLayout" name="verticalLayout">
17
+ <item>
18
+ <widget class="QGroupBox" name="groupBox">
19
+ <property name="title">
20
+ <string>State</string>
21
+ </property>
22
+ <layout class="QGridLayout" name="gridLayout">
23
+ <item row="0" column="0">
24
+ <widget class="QCheckBox" name="_state__restore_cursor_position">
25
+ <property name="text">
26
+ <string>Restore cursor position when opening documents</string>
27
+ </property>
28
+ </widget>
29
+ </item>
30
+ <item row="1" column="0">
31
+ <widget class="QCheckBox" name="_state__restore_project_files">
32
+ <property name="text">
33
+ <string>Restore open files when opening a project</string>
34
+ </property>
35
+ </widget>
36
+ </item>
37
+ <item row="2" column="0">
38
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
39
+ <item>
40
+ <widget class="QLabel" name="label_2">
41
+ <property name="text">
42
+ <string>On startup</string>
43
+ </property>
44
+ </widget>
45
+ </item>
46
+ <item>
47
+ <widget class="KComboBox" name="_state__startup_behaviour">
48
+ <property name="access" stdset="0">
49
+ <string>$startup_behaviour</string>
50
+ </property>
51
+ <item>
52
+ <property name="text">
53
+ <string>Restore open project and files</string>
54
+ </property>
55
+ </item>
56
+ <item>
57
+ <property name="text">
58
+ <string>Restore open files only</string>
59
+ </property>
60
+ </item>
61
+ <item>
62
+ <property name="text">
63
+ <string>Restore open project only</string>
64
+ </property>
65
+ </item>
66
+ <item>
67
+ <property name="text">
68
+ <string>Keep empty workspace</string>
69
+ </property>
70
+ </item>
71
+ </widget>
72
+ </item>
73
+ </layout>
74
+ </item>
75
+ </layout>
76
+ </widget>
77
+ </item>
78
+ </layout>
79
+ </widget>
80
+ <customwidgets>
81
+ <customwidget>
82
+ <class>KComboBox</class>
83
+ <extends>QComboBox</extends>
84
+ <header>kcombobox.h</header>
85
+ </customwidget>
86
+ </customwidgets>
87
+ <resources/>
88
+ <connections/>
89
+ </ui>
@@ -0,0 +1,18 @@
1
+ name: syntax_checker
2
+ version: 0.0.1
3
+ about:
4
+ authors: [Stefano Crocco, stefano.crocco@alice.it]
5
+ license: :gpl
6
+ description: Checks the syntax of the current document
7
+ icon: tools-check-spelling
8
+ bug_address: http://github.com/stcrocco/ruber/issues
9
+ class: Ruber::SyntaxChecker::SyntaxCheckerPlugin
10
+ require: syntax_checker
11
+ deps: ruby_development
12
+ config_options:
13
+ syntax_checker:
14
+ automatic_check: {default: true}
15
+ config_widgets:
16
+ {caption: Syntax, code: 'Qt::CheckBox.new("&Automatically check syntax"){self.object_name = "_syntax_checker__automatic_check"}', pixmap: tools-check-spelling.png}
17
+ extensions:
18
+ syntax_checker: {class: Ruber::SyntaxChecker::SyntaxCheckerExtension, scope: document}
@@ -0,0 +1,662 @@
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
+ require 'open3'
23
+
24
+ require 'facets/boolean'
25
+
26
+ module Ruber
27
+
28
+ =begin rdoc
29
+ Module for the *Syntax checker* plugin.
30
+
31
+ This plugin provides a framework to let the user know whether the file in the
32
+ current editor view is syntactically correct or not (according to the most appropriate
33
+ sytnax for that kind of file). The result of the syntax check is displayed in a
34
+ widget at the right end of the status bar (this will be referred to as the *syntax
35
+ result widget). This implementation of the @syntax_checker@ feature uses a @KDE::Led@
36
+ whose colour changes according to the result of the syntax check, but other plugins
37
+ may use other widgets. If the user right clikcs on the widget, a menu showing a
38
+ list of syntax errors is displayed. Clicking on it will jump to the corresponding
39
+ like, while clicking on the widget itself jumps to the line corresponding to the
40
+ first error.
41
+
42
+ The syntax check is run every time the file is activated or saved and the user can choose to
43
+ have it run automatically when one second has passed since he last edited the
44
+ file. The reasoning is that it makes little sense to check the syntax while the
45
+ user is writing text: it will most likely be wrong (because the user hasn't finished
46
+ writing what he wants), may quickly change from valid to invalid depending on the
47
+ point the user has reached in writing, and the user won't look at it because he's
48
+ busy writing.
49
+
50
+ The kind of syntax check to perform on a document is decided according to the mimetype
51
+ of the document (this means that only documents associated with a file can be
52
+ syntax-checked). Currently, this plugin contains syntax checkers for ruby files
53
+ and YAML files, but syntax checkers for other kind of files can be added by other
54
+ plugins (see the documentation for {SyntaxCheckerPlugin#register_syntax_checker register_syntax_checker}
55
+ for details)
56
+
57
+ @api feature syntax_checker
58
+ @plugin
59
+ @config_option syntax_checker automatic_check [Boolean] Whether to perform automatic
60
+ syntax checks after one second of inactivity
61
+ =end
62
+ module SyntaxChecker
63
+
64
+ =begin rdoc
65
+ Plugin object for the @sytax_checker@ feature
66
+
67
+ @api class SyntaxChecker::SyntaxCheckerPlugin
68
+ @api_method #register_syntax_checker
69
+ @api_method #remove_syntax_checker
70
+ =end
71
+ class SyntaxCheckerPlugin < Plugin
72
+
73
+ =begin rdoc
74
+ Class containing the information about a syntax error. It has four attributes:
75
+ line:= the line at which the syntax error occurred (note that this starts from
76
+ 1, while Document and EditorView count lines from 0)
77
+ message:= a string describing the syntax error
78
+ code:= a string with the code around the point where the syntax error occurred
79
+ column:= the column at which the syntax error occurred
80
+
81
+ This class'constructor can take up to four parameters, corresponding (in
82
+ the same order) to the four attributes described above.
83
+ =end
84
+ ErrorDescription = Struct.new(:line, :message, :code, :column)
85
+
86
+ =begin rdoc
87
+ Colors used to display the different states of the document
88
+ =end
89
+ COLORS = {
90
+ :correct => Qt::Color.new(Qt.green),
91
+ :error => Qt::Color.new(Qt.red),
92
+ :unknown => Qt::Color.new(Qt.gray)
93
+ }
94
+
95
+ =begin rdoc
96
+ Signal emitted when it's time to perform an automatic syntax check
97
+ =end
98
+ signals :timeout
99
+
100
+ =begin rdoc
101
+ Creates an instance of the plugin
102
+
103
+ It also registers the two built-in syntax checkers
104
+
105
+ @param [Ruber::PluginSpecification] the specification associated to the
106
+ plugin
107
+ =end
108
+ def initialize psf
109
+ super
110
+ @availlable_syntax_checkers = {}
111
+ register_syntax_checker RubySyntaxChecker, 'application/x-ruby',
112
+ %w[*.rb rakefile Rakefile]
113
+ register_syntax_checker YamlSyntaxChecker, [], %w[*.yml *.yaml]
114
+ @led = SyntaxResultWidget.new
115
+ Ruber[:main_window].status_bar.add_permanent_widget @led
116
+ mark_document_as :unknown
117
+ end
118
+
119
+ =begin rdoc
120
+ Instantiates an appropriate syntax checker for a document
121
+
122
+ @param [Document] doc the document to create the syntax checker for
123
+ @return [Object,nil] a syntax checker suitable for _doc_ or *nil* if _doc_ is not
124
+ associated with a file or no syntax checker has been registered for <i>doc</i>'s extension
125
+ or mimetype
126
+ =end
127
+ def syntax_checker_for doc
128
+ checker_cls = @availlable_syntax_checkers.find! do |cls, data|
129
+ doc.file_type_match?(*data) ? cls : nil
130
+ end
131
+ checker_cls ? checker_cls.new(doc) : nil
132
+ end
133
+
134
+ =begin rdoc
135
+ Tells the plugin to display the result of the syntax check for the current document
136
+
137
+ @param [Symbol] status the result of the syntax check. It can be one of @:correct@,
138
+ @:error@ or @:unknown@ (if no syntax checker was availlable for the document)
139
+ @param [Array<SyntaxCheckerPlugin::ErrorDescription>] errors an array containing
140
+ instances of class {SyntaxCheckerPlugin::ErrorDescription} describing each of the
141
+ syntax error which where found in the document. This argument is only used if
142
+ _status_ is @:error@
143
+ @return [SyntaxCheckerPlugin] *self*
144
+ =end
145
+ def mark_document_as status, errors = []
146
+ msg = case status
147
+ when :correct then 'Syntax OK'
148
+ when :unknown then 'Document type unknown'
149
+ when :error then errors.map{|e| format_error_message e}.join "\n"
150
+ end
151
+ @led.tool_tip = msg
152
+ @led.color = COLORS[status]
153
+ self
154
+ end
155
+
156
+ =begin rdoc
157
+ Registers a new syntax checker.
158
+
159
+ A syntax checker is an object which analyzes the contents of a document and tells
160
+ whether its syntax is correct or not. To register it with the plugin, you need
161
+ to pass the object's class, toghether with the mimetypes and file patterns it
162
+ should be used to check syntax for, to this method. When a new document with the
163
+ appropriate mimetype or file name is created, a new instances of the syntax checker
164
+ class will be created.
165
+
166
+ The syntax checker class must have the following characteristics:
167
+ * its constructor should take the document as only parameter
168
+ * it should have a @check@ method which takes a string (corresponding to the text
169
+ of the document) and performs the syntax check on it. It must return a string
170
+ with all the information needed to retrieve the information about the single
171
+ errors.
172
+ * it should have a @convert_check_result@ method, which takes the string
173
+ returned by the @check@ method and converts it to an array of {ErrorDescription}
174
+ objects, with each object containing the information about a single error. If
175
+ the document doesn't contain any syntax error, this method should return an
176
+ empty array.
177
+
178
+
179
+ @param [Class] cls the class to instantiate to create the syntax checker
180
+ @param [String, <String>] mimetypes the mimetypes to use the new syntax checker
181
+ for. It has the format described in {Document#file_type_match?}
182
+ @param [String, <String>] patterns the file patterns to use the new syntax checker
183
+ for. It has the format described in {Document#file_type_match?}
184
+ @return [SyntaxCheckerPlugin] *self*
185
+ @raise ArgumentError if _cls_ had already been registered as a syntax checker.
186
+ =end
187
+ def register_syntax_checker cls, mimetypes, patterns = []
188
+ if @availlable_syntax_checkers.include? cls
189
+ raise ArgumentError, "class #{cls} has already been registered as syntax checker"
190
+ else
191
+ @availlable_syntax_checkers[cls] = [mimetypes, patterns] end
192
+ end
193
+
194
+ =begin rdoc
195
+ Removes a registered syntax checker.
196
+
197
+ Nothing is done if the syntax checker hadn't been registered.
198
+
199
+ @param [Class] cls the class of the syntax checker to remove
200
+ @return [Boolean] *true* if the syntax checker was removed and *false* if it wasn't
201
+ registered
202
+ =end
203
+ def remove_syntax_checker cls
204
+ res = @availlable_syntax_checkers.delete cls
205
+ res.to_bool
206
+ end
207
+
208
+ =begin rdoc
209
+ Prepares the plugin for application shutdown
210
+ @return [SyntaxCheckerPlugin] *self*
211
+ =end
212
+ def shutdown
213
+ @timer.stop
214
+ self
215
+ end
216
+
217
+ =begin rdoc
218
+ Prepares the plugin to be unloaded
219
+
220
+ @return [SyntaxCheckerPlugin] *self*
221
+ =end
222
+ def unload
223
+ Ruber[:main_window].status_bar.remove_widget @led
224
+ super
225
+ self
226
+ end
227
+
228
+ =begin rdoc
229
+ Generates an error message from an error object
230
+
231
+ The returned string contains the error message together with information about
232
+ the line and the column (if known) where the error happened
233
+
234
+ @param [ErrorDescription] error the object containing the error message
235
+ @return [String] a string containing the error message, including line and row
236
+ where the error happened, formatted in a standard way
237
+ =end
238
+ def format_error_message error
239
+ res = "Line #{error.line}"
240
+ res += " Col #{error.column}" if error.column
241
+ res += ": #{error.message}"
242
+ res
243
+ end
244
+
245
+ =begin
246
+ Starts the timer for the automatic check
247
+
248
+ @return [SyntaxCheckerPlugin] *self*
249
+ =end
250
+ def start_timer
251
+ if @timer
252
+ @timer.stop
253
+ @timer.start
254
+ end
255
+ self
256
+ end
257
+
258
+ =begin
259
+ Stops the timer for the automatic check
260
+
261
+ @return [SyntaxCheckerPlugin] *self*
262
+ =end
263
+ def stop_timer
264
+ @timer.stop if @timer
265
+ self
266
+ end
267
+
268
+ =begin rdoc
269
+ Loads the settings
270
+
271
+ If automatic checking has been enabled, a timer is created; if it has been disabled,
272
+ the timer is destroyed
273
+
274
+ @return [nil]
275
+ =end
276
+ def load_settings
277
+ auto_check = Ruber[:config][:syntax_checker, :automatic_check]
278
+ if auto_check and !@timer
279
+ @timer = Qt::Timer.new self
280
+ @timer.interval = 1000
281
+ connect @timer, SIGNAL(:timeout), self, SIGNAL(:timeout)
282
+ @timer.start if Ruber[:main_window].current_document
283
+ elsif !auto_check
284
+ @timer.disconnect self
285
+ @timer.dispose
286
+ @timer = nil
287
+ end
288
+ nil
289
+ end
290
+
291
+ end
292
+
293
+ =begin rdoc
294
+ Document extension which checks the syntax for a document. The syntax check happens:
295
+ * when the document becomes active
296
+ * when the document is saved
297
+ * one second after the last modification (if the user has enabled automatic checks)
298
+
299
+ The check can only be done if a syntax checker for the document's mimetype
300
+ or file extension exists. New syntax checkers must be added to the syntax checker
301
+ plugin using the {SyntaxCheckerPlugin#register_syntax_checker} method,
302
+ and can be removed using {SyntaxCheckerPlugin#remove_syntax_checker}.
303
+
304
+ The appropriate syntax checker for the document is chosen when the extension is
305
+ added to the document, and is changed (if needed) whenever the @document_name@
306
+ of the document changes.
307
+
308
+ *Note:* in the documentation of this class, the term _document_ will refer
309
+ to the Document passed as argument to the constructor.
310
+ =end
311
+ class SyntaxCheckerExtension < Qt::Object
312
+
313
+ include Extension
314
+
315
+ =begin rdoc
316
+ Signal emitted after a syntax check has been completed
317
+
318
+ @param [String] result a string containing the results of the syntax check
319
+ =end
320
+ signals 'check_done(QString)'
321
+
322
+ slots 'update_ui(QString)', :check, :document_activated,
323
+ :document_deactivated, :document_name_changed, :create_syntax_checker
324
+
325
+ =begin rdoc
326
+ A list of the syntax errors found in the document
327
+
328
+ The list is empty if the document doesn't contain syntax errors or if it hasn't
329
+ been checked yet
330
+
331
+ @return [<SyntaxCheckerPlugin::ErrorDescription>] the syntax errors found in the
332
+ document
333
+ =end
334
+ attr_reader :errors
335
+
336
+ =begin rdoc
337
+ Creates a new instance
338
+
339
+ @param [DocumentProject] prj the project associated with the document
340
+ =end
341
+ def initialize prj
342
+ super
343
+ @plugin = Ruber[:syntax_checker]
344
+ @doc = prj.document
345
+ @errors = []
346
+ @checker = nil
347
+ connect @doc, SIGNAL(:activated), self, SLOT(:document_activated)
348
+ connect @doc, SIGNAL(:deactivated), self, SLOT(:document_deactivated)
349
+ @doc.connect(SIGNAL('modified_changed(bool, QObject*)')) do |mod, _|
350
+ check if !mod
351
+ end
352
+ connect @doc, SIGNAL('document_name_changed(QString, QObject*)'), self, SLOT(:create_syntax_checker)
353
+ @doc.connect SIGNAL('text_changed(QObject*)') do
354
+ if @doc.active?
355
+ @plugin.stop_timer
356
+ @plugin.start_timer
357
+ end
358
+ end
359
+ @thread = nil
360
+ create_syntax_checker
361
+ end
362
+
363
+ =begin rdoc
364
+ Sets the syntax status according to the results of the last syntax check
365
+
366
+ It marks the current document as having correct or incorrect syntax depending on
367
+ the contents of _str_.
368
+
369
+ @param [String] str the string containing the results of the syntax check (such as
370
+ the one that a syntax checker's @check@ method). It is passed to the syntax checker's
371
+ @convert_check_result@ method
372
+ @return [nil]
373
+ =end
374
+ def update_ui str
375
+ @errors = @checker.convert_check_result str
376
+ if @errors.empty? then @plugin.mark_document_as :correct
377
+ else @plugin.mark_document_as :error, @errors
378
+ end
379
+ nil
380
+ end
381
+
382
+ =begin rdoc
383
+ Starts a syntax check for the document
384
+
385
+ The syntax check can be synchronous or asynchronous, according to the value of
386
+ the argument. In the first case, this method won't return until the syntax check
387
+ has finished and the UI has been updated. In the second case, the method will
388
+ start the syntax check in a new thread and return immediately. The {#check_done}
389
+ signal will be emitted when the syntax check has been finished.
390
+
391
+ Nothing will be done if no checker exists for the document.
392
+
393
+ *Note:* while an asynchronous syntax check avoids freezing the UI if it takes
394
+ a long time, it seems that it takes much longer than a synchronous check.
395
+
396
+ @param [Boolean] async whether the syntax check should or not be asynchronous
397
+ @todo the decision on whether the check should be synchronous or asynchronous
398
+ should be delegated to the checker
399
+ @return [nil]
400
+ =end
401
+ def check async = false
402
+ return unless @checker
403
+ @plugin.stop_timer
404
+ if async
405
+ @threak.kill if @thread
406
+ @thread = Thread.new(@doc.text) do |str|
407
+ res = @checker.check @doc.text
408
+ emit check_done res
409
+ end
410
+ else
411
+ res = @checker.check @doc.text
412
+ update_ui res
413
+ end
414
+ nil
415
+ end
416
+
417
+ private
418
+
419
+ =begin rdoc
420
+ Creates a syntax checker for the document
421
+
422
+ If needed, it also removes the old one and immediately performs a syntax check
423
+
424
+ @return [nil]
425
+ =end
426
+ def create_syntax_checker
427
+ new_checker = Ruber[:syntax_checker].syntax_checker_for @doc
428
+ if @checker.class != new_checker.class
429
+ @checker.disconnect if @checker
430
+ @checker = nil
431
+ if new_checker
432
+ @checker = new_checker
433
+ connect self, SIGNAL('check_done(QString)'), self, SLOT('update_ui(QString)')
434
+ end
435
+ check if @doc.active?
436
+ end
437
+ nil
438
+ end
439
+
440
+ =begin rdoc
441
+ Informs the extension that the document is not active anymore
442
+
443
+ @return [nil]
444
+ =end
445
+ def document_deactivated
446
+ @thread.kill if @thread
447
+ @plugin.stop_timer
448
+ @plugin.disconnect SIGNAL(:timeout), self, SLOT(:check)
449
+ Ruber[:syntax_checker].mark_document_as :unknown
450
+ nil
451
+ end
452
+
453
+ =begin rdoc
454
+ Informs the extension that the document became active
455
+
456
+ @return [nil]
457
+ =end
458
+ def document_activated
459
+ connect @plugin, SIGNAL(:timeout), self, SLOT(:check)
460
+ check
461
+ nil
462
+ end
463
+
464
+ end
465
+
466
+ =begin rdoc
467
+ Class which checks the syntax of a ruby file.
468
+
469
+ To do so, it runs a separate ruby process (using the ruby interpreter set by the
470
+ Ruby Runner plugin) passing it the @-c@ and the @-e@ options with the document's
471
+ content as argument.
472
+
473
+ The process is executed using @Open3.popen3@
474
+ =end
475
+ class RubySyntaxChecker
476
+
477
+ =begin rdoc
478
+ Creates a new instance.
479
+
480
+ @param [Document] doc the document to check
481
+ =end
482
+ def initialize doc
483
+ @doc = doc
484
+ end
485
+
486
+ =begin rdoc
487
+ Checks the syntax of the given string.
488
+
489
+ @param [String] str the string to check (usually, it'll be the document's text)
490
+ @return [String] a string containing the lines of output produced by ruby concerning
491
+ syntax errors or an empty string if there were no syntax error
492
+ =end
493
+ def check str
494
+ ruby = Ruber[:ruby_development].interpreter_for @doc
495
+ Open3.popen3(ruby, '-c', '-e', str) do |in_s, out_s, err_s|
496
+ error = err_s.read
497
+ error.gsub! %r{^-e(?=:\d+:\s+syntax error,)}, @doc.path
498
+ out_s.read.strip != 'Syntax OK' ? error : ''
499
+ end
500
+ end
501
+
502
+ =begin rdoc
503
+ Parses the output of {#check}
504
+
505
+ @param [String] str the string to parse.
506
+ @return [<SyntaxCheckerPlugin::ErrorDescription>] a list of {SyntaxCheckerPlugin::ErrorDescription ErrorDescription}s
507
+ corresponding to the syntax errors mentioned in _str_
508
+ =end
509
+ def convert_check_result str
510
+ groups = [[]]
511
+ lines = str.split "\n"
512
+ lines.each do |l|
513
+ if l.match(/^#{Regexp.quote(@doc.path||'')}:\d+:\s+syntax error,\s+/) then groups << [l]
514
+ else groups[-1] << l
515
+ end
516
+ end
517
+ groups.delete_if{|g| g.empty?}
518
+ groups.map do |a|
519
+ a.shift.match(/^#{Regexp.quote(@doc.path||'')}:(\d+):\s+syntax error,\s+(.*)/)
520
+ msgs = [$2]
521
+ error = SyntaxCheckerPlugin::ErrorDescription.new $1.to_i
522
+ if a[-1] and a[-1].match(/^\s*\^\s*$/)
523
+ error.code = a[-2]
524
+ # Sometimes, ruby doesn't report the whole line where the error occurs, but only
525
+ # the part nearest it. In this case, the beginning of the line is replaced with ... .
526
+ # In this case, to obtain the correct column number, we try a regexp match
527
+ # between the part of code reported by ruby and the whole line. If it works, we
528
+ # add that position to the one returned by ruby. If it doesn't (for example because
529
+ # the user changed the document in the meantime), we'll just report what ruby reports
530
+ col = a[-1].index('^')
531
+ if a[-2].match(/^\.\.\./)
532
+ lines = @doc.text.split("\n")
533
+ # error.line is 1-based
534
+ l = lines[error.line-1] || ''
535
+ pos = (l =~ /#{Regexp.quote(a[-2][3..-1])}/)
536
+ error.column = pos ? col + pos - 1 : col
537
+ else error.column = col
538
+ end
539
+ a.pop 2
540
+ end
541
+ a.each{|l| msgs << l}
542
+ error.message = msgs.join ' '
543
+ error
544
+ end
545
+ end
546
+
547
+ end
548
+
549
+ =begin rdoc
550
+ Class which checks the syntax of a ruby file.
551
+
552
+ It calls the @YAML.load@ method on the document's content within a begin/rescue
553
+ block, rescuing any ArgumentError exception. The message of the exception is used
554
+ to find information about the error
555
+ =end
556
+ class YamlSyntaxChecker
557
+
558
+ =begin rdoc
559
+ Creates a new instance
560
+
561
+ @param [Document] doc the document to check
562
+ =end
563
+ def initialize doc
564
+ end
565
+
566
+ =begin rdoc
567
+ Checks the syntax of the given string.
568
+
569
+ @param [String] str the string to check (usually, it'll be the document's text)
570
+ @return [String] a string containing the lines of output produced by @YAML.load@
571
+ concerning syntax errors or an empty string if there were no syntax error
572
+ =end
573
+ def check str
574
+ begin
575
+ YAML.load str
576
+ ''
577
+ rescue ArgumentError => e
578
+ e.message
579
+ end
580
+ end
581
+
582
+ =begin rdoc
583
+ Parses the output of {#check}
584
+
585
+ @param [String] str the string to parse.
586
+ @return [<SyntaxCheckerPlugin::ErrorDescription>] a list of {SyntaxCheckerPlugin::ErrorDescription ErrorDescription}s
587
+ corresponding to the syntax errors mentioned in _str_
588
+ =end
589
+ def convert_check_result str
590
+ return [] if str.empty?
591
+ str.match(/^syntax error on line (\d+), col (\d+): `(.*)'$/)
592
+ error = SyntaxCheckerPlugin::ErrorDescription.new $1.to_i, 'Syntax error', $3.to_s, $2.to_i
593
+ [error]
594
+ end
595
+
596
+ end
597
+
598
+ =begin rdoc
599
+ @KDE::Led@ with the ability to jump to the first syntax error on left or middle mouse
600
+ click and to popup a menu with a list of all syntax errors in the current document
601
+ on right click
602
+ =end
603
+ class SyntaxResultWidget < KDE::Led
604
+
605
+ =begin rdoc
606
+ Override of @Qt::Widget#mouseReleaseEvent@
607
+
608
+ If the event refers to the left button and the current document contains syntax
609
+ errors, it moves the cursor in the editor view to the position of the first error
610
+
611
+ @return [nil]
612
+ =end
613
+ def mouseReleaseEvent e
614
+ return unless e.button == Qt::LeftButton or e.button == Qt::MidButton
615
+ doc = Ruber[:main_window].current_document
616
+ view = Ruber[:main_window].active_editor
617
+ return unless view and doc and doc.extension(:syntax_checker)
618
+ checker = doc.extension(:syntax_checker)
619
+ err = checker.errors.first
620
+ if err
621
+ view.go_to err.line - 1, (err.column || 0)
622
+ Ruber[:main_window].status_bar.show_message Ruber[:syntax_checker].format_error_message(err), 4000
623
+ end
624
+ nil
625
+ end
626
+
627
+ =begin rdoc
628
+ Override of @Qt::Widget#contextMenuEvent@
629
+
630
+ If the current document contains syntax errors, it displays a menu listing them.
631
+ Each action in the menu moves the cursor in the editor view to display to the line
632
+ and column of the corresponding error.
633
+
634
+ If the current document doesn't contain syntax errors, the menu will only contain
635
+ an entry with text @Syntax OK@ which does nothing when activated.
636
+
637
+ @return [nil]
638
+ =end
639
+ def contextMenuEvent event
640
+ doc = Ruber[:main_window].current_document
641
+ view = Ruber[:main_window].active_editor
642
+ return unless doc and view and (checker = doc.extension :syntax_checker)
643
+ errors = checker.errors
644
+ actions = errors.map do |e|
645
+ a = KDE::Action.new Ruber[:syntax_checker].format_error_message(e), self
646
+ end
647
+ actions << KDE::Action.new('Syntax OK', self) if actions.empty?
648
+ res = Qt::Menu.exec(actions, event.global_pos)
649
+ return if !res or errors.empty?
650
+ idx = actions.index res
651
+ return unless idx
652
+ error = errors[idx]
653
+ view.go_to error.line - 1, (error.column || 0)
654
+ Ruber[:main_window].status_bar.show_message res.text, 4000
655
+ nil
656
+ end
657
+
658
+ end
659
+
660
+ end
661
+
662
+ end