ruber 0.0.9 → 0.0.10

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 (73) hide show
  1. data/CHANGES +42 -1
  2. data/lib/ruber/application/application.rb +25 -5
  3. data/lib/ruber/application/plugin.yaml +2 -10
  4. data/lib/ruber/component_manager.rb +2 -2
  5. data/lib/ruber/document_project.rb +5 -3
  6. data/lib/ruber/editor/document.rb +5 -4
  7. data/lib/ruber/editor/ktexteditor_wrapper.rb +1 -1
  8. data/lib/ruber/exception_widgets.rb +1 -1
  9. data/lib/ruber/main_window/main_window.rb +4 -3
  10. data/lib/ruber/main_window/main_window_actions.rb +2 -2
  11. data/lib/ruber/main_window/main_window_internal.rb +1 -1
  12. data/lib/ruber/output_widget.rb +17 -5
  13. data/lib/ruber/project.rb +34 -3
  14. data/lib/ruber/project_dir_scanner.rb +171 -0
  15. data/lib/ruber/settings_container.rb +7 -7
  16. data/lib/ruber/settings_dialog.rb +24 -24
  17. data/lib/ruber/version.rb +1 -1
  18. data/lib/ruber/world/environment.rb +1 -0
  19. data/lib/ruber/world/plugin.yaml +7 -2
  20. data/lib/ruber/{application → world}/project_files_widget.rb +0 -0
  21. data/lib/ruber/{application → world}/ui/project_files_rule_chooser_widget.rb +2 -2
  22. data/lib/ruber/{application → world}/ui/project_files_rule_chooser_widget.ui +0 -0
  23. data/lib/ruber/{application → world}/ui/project_files_widget.rb +2 -2
  24. data/lib/ruber/{application → world}/ui/project_files_widget.ui +0 -0
  25. data/plugins/auto_end/auto_end.rb +21 -18
  26. data/plugins/autosave/autosave.rb +1 -1
  27. data/plugins/find_in_files/find_in_files.rb +1 -1
  28. data/plugins/irb/irb.png +0 -0
  29. data/plugins/irb/irb.rb +142 -0
  30. data/plugins/irb/irb.svg +240 -0
  31. data/plugins/irb/irb_controller.rb +541 -0
  32. data/plugins/irb/irbrc.rb +21 -0
  33. data/plugins/irb/plugin.yaml +19 -0
  34. data/plugins/irb/ui/irb_config_widget.rb +151 -0
  35. data/plugins/irb/ui/irb_config_widget.ui +123 -0
  36. data/plugins/irb/ui/irb_tool_widget.rb +97 -0
  37. data/plugins/irb/ui/irb_tool_widget.ui +86 -0
  38. data/plugins/project_browser/project_browser.rb +1 -1
  39. data/plugins/rspec/plugin.yaml +6 -3
  40. data/plugins/rspec/rspec.rb +172 -473
  41. data/plugins/rspec/tool_widget.rb +462 -0
  42. data/plugins/rspec/ui/rspec_project_widget.rb +58 -38
  43. data/plugins/rspec/ui/rspec_project_widget.ui +68 -64
  44. data/plugins/ruberri/class_formatter.rb +126 -0
  45. data/plugins/ruberri/method_formatter.rb +90 -0
  46. data/plugins/ruberri/plugin.yaml +13 -0
  47. data/plugins/ruberri/ruberri.rb +226 -0
  48. data/plugins/ruberri/search.rb +111 -0
  49. data/plugins/ruberri/ui/tool_widget.rb +73 -0
  50. data/plugins/ruberri/ui/tool_widget.ui +49 -0
  51. data/plugins/ruby_runner/ruby_runner.rb +2 -2
  52. data/plugins/ruby_syntax_checker/plugin.yaml +11 -0
  53. data/plugins/ruby_syntax_checker/ruby_syntax_checker.rb +147 -0
  54. data/plugins/syntax_checker/plugin.yaml +10 -6
  55. data/plugins/syntax_checker/syntax_checker.rb +216 -520
  56. data/plugins/syntax_checker/ui/config_widget.rb +61 -0
  57. data/plugins/syntax_checker/ui/config_widget.ui +44 -0
  58. data/plugins/yaml_syntax_checker/plugin.yaml +11 -0
  59. data/plugins/yaml_syntax_checker/yaml_syntax_checker.rb +62 -0
  60. data/ruber.desktop +0 -0
  61. data/spec/auto_end_spec.rb +224 -186
  62. data/spec/document_project_spec.rb +9 -1
  63. data/spec/document_spec.rb +9 -0
  64. data/spec/environment_spec.rb +12 -0
  65. data/spec/output_widget_spec.rb +69 -2
  66. data/spec/project_dir_scanner_spec.rb +195 -0
  67. data/spec/project_spec.rb +43 -73
  68. data/spec/ruby_syntax_checker_spec.rb +361 -0
  69. data/spec/syntax_checker_spec.rb +1132 -0
  70. data/spec/yaml_syntax_checker_spec.rb +130 -0
  71. metadata +232 -225
  72. data/lib/ruber/application/project_files_list.rb +0 -320
  73. data/spec/project_files_list_spec.rb +0 -411
