ruber 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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