textbringer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +21 -0
- data/README.md +58 -0
- data/Rakefile +9 -0
- data/bin/console +9 -0
- data/exe/tb +40 -0
- data/lib/textbringer/buffer.rb +1367 -0
- data/lib/textbringer/commands.rb +690 -0
- data/lib/textbringer/config.rb +9 -0
- data/lib/textbringer/controller.rb +129 -0
- data/lib/textbringer/errors.rb +12 -0
- data/lib/textbringer/keymap.rb +141 -0
- data/lib/textbringer/mode.rb +64 -0
- data/lib/textbringer/modes/backtrace_mode.rb +38 -0
- data/lib/textbringer/modes/fundamental_mode.rb +6 -0
- data/lib/textbringer/modes/programming_mode.rb +43 -0
- data/lib/textbringer/modes/ruby_mode.rb +192 -0
- data/lib/textbringer/modes.rb +7 -0
- data/lib/textbringer/utils.rb +277 -0
- data/lib/textbringer/version.rb +3 -0
- data/lib/textbringer/window.rb +646 -0
- data/lib/textbringer.rb +10 -0
- data/textbringer.gemspec +30 -0
- metadata +170 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
TOP_LEVEL_TAG = Object.new
|
5
|
+
RECURSIVE_EDIT_TAG = Object.new
|
6
|
+
|
7
|
+
class Controller
|
8
|
+
attr_accessor :this_command, :last_command, :overriding_map
|
9
|
+
attr_accessor :prefix_arg, :current_prefix_arg
|
10
|
+
attr_reader :last_key
|
11
|
+
|
12
|
+
@@current = nil
|
13
|
+
|
14
|
+
def self.current
|
15
|
+
@@current
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.current=(controller)
|
19
|
+
@@current = controller
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@key_sequence = []
|
24
|
+
@last_key = nil
|
25
|
+
@recursive_edit_level = 0
|
26
|
+
@this_command = nil
|
27
|
+
@last_command = nil
|
28
|
+
@overriding_map = nil
|
29
|
+
@prefix_arg = nil
|
30
|
+
@current_prefix_arg = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def command_loop(tag)
|
34
|
+
catch(tag) do
|
35
|
+
loop do
|
36
|
+
begin
|
37
|
+
c = Window.current.getch
|
38
|
+
Window.echo_area.clear_message
|
39
|
+
@last_key = c
|
40
|
+
@key_sequence << @last_key
|
41
|
+
cmd = key_binding(@key_sequence)
|
42
|
+
if cmd.is_a?(Symbol) || cmd.respond_to?(:call)
|
43
|
+
@key_sequence.clear
|
44
|
+
@this_command = cmd
|
45
|
+
@current_prefix_arg = @prefix_arg
|
46
|
+
@prefix_arg = nil
|
47
|
+
begin
|
48
|
+
run_hooks(:pre_command_hook, remove_on_error: true)
|
49
|
+
if cmd.is_a?(Symbol)
|
50
|
+
send(cmd)
|
51
|
+
else
|
52
|
+
cmd.call
|
53
|
+
end
|
54
|
+
ensure
|
55
|
+
run_hooks(:post_command_hook, remove_on_error: true)
|
56
|
+
@last_command = @this_command
|
57
|
+
@this_command = nil
|
58
|
+
end
|
59
|
+
else
|
60
|
+
if cmd.nil?
|
61
|
+
keys = @key_sequence.map { |c| key_name(c) }.join(" ")
|
62
|
+
@key_sequence.clear
|
63
|
+
Window.echo_area.show("#{keys} is undefined")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
rescue Exception => e
|
67
|
+
handle_exception(e)
|
68
|
+
end
|
69
|
+
Window.redisplay
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def wait_input(msecs)
|
75
|
+
Window.current.wait_input(msecs)
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_char
|
79
|
+
Window.current.getch
|
80
|
+
end
|
81
|
+
|
82
|
+
def received_keyboard_quit?
|
83
|
+
while (key = Window.current.getch_nonblock) && key >= 0
|
84
|
+
if GLOBAL_MAP.lookup([key]) == :keyboard_quit
|
85
|
+
return true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
false
|
89
|
+
end
|
90
|
+
|
91
|
+
def recursive_edit
|
92
|
+
@recursive_edit_level += 1
|
93
|
+
begin
|
94
|
+
if command_loop(RECURSIVE_EDIT_TAG)
|
95
|
+
raise Quit
|
96
|
+
end
|
97
|
+
ensure
|
98
|
+
@recursive_edit_level -= 1
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def key_name(key)
|
105
|
+
case key
|
106
|
+
when Integer
|
107
|
+
if key < 0x80
|
108
|
+
s = Ncurses.keyname(key)
|
109
|
+
case s
|
110
|
+
when /\AKEY_(.*)/
|
111
|
+
"<#{$1.downcase}>"
|
112
|
+
else
|
113
|
+
s
|
114
|
+
end
|
115
|
+
else
|
116
|
+
key.chr(Encoding::UTF_8)
|
117
|
+
end
|
118
|
+
else
|
119
|
+
key.to_s
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def key_binding(key_sequence)
|
124
|
+
@overriding_map&.lookup(key_sequence) ||
|
125
|
+
Buffer.current&.keymap&.lookup(key_sequence) ||
|
126
|
+
GLOBAL_MAP.lookup(key_sequence)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ncursesw"
|
4
|
+
|
5
|
+
module Textbringer
|
6
|
+
class Keymap
|
7
|
+
def initialize
|
8
|
+
@map = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def define_key(key, command)
|
12
|
+
key_sequence = kbd(key)
|
13
|
+
|
14
|
+
case key_sequence.size
|
15
|
+
when 0
|
16
|
+
raise ArgumentError, "Empty key"
|
17
|
+
when 1
|
18
|
+
@map[key_sequence.first] = command
|
19
|
+
else
|
20
|
+
k, *ks = key_sequence
|
21
|
+
(@map[k] ||= Keymap.new).define_key(ks, command)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
alias [] define_key
|
25
|
+
|
26
|
+
def lookup(key_sequence)
|
27
|
+
case key_sequence.size
|
28
|
+
when 0
|
29
|
+
raise ArgumentError, "Empty key"
|
30
|
+
when 1
|
31
|
+
@map[key_sequence.first]
|
32
|
+
else
|
33
|
+
k, *ks = key_sequence
|
34
|
+
@map[k]&.lookup(ks)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def handle_undefined_key
|
39
|
+
@map.default_proc = Proc.new { |h, k| yield(k) }
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def kbd(key)
|
45
|
+
case key
|
46
|
+
when Integer, Symbol
|
47
|
+
[key]
|
48
|
+
when String
|
49
|
+
key.unpack("C*")
|
50
|
+
when Array
|
51
|
+
key
|
52
|
+
else
|
53
|
+
raise TypeError, "invalid key type #{key.class}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
GLOBAL_MAP = Keymap.new
|
59
|
+
GLOBAL_MAP.define_key(:resize, :resize_window)
|
60
|
+
GLOBAL_MAP.define_key(:right, :forward_char)
|
61
|
+
GLOBAL_MAP.define_key(?\C-f, :forward_char)
|
62
|
+
GLOBAL_MAP.define_key(:left, :backward_char)
|
63
|
+
GLOBAL_MAP.define_key(?\C-b, :backward_char)
|
64
|
+
GLOBAL_MAP.define_key("\ef", :forward_word)
|
65
|
+
GLOBAL_MAP.define_key("\eb", :backward_word)
|
66
|
+
GLOBAL_MAP.define_key("\egc", :goto_char)
|
67
|
+
GLOBAL_MAP.define_key("\egg", :goto_line)
|
68
|
+
GLOBAL_MAP.define_key("\eg\eg", :goto_line)
|
69
|
+
GLOBAL_MAP.define_key(:down, :next_line)
|
70
|
+
GLOBAL_MAP.define_key(?\C-n, :next_line)
|
71
|
+
GLOBAL_MAP.define_key(:up, :previous_line)
|
72
|
+
GLOBAL_MAP.define_key(?\C-p, :previous_line)
|
73
|
+
GLOBAL_MAP.define_key(:dc, :delete_char)
|
74
|
+
GLOBAL_MAP.define_key(?\C-d, :delete_char)
|
75
|
+
GLOBAL_MAP.define_key(:backspace, :backward_delete_char)
|
76
|
+
GLOBAL_MAP.define_key(?\C-h, :backward_delete_char)
|
77
|
+
GLOBAL_MAP.define_key(?\C-a, :beginning_of_line)
|
78
|
+
GLOBAL_MAP.define_key(:home, :beginning_of_line)
|
79
|
+
GLOBAL_MAP.define_key(?\C-e, :end_of_line)
|
80
|
+
GLOBAL_MAP.define_key(:end, :end_of_line)
|
81
|
+
GLOBAL_MAP.define_key("\e<", :beginning_of_buffer)
|
82
|
+
GLOBAL_MAP.define_key("\e>", :end_of_buffer)
|
83
|
+
(0x20..0x7e).each do |c|
|
84
|
+
GLOBAL_MAP.define_key(c, :self_insert)
|
85
|
+
end
|
86
|
+
GLOBAL_MAP.define_key(?\t, :self_insert)
|
87
|
+
GLOBAL_MAP.define_key(?\C-q, :quoted_insert)
|
88
|
+
GLOBAL_MAP.define_key("\C- ", :set_mark)
|
89
|
+
GLOBAL_MAP.define_key("\C-x\C-x", :exchange_point_and_mark)
|
90
|
+
GLOBAL_MAP.define_key("\ew", :copy_region)
|
91
|
+
GLOBAL_MAP.define_key(?\C-w, :kill_region)
|
92
|
+
GLOBAL_MAP.define_key(?\C-k, :kill_line)
|
93
|
+
GLOBAL_MAP.define_key("\ed", :kill_word)
|
94
|
+
GLOBAL_MAP.define_key(?\C-y, :yank)
|
95
|
+
GLOBAL_MAP.define_key("\ey", :yank_pop)
|
96
|
+
GLOBAL_MAP.define_key(?\C-_, :undo)
|
97
|
+
GLOBAL_MAP.define_key("\C-x\C-_", :redo)
|
98
|
+
GLOBAL_MAP.define_key("\C-t", :transpose_chars)
|
99
|
+
GLOBAL_MAP.define_key(?\n, :newline)
|
100
|
+
GLOBAL_MAP.define_key("\C-l", :recenter)
|
101
|
+
GLOBAL_MAP.define_key("\C-v", :scroll_up)
|
102
|
+
GLOBAL_MAP.define_key(:npage, :scroll_up)
|
103
|
+
GLOBAL_MAP.define_key("\ev", :scroll_down)
|
104
|
+
GLOBAL_MAP.define_key(:ppage, :scroll_down)
|
105
|
+
GLOBAL_MAP.define_key("\C-x0", :delete_window)
|
106
|
+
GLOBAL_MAP.define_key("\C-x1", :delete_other_windows)
|
107
|
+
GLOBAL_MAP.define_key("\C-x2", :split_window)
|
108
|
+
GLOBAL_MAP.define_key("\C-xo", :other_window)
|
109
|
+
GLOBAL_MAP.define_key("\C-x\C-c", :exit_textbringer)
|
110
|
+
GLOBAL_MAP.define_key("\C-z", :suspend_textbringer)
|
111
|
+
GLOBAL_MAP.define_key("\C-x\C-f", :find_file)
|
112
|
+
GLOBAL_MAP.define_key("\C-xb", :switch_to_buffer)
|
113
|
+
GLOBAL_MAP.define_key("\C-x\C-s", :save_buffer)
|
114
|
+
GLOBAL_MAP.define_key("\C-x\C-w", :write_file)
|
115
|
+
GLOBAL_MAP.define_key("\C-xk", :kill_buffer)
|
116
|
+
GLOBAL_MAP.define_key("\C-x\nf", :set_buffer_file_encoding)
|
117
|
+
GLOBAL_MAP.define_key("\C-x\nn", :set_buffer_file_format)
|
118
|
+
GLOBAL_MAP.define_key("\ex", :execute_command)
|
119
|
+
GLOBAL_MAP.define_key("\e:", :eval_expression)
|
120
|
+
GLOBAL_MAP.define_key(?\C-u, :universal_argument)
|
121
|
+
GLOBAL_MAP.define_key(?\C-g, :keyboard_quit)
|
122
|
+
GLOBAL_MAP.define_key(?\C-s, :isearch_forward)
|
123
|
+
GLOBAL_MAP.define_key(?\C-r, :isearch_backward)
|
124
|
+
GLOBAL_MAP.handle_undefined_key do |key|
|
125
|
+
if key.is_a?(Integer) && key > 0x80
|
126
|
+
begin
|
127
|
+
key.chr(Encoding::UTF_8)
|
128
|
+
:self_insert
|
129
|
+
rescue RangeError
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
else
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
MINIBUFFER_LOCAL_MAP = Keymap.new
|
138
|
+
MINIBUFFER_LOCAL_MAP.define_key(?\n, :exit_recursive_edit)
|
139
|
+
MINIBUFFER_LOCAL_MAP.define_key(?\t, :complete_minibuffer)
|
140
|
+
MINIBUFFER_LOCAL_MAP.define_key(?\C-g, :abort_recursive_edit)
|
141
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
class Mode
|
5
|
+
extend Commands
|
6
|
+
include Commands
|
7
|
+
|
8
|
+
@@mode_list = []
|
9
|
+
|
10
|
+
def self.list
|
11
|
+
@@mode_list
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :mode_name
|
16
|
+
attr_accessor :command_name
|
17
|
+
attr_accessor :hook_name
|
18
|
+
attr_accessor :file_name_pattern
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.define_generic_command(name)
|
22
|
+
command_name = (name.to_s + "_command").intern
|
23
|
+
define_command(command_name) do |*args|
|
24
|
+
begin
|
25
|
+
Buffer.current.mode.send(name, *args)
|
26
|
+
rescue NoMethodError => e
|
27
|
+
if e.receiver == Buffer.current.mode && e.name == name
|
28
|
+
raise EditorError,
|
29
|
+
"#{command_name} is not supported in the current mode"
|
30
|
+
else
|
31
|
+
raise
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.inherited(child)
|
38
|
+
base_name = child.name.slice(/[^:]*\z/)
|
39
|
+
child.mode_name = base_name.sub(/Mode\z/, "")
|
40
|
+
command_name = base_name.sub(/\A[A-Z]/) { |s| s.downcase }.
|
41
|
+
gsub(/(?<=[a-z])([A-Z])/) {
|
42
|
+
"_" + $1.downcase
|
43
|
+
}
|
44
|
+
command = command_name.intern
|
45
|
+
hook = (command_name + "_hook").intern
|
46
|
+
child.command_name = command
|
47
|
+
child.hook_name = hook
|
48
|
+
define_command(command) do
|
49
|
+
Buffer.current.apply_mode(child)
|
50
|
+
end
|
51
|
+
@@mode_list.push(child)
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :buffer
|
55
|
+
|
56
|
+
def initialize(buffer)
|
57
|
+
@buffer = buffer
|
58
|
+
end
|
59
|
+
|
60
|
+
def name
|
61
|
+
self.class.mode_name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
class BacktraceMode < Mode
|
5
|
+
define_generic_command :jump_to_source_location
|
6
|
+
|
7
|
+
BACKTRACE_MODE_MAP = Keymap.new
|
8
|
+
BACKTRACE_MODE_MAP.define_key("\n", :jump_to_source_location_command)
|
9
|
+
|
10
|
+
def initialize(buffer)
|
11
|
+
super(buffer)
|
12
|
+
buffer.keymap = BACKTRACE_MODE_MAP
|
13
|
+
end
|
14
|
+
|
15
|
+
def jump_to_source_location
|
16
|
+
file_name, line_number = get_source_location
|
17
|
+
if file_name
|
18
|
+
find_file(file_name)
|
19
|
+
goto_line(line_number)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def get_source_location
|
26
|
+
@buffer.save_excursion do
|
27
|
+
@buffer.beginning_of_line
|
28
|
+
if @buffer.looking_at?(/^(\S*?):(\d+):/)
|
29
|
+
file_name = @buffer.match_string(1)
|
30
|
+
line_number = @buffer.match_string(2).to_i
|
31
|
+
[file_name, line_number]
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
class ProgrammingMode < Mode
|
5
|
+
# abstract mode
|
6
|
+
undefine_command(:programming_mode)
|
7
|
+
|
8
|
+
define_generic_command :indent_line
|
9
|
+
define_generic_command :newline_and_reindent
|
10
|
+
define_generic_command :forward_definition
|
11
|
+
define_generic_command :backward_definition
|
12
|
+
define_generic_command :compile
|
13
|
+
|
14
|
+
PROGRAMMING_MODE_MAP = Keymap.new
|
15
|
+
PROGRAMMING_MODE_MAP.define_key("\t", :indent_line_command)
|
16
|
+
PROGRAMMING_MODE_MAP.define_key("\n", :newline_and_reindent_command)
|
17
|
+
PROGRAMMING_MODE_MAP.define_key("\C-c\C-n", :forward_definition_command)
|
18
|
+
PROGRAMMING_MODE_MAP.define_key("\C-c\C-p", :backward_definition_command)
|
19
|
+
PROGRAMMING_MODE_MAP.define_key("\C-c\C-c", :compile_command)
|
20
|
+
|
21
|
+
def initialize(buffer)
|
22
|
+
super(buffer)
|
23
|
+
buffer.keymap = PROGRAMMING_MODE_MAP
|
24
|
+
end
|
25
|
+
|
26
|
+
def newline_and_reindent
|
27
|
+
n = 1
|
28
|
+
@buffer.save_excursion do
|
29
|
+
pos = @buffer.point
|
30
|
+
@buffer.beginning_of_line
|
31
|
+
if /\A\s+\z/ =~ @buffer.substring(@buffer.point, pos)
|
32
|
+
@buffer.delete_region(@buffer.point, pos)
|
33
|
+
n += 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@buffer.insert("\n")
|
37
|
+
if indent_line
|
38
|
+
n += 1
|
39
|
+
end
|
40
|
+
@buffer.merge_undo(n) if n > 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ripper"
|
4
|
+
|
5
|
+
module Textbringer
|
6
|
+
CONFIG[:ruby_indent_level] = 2
|
7
|
+
CONFIG[:ruby_indent_tabs_mode] = false
|
8
|
+
|
9
|
+
class RubyMode < ProgrammingMode
|
10
|
+
self.file_name_pattern = /\A(?:.*\.(?:rb|ru|rake|thor)|
|
11
|
+
(?:Gem|Rake|Cap|Thor|Vagrant|Guard|Pod)file)\z/x
|
12
|
+
|
13
|
+
def initialize(buffer)
|
14
|
+
super(buffer)
|
15
|
+
@buffer[:indent_tabs_mode] = CONFIG[:ruby_indent_tabs_mode]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return true if modified.
|
19
|
+
def indent_line
|
20
|
+
result = false
|
21
|
+
level = calculate_indentation
|
22
|
+
@buffer.save_excursion do
|
23
|
+
@buffer.beginning_of_line
|
24
|
+
has_space = @buffer.looking_at?(/[ \t]+/)
|
25
|
+
if has_space
|
26
|
+
s = @buffer.match_string(0)
|
27
|
+
break if /\t/ !~ s && s.size == level
|
28
|
+
@buffer.delete_region(@buffer.match_beginning(0),
|
29
|
+
@buffer.match_end(0))
|
30
|
+
else
|
31
|
+
break if level == 0
|
32
|
+
end
|
33
|
+
@buffer.indent_to(level)
|
34
|
+
if has_space
|
35
|
+
@buffer.merge_undo(2)
|
36
|
+
end
|
37
|
+
result = true
|
38
|
+
end
|
39
|
+
pos = @buffer.point
|
40
|
+
@buffer.beginning_of_line
|
41
|
+
@buffer.forward_char while /[ \t]/ =~ @buffer.char_after
|
42
|
+
if @buffer.point < pos
|
43
|
+
@buffer.goto_char(pos)
|
44
|
+
end
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
def forward_definition(n = number_prefix_arg || 1)
|
49
|
+
tokens = Ripper.lex(@buffer.to_s)
|
50
|
+
@buffer.forward_line
|
51
|
+
n.times do |i|
|
52
|
+
tokens = tokens.drop_while { |(l, c), e, t|
|
53
|
+
l < @buffer.current_line ||
|
54
|
+
e != :on_kw || /\A(?:class|module|def)\z/ !~ t
|
55
|
+
}
|
56
|
+
(line, column), event, text = tokens.first
|
57
|
+
if line.nil?
|
58
|
+
@buffer.end_of_buffer
|
59
|
+
break
|
60
|
+
end
|
61
|
+
@buffer.goto_line(line)
|
62
|
+
tokens = tokens.drop(1)
|
63
|
+
end
|
64
|
+
while /\s/ =~ @buffer.char_after
|
65
|
+
@buffer.forward_char
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def backward_definition(n = number_prefix_arg || 1)
|
70
|
+
tokens = Ripper.lex(@buffer.to_s).reverse
|
71
|
+
@buffer.beginning_of_line
|
72
|
+
n.times do |i|
|
73
|
+
tokens = tokens.drop_while { |(l, c), e, t|
|
74
|
+
l >= @buffer.current_line ||
|
75
|
+
e != :on_kw || /\A(?:class|module|def)\z/ !~ t
|
76
|
+
}
|
77
|
+
(line, column), event, text = tokens.first
|
78
|
+
if line.nil?
|
79
|
+
@buffer.beginning_of_buffer
|
80
|
+
break
|
81
|
+
end
|
82
|
+
@buffer.goto_line(line)
|
83
|
+
tokens = tokens.drop(1)
|
84
|
+
end
|
85
|
+
while /\s/ =~ @buffer.char_after
|
86
|
+
@buffer.forward_char
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def compile
|
91
|
+
cmd = read_from_minibuffer("Compile: ", default: default_compile_command)
|
92
|
+
shell_execute(cmd, "*Ruby compile result*")
|
93
|
+
backtrace_mode
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def calculate_indentation
|
99
|
+
if @buffer.current_line == 1
|
100
|
+
return 0
|
101
|
+
end
|
102
|
+
@buffer.save_excursion do
|
103
|
+
@buffer.beginning_of_line
|
104
|
+
bol_pos = @buffer.point
|
105
|
+
tokens = Ripper.lex(@buffer.substring(@buffer.point_min,
|
106
|
+
@buffer.point))
|
107
|
+
line, column, event, text = find_nearest_beginning_token(tokens)
|
108
|
+
if event == :on_lparen
|
109
|
+
return column + 1
|
110
|
+
end
|
111
|
+
if line
|
112
|
+
@buffer.goto_line(line)
|
113
|
+
else
|
114
|
+
@buffer.backward_line
|
115
|
+
end
|
116
|
+
@buffer.looking_at?(/[ \t]*/)
|
117
|
+
base_indentation = @buffer.match_string(0).
|
118
|
+
gsub(/\t/, " " * @buffer[:tab_width]).size
|
119
|
+
@buffer.goto_char(bol_pos)
|
120
|
+
if line.nil? ||
|
121
|
+
@buffer.looking_at?(/[ \t]*([}\])]|(end|else|elsif|when|rescue|ensure)\b)/)
|
122
|
+
indentation = base_indentation
|
123
|
+
else
|
124
|
+
indentation = base_indentation + @buffer[:ruby_indent_level]
|
125
|
+
end
|
126
|
+
_, last_event, last_text = tokens.reverse_each.find { |_, e, _|
|
127
|
+
e != :on_sp && e != :on_nl && e != :on_ignored_nl
|
128
|
+
}
|
129
|
+
if (last_event == :on_op && last_text != "|") ||
|
130
|
+
last_event == :on_period
|
131
|
+
indentation += @buffer[:ruby_indent_level]
|
132
|
+
end
|
133
|
+
indentation
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
BLOCK_END = {
|
138
|
+
"{" => "}",
|
139
|
+
"(" => ")",
|
140
|
+
"[" => "]"
|
141
|
+
}
|
142
|
+
|
143
|
+
def find_nearest_beginning_token(tokens)
|
144
|
+
stack = []
|
145
|
+
(tokens.size - 1).downto(0) do |i|
|
146
|
+
(line, column), event, text = tokens[i]
|
147
|
+
case event
|
148
|
+
when :on_kw
|
149
|
+
case text
|
150
|
+
when "class", "module", "def", "if", "unless", "case",
|
151
|
+
"do", "for", "while", "until", "begin"
|
152
|
+
if /\A(if|unless|while|until)\z/ =~ text
|
153
|
+
ts = tokens[0...i].reverse_each.take_while { |(l,_),| l == line }
|
154
|
+
t = ts.find { |_, e| e != :on_sp }
|
155
|
+
next if t && !(t[1] == :on_op && t[2] == "=")
|
156
|
+
end
|
157
|
+
if stack.empty?
|
158
|
+
return line, column, event, text
|
159
|
+
end
|
160
|
+
if stack.last != "end"
|
161
|
+
raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}"
|
162
|
+
end
|
163
|
+
stack.pop
|
164
|
+
when "end"
|
165
|
+
stack.push(text)
|
166
|
+
end
|
167
|
+
when :on_rbrace, :on_rparen, :on_rbracket
|
168
|
+
stack.push(text)
|
169
|
+
when :on_lbrace, :on_lparen, :on_lbracket, :on_tlambeg
|
170
|
+
if stack.empty?
|
171
|
+
return line, column, event, text
|
172
|
+
end
|
173
|
+
if stack.last != BLOCK_END[text]
|
174
|
+
raise EditorError, "#{@buffer.name}:#{line}: Unmatched #{text}"
|
175
|
+
end
|
176
|
+
stack.pop
|
177
|
+
end
|
178
|
+
end
|
179
|
+
return nil
|
180
|
+
end
|
181
|
+
|
182
|
+
def default_compile_command
|
183
|
+
@buffer[:ruby_compile_command] ||
|
184
|
+
if File.exist?("Rakefile")
|
185
|
+
prefix = File.exist?("Gemfile") ? "bundle exec " : ""
|
186
|
+
prefix + "rake"
|
187
|
+
else
|
188
|
+
"ruby " + @buffer.file_name
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|