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