@@ -0,0 +1,49 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ui version="4.0">
3
+ <class>RIToolWidget</class>
4
+ <widget class="QWidget" name="RIToolWidget">
5
+ <property name="geometry">
6
+ <rect>
7
+ <x>0</x>
8
+ <y>0</y>
9
+ <width>676</width>
10
+ <height>300</height>
11
+ </rect>
12
+ </property>
13
+ <property name="windowTitle">
14
+ <string>Form</string>
15
+ </property>
16
+ <layout class="QGridLayout" name="gridLayout">
17
+ <item row="0" column="0">
18
+ <widget class="QLabel" name="label">
19
+ <property name="text">
20
+ <string>Find</string>
21
+ </property>
22
+ <property name="buddy">
23
+ <cstring>search_term</cstring>
24
+ </property>
25
+ </widget>
26
+ </item>
27
+ <item row="0" column="1">
28
+ <widget class="KLineEdit" name="search_term"/>
29
+ </item>
30
+ <item row="0" column="2">
31
+ <widget class="QPushButton" name="search">
32
+ <property name="text">
33
+ <string>&amp;Search</string>
34
+ </property>
35
+ </widget>
36
+ </item>
37
+ <item row="1" column="0" colspan="3">
38
+ <widget class="QTextBrowser" name="content"/>
39
+ </item>
40
+ </layout>
41
+ </widget>
42
+ <tabstops>
43
+ <tabstop>search_term</tabstop>
44
+ <tabstop>search</tabstop>
45
+ <tabstop>content</tabstop>
46
+ </tabstops>
47
+ <resources/>
48
+ <connections/>
49
+ </ui>
@@ -346,7 +346,7 @@ object
346
346
  current project, even if _target_ is a {Document} or the name of a file belonging
347
347
  to it
348
348
  @param [Boolean] abs it has the same meaning as in {SettingsContainer#[]}
349
- @raise ArgumentError if the option can be found
349
+ @raise [IndexError] if the option can't be found
350
350
  =end
351
351
  def option_for target, group, name, ignore_project = false, abs = false
352
352
  cont = []
@@ -363,7 +363,7 @@ to it
363
363
  abs = :abs if abs
364
364
  cont.each_with_index do |c, i|
365
365
  begin return c[group, name, abs]
366
- rescue ArgumentError
366
+ rescue IndexError
367
367
  raise if i == cont.size - 1
368
368
  end
369
369
  end
@@ -0,0 +1,11 @@
1
+ name: ruby_syntax_checker
2
+ version: 0.0.1
3
+ about:
4
+ authors: [Stefano Crocco, stefano.crocco@alice.it]
5
+ license: gpl
6
+ description: Syntax checker for ruby documents. Requires the syntax checker plugin
7
+ bug_address: http://github.com/stcrocco/ruber/issues
8
+ icon: tools-check-spelling
9
+ deps: [syntax_checker, ruby_development]
10
+ class: Ruber::RubySyntaxChecker::Plugin
11
+ require: ruby_syntax_checker
@@ -0,0 +1,147 @@
1
+ =begin
2
+ Copyright (C) 2010, 2011 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
+ module Ruber
22
+
23
+ module RubySyntaxChecker
24
+
25
+ SyntaxError = Struct.new :line, :column, :message, :formatted_message, :error_type
26
+
27
+ class Plugin < Ruber::Plugin
28
+
29
+ def initialize psf
30
+ super
31
+ Ruber[:syntax_checker].register_syntax_checker RubySyntaxChecker::Checker, ['application/x-ruby'],
32
+ %w[*.rb rakefile Rakefile]
33
+ end
34
+
35
+ def unload
36
+ Ruber[:syntax_checker].remove_syntax_checker RubySyntaxChecker::Checker
37
+ end
38
+
39
+ end
40
+
41
+ class Checker
42
+
43
+ SPECIAL_ERROR_STRINGS = [
44
+ 'unmatched close parenthesis:',
45
+ 'end pattern with unmatched parenthesis:',
46
+ 'unknown regexp option -',
47
+ 'class|module definition in method body',
48
+ 'dynamic constant assignment',
49
+ 'unterminated string meets end of file',
50
+ 'premature end of char-class:',
51
+ 'embedded document meets end of file',
52
+ 'can\'t find string "[^"]+" anywhere before EOF'
53
+ ]
54
+
55
+ ERROR_TYPES=[
56
+ [/\bunexpected \$end,\s+expecting (?:keyword_end|kEND)/, :missing_end],
57
+ [/\bunexpected (?:keyword_end|kEND),\s+expecting \$end/, :extra_end],
58
+ [/\bexpecting '\)/, :missing_close_paren],
59
+ [/\bunexpected '\)/, :extra_close_paren],
60
+ [/\bexpecting '\]'/, :missing_close_bracket],
61
+ [/\bunexpected '\]/, :extra_close_bracket],
62
+ [/\bexpecting '\}'/, :missing_close_brace],
63
+ [/\bunexpected '\}/, :extra_close_brace],
64
+ [/\bunexpected (?:keyword_else|kELSE)/, :extra_else],
65
+ [/\bunexpected (?:keyword_when|kWHEN)/, :extra_when],
66
+ [/\bunexpected (?:keyword_rescue|kRESCUE)/, :extra_rescue],
67
+ [/\bunexpected (?:keyword_ensure|kENSURE)/, :extra_ensure],
68
+ [/\bunterminated string/, :missing_quote],
69
+ [/\bunexpected (?:keyword_end|kEND)/, :misplaced_end],
70
+ [/end pattern with unmatched parenthesis/, :missing_regexp_close_paren],
71
+ [/unmatched close parenthesis/, :extra_regexp_close_paren],
72
+ [/premature end of char-class/, :missing_regexp_close_bracket],
73
+ [/unknown regexp option/, :unknown_regexp_option],
74
+ [/dynamic constant assignment/, :dynamic_constant_assignment],
75
+ [/embedded document meets end of file/, :missing_block_comment_end],
76
+ [/can't find string "[^"]+" anywhere before EOF/, :missing_heredoc_end]
77
+ ]
78
+
79
+ def initialize doc
80
+ @doc = doc
81
+ @regexp = %r{^-e:(\d+):\s+(?:syntax error,|(#{SPECIAL_ERROR_STRINGS.join '|'}))(?:\s+(.*)|$)}
82
+ end
83
+
84
+ def check_syntax text, formatted
85
+ ruby = Ruber[:ruby_development].interpreter_for @doc
86
+ begin
87
+ msg = Open3.popen3(ruby, '-c', '-e', text) do |in_s, out_s, err_s|
88
+ error = err_s.read
89
+ out_s.read.strip != 'Syntax OK' ? error : ''
90
+ end
91
+ rescue SystemCallError
92
+ raise Ruber::SyntaxChecker::SyntaxNotChecked
93
+ end
94
+ parse_output msg, formatted
95
+ end
96
+
97
+ private
98
+
99
+ def parse_output str, formatted
100
+ # The inner array is needed in case the first message doesn\'t use a
101
+ # recognized format (for example, regexp syntax errors don\'t have a standard
102
+ # format). Without this, in the lins cycle, the else clause would be
103
+ # executed and would fail because the error_lines array is empty.
104
+ error_lines = [ [ [] ] ]
105
+ lines = str.split_lines
106
+ return if lines.empty?
107
+ lines.each do |l|
108
+ if l.match @regexp
109
+ error = [[$2 ? "#{$2} #{$3}" : $3], $1.to_i - 1]
110
+ error_type = ERROR_TYPES.find{|a| a[0] =~ l}
111
+ error << error_type[1] if error_type
112
+ error_lines << error
113
+ else error_lines[-1][0] << l
114
+ end
115
+ end
116
+ error_lines.shift if error_lines.first.first.empty?
117
+ errors = error_lines.map do |a, number, type|
118
+ error = SyntaxError.new number, nil, a.shift, nil, type
119
+ a.each_with_index do |l, i|
120
+ if l.match %r{^\s*\^\s*$}
121
+ error.column = l.index '^'
122
+ previous_line = a[i-1]
123
+ if previous_line and previous_line.match /^\.{3}/
124
+ offset = ( @doc.line(error.line) =~ /#{Regexp.quote(previous_line[3..-1])}/)
125
+ error.column += offset if offset
126
+ end
127
+ else error.message << "\n" << l
128
+ end
129
+ end
130
+ if formatted
131
+ msg = error.message.dup
132
+ msg.gsub! /expect(ed|ing)\s+\$end/, 'expect\1 end of file'
133
+ msg.gsub! /expect(ed|ing)\s+kEND/, 'expect\1 `end` keyword'
134
+ msg.gsub! /expect(ed|ing)\s+keyword_end/, 'expect\1 `end` keyword'
135
+ error.formatted_message = msg
136
+ else error.formatted_message = error.message.dup
137
+ end
138
+ error
139
+ end
140
+ errors
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+
147
+ end
@@ -3,16 +3,20 @@ version: 0.0.1
3
3
  about:
4
4
  authors: [Stefano Crocco, stefano.crocco@alice.it]
5
5
  license: :gpl
6
- description: Checks the syntax of the current document
6
+ description: Infrastructure to check the syntax of documents (needs other plugins to perform the actual syntax check)
7
7
  icon: tools-check-spelling
8
8
  bug_address: http://github.com/stcrocco/ruber/issues
9
- class: Ruber::SyntaxChecker::SyntaxCheckerPlugin
9
+ class: Ruber::SyntaxChecker::Plugin
10
10
  require: syntax_checker
11
- deps: ruby_development
12
11
  config_options:
13
12
  syntax_checker:
14
- automatic_check: {default: true}
13
+ time_interval: {default: 1}
14
+ project_options:
15
+ syntax_checker:
16
+ auto_check: {scope: document, default: true}
15
17
  config_widgets:
16
- {caption: Syntax, code: 'Qt::CheckBox.new("&Automatically check syntax"){self.object_name = "_syntax_checker__automatic_check"}', pixmap: tools-check-spelling.png}
18
+ {caption: Syntax, class: Ruber::SyntaxChecker::ConfigWidget, pixmap: tools-check-spelling.png}
19
+ project_widgets:
20
+ {caption: Syntax, code: 'Qt::CheckBox.new("&Automatically check syntax for this document"){self.object_name = "_syntax_checker__auto_check"}', pixmap: tools-check-spelling.png, scope: document}
17
21
  extensions:
18
- syntax_checker: {class: Ruber::SyntaxChecker::SyntaxCheckerExtension, scope: document}
22
+ syntax_checker: {class: Ruber::SyntaxChecker::Extension, scope: document}
@@ -22,6 +22,7 @@ require 'tempfile'
22
22
  require 'open3'
23
23
 
24
24
  require 'facets/boolean'
25
+ require_relative 'ui/config_widget'
25
26
 
26
27
  module Ruber
27
28
 
@@ -61,604 +62,299 @@ syntax checks after one second of inactivity
61
62
  =end
62
63
  module SyntaxChecker
63
64
 
65
+ # SyntaxError = Struct.new :line, :column, :message, :formatted_message
66
+
67
+ class SyntaxNotChecked < Exception
68
+ end
69
+ #
70
+ # class SyntaxError
71
+ #
72
+ # def format
73
+ # res = ""
74
+ # res << KDE.i18n("Line %d") % (line + 1) if line
75
+ # res << KDE.i18n(", column %d") % (column + 1) if line and column
76
+ # msg = formatted_message || message
77
+ # res << ": " if msg and !res.empty?
78
+ # res << msg
79
+ # res
80
+ # end
81
+ #
82
+ # end
83
+
64
84
  =begin rdoc
65
- Plugin object for the @sytax_checker@ feature
85
+ Plugin object for the @syntax_checker@ feature
66
86
 
67
87
  @api class SyntaxChecker::SyntaxCheckerPlugin
68
88
  @api_method #register_syntax_checker
69
89
  @api_method #remove_syntax_checker
70
90
  =end
71
- class SyntaxCheckerPlugin < Plugin
91
+ class Plugin < Ruber::Plugin
72
92
 
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)
93
+ attr_reader :current_status
94
+
95
+ attr_reader :current_errors
85
96
 
86
- =begin rdoc
87
- Colors used to display the different states of the document
88
- =end
89
97
  COLORS = {
90
98
  :correct => Qt::Color.new(Qt.green),
91
- :error => Qt::Color.new(Qt.red),
92
- :unknown => Qt::Color.new(Qt.gray)
93
- }
99
+ :unknown => Qt::Color.new(Qt.gray),
100
+ :incorrect => Qt::Color.new(Qt.red)
101
+ }
94
102
 
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
103
+ MESSAGES = {
104
+ :correct => 'No syntax errors',
105
+ :unknown => 'Unknown document type',
106
+ :incorrect => 'There are syntax errors'
107
+ }
104
108
 
105
- @param [Ruber::PluginSpecification] the specification associated to the
106
- plugin
107
- =end
109
+ class Led < KDE::Led
110
+
111
+ signals 'context_menu_requested(QPoint)'
112
+
113
+ signals :left_clicked
114
+
115
+ def contextMenuEvent e
116
+ emit context_menu_requested(e.global_pos)
117
+ end
118
+
119
+ def mouseReleaseEvent e
120
+ emit left_clicked if e.button == Qt::LeftButton
121
+ end
122
+
123
+ end
124
+
125
+ signals :settings_changed
126
+
127
+ signals :syntax_checker_added
128
+
129
+ signals :syntax_checker_removed
130
+
108
131
  def initialize psf
109
132
  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
133
+ self.connect(SIGNAL('extension_added(QString, QObject*)')) do |name, prj|
134
+ connect prj.extension(name.to_sym), SIGNAL('syntax_checked(QObject*)'), self, SLOT('document_checked(QObject*)')
135
+ end
136
+ @syntax_checkers = {}
137
+ @led = Led.new
138
+ connect @led, SIGNAL('context_menu_requested(QPoint)'), self, SLOT('display_context_menu(QPoint)')
139
+ connect @led, SIGNAL(:left_clicked), self, SLOT(:jump_to_first_error)
115
140
  Ruber[:main_window].status_bar.add_permanent_widget @led
116
- mark_document_as :unknown
141
+ set_current_status :unknown
117
142
  end
118
143
 
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
144
+ def unload
145
+ Ruber[:main_window].status_bar.remove_widget @led
146
+ super
147
+ self
132
148
  end
133
149
 
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
+ def format_error error
151
+ msg = ''
152
+ if error.line
153
+ msg << KDE.i18n("Line %d") % (error.line + 1)
154
+ msg << (error.column ? (KDE.i18n(", column %d: ")) % (error.column + 1) : ': ')
150
155
  end
151
- @led.tool_tip = msg
156
+ msg << KDE.i18n(error.formatted_message || error.message || 'UNKNOWN ERROR')
157
+ msg
158
+ end
159
+
160
+ def set_current_status status, errors = []
152
161
  @led.color = COLORS[status]
153
- self
162
+ if status == :incorrect and !errors.empty?
163
+ tool_tip = errors.map do |e|
164
+ format_error e
165
+ end.join "\n"
166
+ @led.tool_tip = tool_tip
167
+ else @led.tool_tip = i18n(MESSAGES[status])
168
+ end
169
+ @current_status = status
170
+ if @current_status == :incorrect
171
+ @current_errors = errors.dup
172
+ else @current_errors = nil
173
+ end
154
174
  end
155
175
 
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
176
  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
177
+ if @syntax_checkers.include? cls
178
+ raise ArgumentError, "#{cls} has already been registered as syntax checker"
179
+ end
180
+ @syntax_checkers[cls] = [mimetypes, patterns]
181
+ emit syntax_checker_added
192
182
  end
193
183
 
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
184
  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
185
+ emit syntax_checker_removed if @syntax_checkers.delete cls
215
186
  end
216
187
 
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
188
+ def syntax_checker_for doc
189
+ cls = @syntax_checkers.find do |_, rules|
190
+ doc.file_type_match?(rules[0], rules[1])
191
+ end
192
+ return unless cls
193
+ cls[0]
226
194
  end
227
195
 
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
196
+ def load_settings
197
+ emit settings_changed
243
198
  end
244
199
 
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
200
+ private
201
+
202
+ def display_context_menu pt
203
+ return if @current_status == :unknown
204
+ if !@current_errors || @current_errors.empty?
205
+ actions = [KDE::Action.new(i18n(MESSAGES[@current_status]), @led)]
206
+ else actions = @current_errors.map{|e| KDE::Action.new format_error(e), @led}
254
207
  end
255
- self
208
+ choice = Qt::Menu.exec actions, pt
209
+ return if !choice or !@current_errors or @current_errors.empty?
210
+ error = @current_errors[actions.index(choice)]
211
+ line = error.line
212
+ col = error.column || 0
213
+ Ruber[:world].active_environment.active_editor.go_to line, col if line
256
214
  end
215
+ slots 'display_context_menu(QPoint)'
257
216
 
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
217
+ def jump_to_first_error
218
+ return unless @current_errors and !@current_errors.empty?
219
+ e = @current_errors[0]
220
+ col = e.column || 0
221
+ if e.line
222
+ Ruber[:world].active_environment.active_editor.go_to e.line, col
223
+ end
266
224
  end
225
+ slots :jump_to_first_error
267
226
 
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
227
+ def document_checked doc
228
+ return unless doc.active?
229
+ ext = doc.extension(:syntax_checker)
230
+ set_current_status ext.status, ext.errors
289
231
  end
232
+ slots 'document_checked(QObject*)'
290
233
 
291
234
  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
235
+
236
+ class Extension < Qt::Object
312
237
 
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)'
238
+ include Ruber::Extension
321
239
 
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
240
+ SyntaxErrorMark = KTextEditor::MarkInterface.markType09
241
+
242
+ DEFAULT_CHECK_OPTIONS = {:format => true, :update => true}
327
243
 
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
244
  attr_reader :errors
335
245
 
336
- =begin rdoc
337
- Creates a new instance
338
-
339
- @param [DocumentProject] prj the project associated with the document
340
- =end
246
+ attr_reader :status
247
+
248
+ signals 'syntax_checked(QObject*)'
249
+
341
250
  def initialize prj
342
251
  super
343
- @plugin = Ruber[:syntax_checker]
252
+ @status = :unknown
253
+ @errors = nil
344
254
  @doc = prj.document
345
- @errors = []
346
- @checker = nil
255
+ @project = prj
256
+ connect @doc, SIGNAL('document_url_changed(QObject*)'), self, SLOT(:create_syntax_checker)
257
+ connect @doc, SIGNAL('document_saved_or_uploaded(QObject*, bool)'), self,
258
+ SLOT(:auto_check)
259
+ connect @doc, SIGNAL('text_changed(QObject*)'), self, SLOT(:start_waiting)
260
+ create_syntax_checker
347
261
  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
262
+ connect @doc, SIGNAL(:deactivated), self, SLOT(:delete_timer)
263
+ connect Ruber[:syntax_checker], SIGNAL(:settings_changed), self, SLOT(:load_settings)
264
+ connect Ruber[:syntax_checker], SIGNAL(:syntax_checker_added), self, SLOT(:create_syntax_checker_if_needed)
265
+ connect Ruber[:syntax_checker], SIGNAL(:syntax_checker_removed), self, SLOT(:create_syntax_checker)
266
+ load_settings
267
+ document_activated false if @doc.active?
268
+ end
269
+
270
+ def check_syntax options = DEFAULT_CHECK_OPTIONS
271
+ options = DEFAULT_CHECK_OPTIONS.merge options
272
+ if @checker
273
+ begin
274
+ errors = @checker.check_syntax @doc.text, options[:format]
275
+ res = {:errors => errors, :result => errors ? :incorrect : :correct}
276
+ rescue SyntaxNotChecked
277
+ res = {:result => :unknown, :errors => nil}
278
+ end
279
+ else res = {:result => :unknown, :errors => nil}
351
280
  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
281
+ if options[:format] and options[:update]
282
+ @errors = errors
283
+ @status = res[:result]
284
+ if errors
285
+ #Uncomment the following lines when KTextEditor::MarkInterface#marks works
286
+ # iface = @doc.interface('mark_interface')
287
+ # errors.each do |e|
288
+ # iface.add_mark e.line, SyntaxErrorMark if e.line
289
+ # end
357
290
  end
291
+ emit syntax_checked(@doc) if options[:format]
358
292
  end
359
- @thread = nil
360
- create_syntax_checker
293
+ res
361
294
  end
295
+ slots :check_syntax
362
296
 
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
297
+ def remove_from_project
298
+ super
299
+ @timer.stop if @timer
380
300
  end
381
301
 
382
- def check_syntax async = false
383
-
384
- end
302
+ private
385
303
 
386
- =begin rdoc
387
- Starts a syntax check for the document
388
-
389
- The syntax check can be synchronous or asynchronous, according to the value of
390
- the argument. In the first case, this method won't return until the syntax check
391
- has finished and the UI has been updated. In the second case, the method will
392
- start the syntax check in a new thread and return immediately. The {#check_done}
393
- signal will be emitted when the syntax check has been finished.
394
-
395
- Nothing will be done if no checker exists for the document.
396
-
397
- *Note:* while an asynchronous syntax check avoids freezing the UI if it takes
398
- a long time, it seems that it takes much longer than a synchronous check.
399
-
400
- @param [Boolean] async whether the syntax check should or not be asynchronous
401
- @todo the decision on whether the check should be synchronous or asynchronous
402
- should be delegated to the checker
403
- @return [nil]
404
- =end
405
- def check async = false
406
- return unless @checker
407
- @plugin.stop_timer
408
- if async
409
- @threak.kill if @thread
410
- @thread = Thread.new(@doc.text) do |str|
411
- res = @checker.check @doc.text
412
- emit check_done res
413
- end
414
- else
415
- res = @checker.check @doc.text
416
- update_ui res
304
+ def create_syntax_checker_if_needed
305
+ unless @checker
306
+ create_syntax_checker
417
307
  end
418
- nil
419
308
  end
309
+ slots :create_syntax_checker_if_needed
420
310
 
421
- private
422
-
423
- =begin rdoc
424
- Creates a syntax checker for the document
425
-
426
- If needed, it also removes the old one and immediately performs a syntax check
427
-
428
- @return [nil]
429
- =end
430
311
  def create_syntax_checker
431
- new_checker = Ruber[:syntax_checker].syntax_checker_for @doc
432
- if @checker.class != new_checker.class
433
- @checker.disconnect if @checker
434
- @checker = nil
435
- if new_checker
436
- @checker = new_checker
437
- connect self, SIGNAL('check_done(QString)'), self, SLOT('update_ui(QString)')
438
- end
439
- check if @doc.active?
440
- end
441
- nil
312
+ checker_cls = Ruber[:syntax_checker].syntax_checker_for @doc
313
+ @checker = checker_cls ? checker_cls.new(@doc) : nil
314
+ auto_check
442
315
  end
316
+ slots :create_syntax_checker
443
317
 
444
- =begin rdoc
445
- Informs the extension that the document is not active anymore
446
-
447
- @return [nil]
448
- =end
449
- def document_deactivated
450
- @thread.kill if @thread
451
- @plugin.stop_timer
452
- @plugin.disconnect SIGNAL(:timeout), self, SLOT(:check)
453
- Ruber[:syntax_checker].mark_document_as :unknown
454
- nil
318
+ def auto_check
319
+ check_syntax if @project[:syntax_checker, :auto_check]
455
320
  end
321
+ slots :auto_check
456
322
 
457
- =begin rdoc
458
- Informs the extension that the document became active
459
-
460
- @return [nil]
461
- =end
462
- def document_activated
463
- connect @plugin, SIGNAL(:timeout), self, SLOT(:check)
464
- check
465
- nil
323
+ def document_activated check_syntax = true
324
+ @timer = Qt::Timer.new self
325
+ @timer.singleShot = true
326
+ connect @timer, SIGNAL(:timeout), self, SLOT(:auto_check)
327
+ auto_check if check_syntax
466
328
  end
329
+ slots :document_activated
467
330
 
468
- end
469
-
470
- =begin rdoc
471
- Class which checks the syntax of a ruby file.
472
-
473
- To do so, it runs a separate ruby process (using the ruby interpreter set by the
474
- Ruby Runner plugin) passing it the @-c@ and the @-e@ options with the document's
475
- content as argument.
476
-
477
- The process is executed using @Open3.popen3@
478
- =end
479
- class RubySyntaxChecker
480
-
481
- =begin rdoc
482
- Creates a new instance.
483
-
484
- @param [Document] doc the document to check
485
- =end
486
- def initialize doc
487
- @doc = doc
331
+ def delete_timer
332
+ @timer.stop
333
+ @timer.delete_later
334
+ @timer = nil
488
335
  end
336
+ slots :delete_timer
489
337
 
490
- =begin rdoc
491
- Checks the syntax of the given string.
492
-
493
- @param [String] str the string to check (usually, it'll be the document's text)
494
- @return [String] a string containing the lines of output produced by ruby concerning
495
- syntax errors or an empty string if there were no syntax error
496
- =end
497
- def check str
498
- ruby = Ruber[:ruby_development].interpreter_for @doc
499
- Open3.popen3(ruby, '-c', '-e', str) do |in_s, out_s, err_s|
500
- error = err_s.read
501
- error.gsub! %r{^-e(?=:\d+:\s+syntax error,)}, @doc.path
502
- out_s.read.strip != 'Syntax OK' ? error : ''
503
- end
338
+ def load_settings
339
+ @time_interval = Ruber[:config][:syntax_checker, :time_interval]
504
340
  end
341
+ slots :load_settings
505
342
 
506
- =begin rdoc
507
- Parses the output of {#check}
508
-
509
- @param [String] str the string to parse.
510
- @return [<SyntaxCheckerPlugin::ErrorDescription>] a list of {SyntaxCheckerPlugin::ErrorDescription ErrorDescription}s
511
- corresponding to the syntax errors mentioned in _str_
512
- =end
513
- def convert_check_result str
514
- groups = [[]]
515
- lines = str.split "\n"
516
- lines.each do |l|
517
- if l.match(/^#{Regexp.quote(@doc.path||'')}:\d+:\s+syntax error,\s+/) then groups << [l]
518
- else groups[-1] << l
519
- end
520
- end
521
- groups.delete_if{|g| g.empty?}
522
- groups.map do |a|
523
- a.shift.match(/^#{Regexp.quote(@doc.path||'')}:(\d+):\s+syntax error,\s+(.*)/)
524
- msgs = [$2]
525
- error = SyntaxCheckerPlugin::ErrorDescription.new $1.to_i
526
- if a[-1] and a[-1].match(/^\s*\^\s*$/)
527
- error.code = a[-2]
528
- # Sometimes, ruby doesn't report the whole line where the error occurs, but only
529
- # the part nearest it. In this case, the beginning of the line is replaced with ... .
530
- # In this case, to obtain the correct column number, we try a regexp match
531
- # between the part of code reported by ruby and the whole line. If it works, we
532
- # add that position to the one returned by ruby. If it doesn't (for example because
533
- # the user changed the document in the meantime), we'll just report what ruby reports
534
- col = a[-1].index('^')
535
- if a[-2].match(/^\.\.\./)
536
- lines = @doc.text.split("\n")
537
- # error.line is 1-based
538
- l = lines[error.line-1] || ''
539
- pos = (l =~ /#{Regexp.quote(a[-2][3..-1])}/)
540
- error.column = pos ? col + pos - 1 : col
541
- else error.column = col
542
- end
543
- a.pop 2
544
- end
545
- a.each{|l| msgs << l}
546
- error.message = msgs.join ' '
547
- error
548
- end
343
+ def start_waiting
344
+ @timer.start 1_000 * @time_interval if @time_interval > 0 and @doc.active?
549
345
  end
346
+ slots :start_waiting
550
347
 
551
348
  end
552
-
553
- =begin rdoc
554
- Class which checks the syntax of a ruby file.
555
-
556
- It calls the @YAML.load@ method on the document's content within a begin/rescue
557
- block, rescuing any ArgumentError exception. The message of the exception is used
558
- to find information about the error
559
- =end
560
- class YamlSyntaxChecker
561
-
562
- =begin rdoc
563
- Creates a new instance
564
-
565
- @param [Document] doc the document to check
566
- =end
567
- def initialize doc
568
- end
569
-
570
- =begin rdoc
571
- Checks the syntax of the given string.
572
-
573
- @param [String] str the string to check (usually, it'll be the document's text)
574
- @return [String] a string containing the lines of output produced by @YAML.load@
575
- concerning syntax errors or an empty string if there were no syntax error
576
- =end
577
- def check str
578
- begin
579
- YAML.load str
580
- ''
581
- rescue ArgumentError => e
582
- e.message
583
- end
584
- end
349
+
350
+ class ConfigWidget < Qt::Widget
585
351
 
586
- =begin rdoc
587
- Parses the output of {#check}
588
-
589
- @param [String] str the string to parse.
590
- @return [<SyntaxCheckerPlugin::ErrorDescription>] a list of {SyntaxCheckerPlugin::ErrorDescription ErrorDescription}s
591
- corresponding to the syntax errors mentioned in _str_
592
- =end
593
- def convert_check_result str
594
- return [] if str.empty?
595
- str.match(/^syntax error on line (\d+), col (\d+): `(.*)'$/)
596
- error = SyntaxCheckerPlugin::ErrorDescription.new $1.to_i, 'Syntax error', $3.to_s, $2.to_i
597
- [error]
352
+ def initialize parent = nil
353
+ super
354
+ @ui = Ui::SyntaxCheckerConfigWidget.new
355
+ @ui.setup_ui self
598
356
  end
599
357
 
600
- end
601
-
602
- =begin rdoc
603
- @KDE::Led@ with the ability to jump to the first syntax error on left or middle mouse
604
- click and to popup a menu with a list of all syntax errors in the current document
605
- on right click
606
- =end
607
- class SyntaxResultWidget < KDE::Led
608
-
609
- =begin rdoc
610
- Override of @Qt::Widget#mouseReleaseEvent@
611
-
612
- If the event refers to the left button and the current document contains syntax
613
- errors, it moves the cursor in the editor view to the position of the first error
614
-
615
- @return [nil]
616
- =end
617
- def mouseReleaseEvent e
618
- return unless e.button == Qt::LeftButton or e.button == Qt::MidButton
619
- doc = Ruber[:main_window].current_document
620
- view = Ruber[:main_window].active_editor
621
- return unless view and doc and doc.extension(:syntax_checker)
622
- checker = doc.extension(:syntax_checker)
623
- err = checker.errors.first
624
- if err
625
- view.go_to err.line - 1, (err.column || 0)
626
- Ruber[:main_window].status_bar.show_message Ruber[:syntax_checker].format_error_message(err), 4000
627
- end
628
- nil
629
- end
630
-
631
- =begin rdoc
632
- Override of @Qt::Widget#contextMenuEvent@
633
-
634
- If the current document contains syntax errors, it displays a menu listing them.
635
- Each action in the menu moves the cursor in the editor view to display to the line
636
- and column of the corresponding error.
637
-
638
- If the current document doesn't contain syntax errors, the menu will only contain
639
- an entry with text @Syntax OK@ which does nothing when activated.
640
-
641
- @return [nil]
642
- =end
643
- def contextMenuEvent event
644
- doc = Ruber[:main_window].current_document
645
- view = Ruber[:main_window].active_editor
646
- return unless doc and view and (checker = doc.extension :syntax_checker)
647
- errors = checker.errors
648
- actions = errors.map do |e|
649
- a = KDE::Action.new Ruber[:syntax_checker].format_error_message(e), self
650
- end
651
- actions << KDE::Action.new('Syntax OK', self) if actions.empty?
652
- res = Qt::Menu.exec(actions, event.global_pos)
653
- return if !res or errors.empty?
654
- idx = actions.index res
655
- return unless idx
656
- error = errors[idx]
657
- view.go_to error.line - 1, (error.column || 0)
658
- Ruber[:main_window].status_bar.show_message res.text, 4000
659
- nil
660
- end
661
-
662
358
  end
663
359
 
664
360
  end