textbringer 0.1.3 → 0.1.4
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.
- checksums.yaml +4 -4
- data/CHANGES.md +7 -0
- data/Guardfile +22 -0
- data/README.md +10 -0
- data/Rakefile +1 -0
- data/appveyor.yml +13 -0
- data/lib/textbringer.rb +2 -0
- data/lib/textbringer/buffer.rb +72 -24
- data/lib/textbringer/commands.rb +30 -7
- data/lib/textbringer/commands/ctags.rb +114 -0
- data/lib/textbringer/commands/dabbrev.rb +96 -0
- data/lib/textbringer/config.rb +4 -2
- data/lib/textbringer/controller.rb +1 -1
- data/lib/textbringer/keymap.rb +2 -1
- data/lib/textbringer/mode.rb +1 -0
- data/lib/textbringer/modes/fundamental_mode.rb +3 -0
- data/lib/textbringer/modes/programming_mode.rb +3 -1
- data/lib/textbringer/modes/ruby_mode.rb +52 -6
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer/window.rb +11 -11
- data/textbringer.gemspec +3 -0
- metadata +48 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4c0499915d9509f0f2ef8d8b30b8a3c87955c9c
|
4
|
+
data.tar.gz: 017b6339f497de93887ee252df2b1da9b3c20bae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 813608d1b0cd0f5a120256a49f5509a75f0d4c8a05c1f803e3b6625f8161f3a249a944fc447e16dc50eb9fb9754f2e8b6ab2d55bca0df4ef9a45887151d04637
|
7
|
+
data.tar.gz: a3b1267f21be52ef1e9136cc530cf7b0f0cd158d636940d0143980b79a54ef96cefbb354aae2e9eb877942097ee90044e2e8d4991cbfb55671c5f049fc8bb365
|
data/CHANGES.md
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
guard :shell do
|
19
|
+
watch(%r'^(lib|test)/.+\.rb$') do
|
20
|
+
`ripper-tags -R`
|
21
|
+
end
|
22
|
+
end
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Textbringer
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/textbringer)
|
3
4
|
[](https://travis-ci.org/shugo/textbringer)
|
5
|
+
[](https://ci.appveyor.com/project/shugo31737/textbringer)
|
4
6
|
|
5
7
|
Textbringer is a member of a demon race that takes on the form of a text
|
6
8
|
editor.
|
@@ -9,6 +11,14 @@ editor.
|
|
9
11
|
|
10
12
|
[](https://asciinema.org/a/100156)
|
11
13
|
|
14
|
+
## WARNING
|
15
|
+
|
16
|
+
Textbringer is beta software, and you may lose your text. Unsaved buffers will
|
17
|
+
be dumped in ~/.textbringer/buffer_dump on crash.
|
18
|
+
|
19
|
+
APIs are undocumented and unstable. There is no compatibility even in the same
|
20
|
+
minor versions.
|
21
|
+
|
12
22
|
## Installation
|
13
23
|
|
14
24
|
$ gem install textbringer
|
data/Rakefile
CHANGED
data/appveyor.yml
ADDED
data/lib/textbringer.rb
CHANGED
@@ -6,5 +6,7 @@ require_relative "textbringer/window"
|
|
6
6
|
require_relative "textbringer/keymap"
|
7
7
|
require_relative "textbringer/utils"
|
8
8
|
require_relative "textbringer/commands"
|
9
|
+
require_relative "textbringer/commands/dabbrev"
|
10
|
+
require_relative "textbringer/commands/ctags"
|
9
11
|
require_relative "textbringer/modes"
|
10
12
|
require_relative "textbringer/controller"
|
data/lib/textbringer/buffer.rb
CHANGED
@@ -122,8 +122,8 @@ module Textbringer
|
|
122
122
|
|
123
123
|
def self.find_file(file_name)
|
124
124
|
file_name = File.expand_path(file_name)
|
125
|
-
buffer = @@table.each_value.find { |
|
126
|
-
|
125
|
+
buffer = @@table.each_value.find { |b|
|
126
|
+
b.file_name == file_name
|
127
127
|
}
|
128
128
|
if buffer.nil?
|
129
129
|
name = File.basename(file_name)
|
@@ -154,7 +154,7 @@ module Textbringer
|
|
154
154
|
end
|
155
155
|
|
156
156
|
def self.each(&block)
|
157
|
-
@@
|
157
|
+
@@list.each(&block)
|
158
158
|
end
|
159
159
|
|
160
160
|
def self.display_width(s)
|
@@ -164,7 +164,8 @@ module Textbringer
|
|
164
164
|
|
165
165
|
# s might not be copied.
|
166
166
|
def initialize(s = String.new, name: nil,
|
167
|
-
file_name: nil,
|
167
|
+
file_name: nil,
|
168
|
+
file_encoding: CONFIG[:default_file_encoding],
|
168
169
|
file_mtime: nil, new_file: true, undo_limit: UNDO_LIMIT,
|
169
170
|
read_only: false)
|
170
171
|
case s.encoding
|
@@ -188,7 +189,7 @@ module Textbringer
|
|
188
189
|
@file_format = :dos
|
189
190
|
@contents.gsub!(/\r/, "")
|
190
191
|
else
|
191
|
-
@file_format = :
|
192
|
+
@file_format = CONFIG[:default_file_format]
|
192
193
|
end
|
193
194
|
@new_file = new_file
|
194
195
|
@undo_limit = undo_limit
|
@@ -206,7 +207,7 @@ module Textbringer
|
|
206
207
|
@undoing = false
|
207
208
|
@version = 0
|
208
209
|
@modified = false
|
209
|
-
@mode =
|
210
|
+
@mode = FundamentalMode.new(self)
|
210
211
|
@keymap = nil
|
211
212
|
@attributes = {}
|
212
213
|
@save_point_level = 0
|
@@ -303,7 +304,9 @@ module Textbringer
|
|
303
304
|
end
|
304
305
|
|
305
306
|
def self.open(file_name, name: File.basename(file_name))
|
306
|
-
s, mtime = File.open(file_name
|
307
|
+
s, mtime = File.open(file_name,
|
308
|
+
external_encoding: Encoding::ASCII_8BIT,
|
309
|
+
binmode: true) { |f|
|
307
310
|
f.flock(File::LOCK_SH)
|
308
311
|
[f.read, f.mtime]
|
309
312
|
}
|
@@ -324,7 +327,8 @@ module Textbringer
|
|
324
327
|
end
|
325
328
|
file_name = File.expand_path(file_name)
|
326
329
|
begin
|
327
|
-
File.open(file_name, "w",
|
330
|
+
File.open(file_name, "w",
|
331
|
+
external_encoding: @file_encoding, binmode: true) do |f|
|
328
332
|
f.flock(File::LOCK_EX)
|
329
333
|
write_to_file(f)
|
330
334
|
f.flush
|
@@ -394,6 +398,19 @@ module Textbringer
|
|
394
398
|
end
|
395
399
|
end
|
396
400
|
|
401
|
+
def char_before(location = @point)
|
402
|
+
if @binary
|
403
|
+
byte_before(location)
|
404
|
+
else
|
405
|
+
if beginning_of_buffer?
|
406
|
+
nil
|
407
|
+
else
|
408
|
+
pos = get_pos(location, -1)
|
409
|
+
substring(pos, location)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
397
414
|
def bytesize
|
398
415
|
@contents.bytesize - gap_size
|
399
416
|
end
|
@@ -549,28 +566,28 @@ module Textbringer
|
|
549
566
|
forward_char(-n)
|
550
567
|
end
|
551
568
|
|
552
|
-
def forward_word(n = 1)
|
569
|
+
def forward_word(n = 1, regexp: /\p{Letter}|\p{Number}/)
|
553
570
|
n.times do
|
554
|
-
while !end_of_buffer? &&
|
571
|
+
while !end_of_buffer? && regexp !~ char_after
|
555
572
|
forward_char
|
556
573
|
end
|
557
|
-
while !end_of_buffer? &&
|
574
|
+
while !end_of_buffer? && regexp =~ char_after
|
558
575
|
forward_char
|
559
576
|
end
|
560
577
|
end
|
561
578
|
end
|
562
579
|
|
563
|
-
def backward_word(n = 1)
|
580
|
+
def backward_word(n = 1, regexp: /\p{Letter}|\p{Number}/)
|
564
581
|
n.times do
|
565
582
|
break if beginning_of_buffer?
|
566
583
|
backward_char
|
567
|
-
while !beginning_of_buffer? &&
|
584
|
+
while !beginning_of_buffer? && regexp !~ char_after
|
568
585
|
backward_char
|
569
586
|
end
|
570
|
-
while !beginning_of_buffer? &&
|
587
|
+
while !beginning_of_buffer? && regexp =~ char_after
|
571
588
|
backward_char
|
572
589
|
end
|
573
|
-
if
|
590
|
+
if regexp !~ char_after
|
574
591
|
forward_char
|
575
592
|
end
|
576
593
|
end
|
@@ -914,26 +931,38 @@ module Textbringer
|
|
914
931
|
end
|
915
932
|
end
|
916
933
|
|
917
|
-
def re_search_forward(s)
|
934
|
+
def re_search_forward(s, raise_error: true)
|
918
935
|
re = new_regexp(s)
|
919
936
|
i = byteindex(true, re, @point)
|
920
937
|
if i.nil?
|
921
|
-
|
938
|
+
if raise_error
|
939
|
+
raise SearchError, "Search failed"
|
940
|
+
else
|
941
|
+
return nil
|
942
|
+
end
|
922
943
|
end
|
923
944
|
goto_char(match_end(0))
|
924
945
|
end
|
925
946
|
|
926
|
-
def re_search_backward(s)
|
947
|
+
def re_search_backward(s, raise_error: true)
|
927
948
|
re = new_regexp(s)
|
928
949
|
pos = @point
|
929
950
|
begin
|
930
951
|
i = byteindex(false, re, pos)
|
931
952
|
if i.nil?
|
932
|
-
|
953
|
+
if raise_error
|
954
|
+
raise SearchError, "Search failed"
|
955
|
+
else
|
956
|
+
return nil
|
957
|
+
end
|
933
958
|
end
|
934
959
|
pos = get_pos(pos, -1)
|
935
960
|
rescue RangeError
|
936
|
-
|
961
|
+
if raise_error
|
962
|
+
raise SearchError, "Search failed"
|
963
|
+
else
|
964
|
+
return nil
|
965
|
+
end
|
937
966
|
end while match_end(0) > @point
|
938
967
|
goto_char(match_beginning(0))
|
939
968
|
end
|
@@ -1096,19 +1125,19 @@ module Textbringer
|
|
1096
1125
|
end
|
1097
1126
|
|
1098
1127
|
def dump(path)
|
1099
|
-
File.
|
1128
|
+
File.binwrite(path, to_s)
|
1100
1129
|
metadata = {
|
1101
1130
|
"name" => name,
|
1102
1131
|
"file_name" => file_name,
|
1103
1132
|
"file_encoding" => file_encoding.name,
|
1104
1133
|
"file_format" => file_format.to_s
|
1105
1134
|
}
|
1106
|
-
File.
|
1135
|
+
File.binwrite(path + ".metadata", metadata.to_json)
|
1107
1136
|
end
|
1108
1137
|
|
1109
1138
|
def self.load(path)
|
1110
|
-
buffer = Buffer.new(File.
|
1111
|
-
metadata = JSON.parse(File.
|
1139
|
+
buffer = Buffer.new(File.binread(path))
|
1140
|
+
metadata = JSON.parse(File.binread(path + ".metadata"))
|
1112
1141
|
buffer.name = metadata["name"]
|
1113
1142
|
buffer.file_name = metadata["file_name"] if metadata["file_name"]
|
1114
1143
|
buffer.file_encoding = Encoding.find(metadata["file_encoding"])
|
@@ -1141,6 +1170,24 @@ module Textbringer
|
|
1141
1170
|
end
|
1142
1171
|
end
|
1143
1172
|
|
1173
|
+
def current_symbol
|
1174
|
+
from = save_point { skip_re_backward(@mode.symbol_pattern); @point }
|
1175
|
+
to = save_point { skip_re_forward(@mode.symbol_pattern); @point }
|
1176
|
+
from < to ? substring(from, to) : nil
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
def skip_re_forward(re)
|
1180
|
+
while re =~ char_after
|
1181
|
+
forward_char
|
1182
|
+
end
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
def skip_re_backward(re)
|
1186
|
+
while re =~ char_before
|
1187
|
+
backward_char
|
1188
|
+
end
|
1189
|
+
end
|
1190
|
+
|
1144
1191
|
private
|
1145
1192
|
|
1146
1193
|
def adjust_gap(min_size = 0, pos = @point)
|
@@ -1293,6 +1340,7 @@ module Textbringer
|
|
1293
1340
|
end
|
1294
1341
|
|
1295
1342
|
class Mark
|
1343
|
+
attr_reader :buffer
|
1296
1344
|
attr_accessor :location
|
1297
1345
|
|
1298
1346
|
def initialize(buffer, location)
|
data/lib/textbringer/commands.rb
CHANGED
@@ -52,7 +52,6 @@ module Textbringer
|
|
52
52
|
:end_of_line,
|
53
53
|
:beginning_of_buffer,
|
54
54
|
:end_of_buffer,
|
55
|
-
:set_mark,
|
56
55
|
:exchange_point_and_mark,
|
57
56
|
:copy_region,
|
58
57
|
:kill_region,
|
@@ -66,6 +65,11 @@ module Textbringer
|
|
66
65
|
end
|
67
66
|
end
|
68
67
|
|
68
|
+
define_command(:set_mark_command) do
|
69
|
+
Buffer.current.set_mark
|
70
|
+
message("Mark set")
|
71
|
+
end
|
72
|
+
|
69
73
|
define_command(:goto_char) do
|
70
74
|
|n = read_from_minibuffer("Go to char: ")|
|
71
75
|
Buffer.current.goto_char(n.to_i)
|
@@ -125,6 +129,13 @@ module Textbringer
|
|
125
129
|
Buffer.current.re_search_forward(s)
|
126
130
|
end
|
127
131
|
|
132
|
+
define_command(:re_search_backward) do
|
133
|
+
|s = read_from_minibuffer("RE search backward: ",
|
134
|
+
default: RE_SEARCH_STATUS[:last_regexp])|
|
135
|
+
RE_SEARCH_STATUS[:last_regexp] = s
|
136
|
+
Buffer.current.re_search_backward(s)
|
137
|
+
end
|
138
|
+
|
128
139
|
def match_beginning(n)
|
129
140
|
Buffer.current.match_beginning(n)
|
130
141
|
end
|
@@ -143,7 +154,7 @@ module Textbringer
|
|
143
154
|
|
144
155
|
define_command(:query_replace_regexp) do
|
145
156
|
|regexp = read_from_minibuffer("Query replace regexp: "),
|
146
|
-
to_str = read_from_minibuffer("
|
157
|
+
to_str = read_from_minibuffer("with: ")|
|
147
158
|
n = 0
|
148
159
|
begin
|
149
160
|
loop do
|
@@ -255,9 +266,15 @@ module Textbringer
|
|
255
266
|
message("New file")
|
256
267
|
end
|
257
268
|
switch_to_buffer(buffer)
|
258
|
-
|
259
|
-
|
260
|
-
|
269
|
+
shebang = buffer.save_excursion {
|
270
|
+
buffer.beginning_of_buffer
|
271
|
+
buffer.looking_at?(/#!.*$/) ? buffer.match_string(0) : nil
|
272
|
+
}
|
273
|
+
mode = Mode.list.find { |m|
|
274
|
+
(m.file_name_pattern &&
|
275
|
+
m.file_name_pattern =~ File.basename(buffer.file_name)) ||
|
276
|
+
(m.interpreter_name_pattern &&
|
277
|
+
m.interpreter_name_pattern =~ shebang)
|
261
278
|
} || FundamentalMode
|
262
279
|
send(mode.command_name)
|
263
280
|
end
|
@@ -630,7 +647,12 @@ module Textbringer
|
|
630
647
|
Window.redisplay
|
631
648
|
signals = [:INT, :TERM, :KILL]
|
632
649
|
begin
|
633
|
-
|
650
|
+
if /mswin32|mingw32/ =~ RUBY_PLATFORM
|
651
|
+
opts = {}
|
652
|
+
else
|
653
|
+
opts = {pgroup: true}
|
654
|
+
end
|
655
|
+
Open3.popen2e(cmd, opts) do |input, output, wait_thread|
|
634
656
|
input.close
|
635
657
|
loop do
|
636
658
|
status = output.wait_readable(0.5)
|
@@ -639,7 +661,8 @@ module Textbringer
|
|
639
661
|
end
|
640
662
|
if status
|
641
663
|
begin
|
642
|
-
s = output.read_nonblock(1024)
|
664
|
+
s = output.read_nonblock(1024).force_encoding("utf-8").
|
665
|
+
scrub("\u{3013}").gsub(/\r\n/, "\n")
|
643
666
|
buffer.insert(s)
|
644
667
|
Window.redisplay
|
645
668
|
rescue EOFError
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
module Commands
|
5
|
+
GLOBAL_MAP.define_key("\e.", :find_tag)
|
6
|
+
GLOBAL_MAP.define_key("\e*", :pop_tag_mark)
|
7
|
+
|
8
|
+
CTAGS = {
|
9
|
+
path: nil,
|
10
|
+
tags: nil,
|
11
|
+
tags_mtime: nil,
|
12
|
+
name: nil,
|
13
|
+
candidates: nil,
|
14
|
+
index: nil,
|
15
|
+
tag_mark_stack: []
|
16
|
+
}
|
17
|
+
TAG_MARK_LIMIT = 16
|
18
|
+
|
19
|
+
define_command(:find_tag) do |next_p = current_prefix_arg|
|
20
|
+
tags = get_tags
|
21
|
+
if next_p
|
22
|
+
name = CTAGS[:name]
|
23
|
+
if name.nil?
|
24
|
+
raise EditorError, "Tag search not started yet"
|
25
|
+
end
|
26
|
+
candidates = CTAGS[:candidates]
|
27
|
+
index = CTAGS[:index]
|
28
|
+
if next_p == :-
|
29
|
+
index -= 1
|
30
|
+
else
|
31
|
+
index += 1
|
32
|
+
end
|
33
|
+
if index < 0
|
34
|
+
raise EditorError, "No previous tags for #{name}"
|
35
|
+
end
|
36
|
+
if index >= candidates.size
|
37
|
+
raise EditorError, "No more tags for #{name}"
|
38
|
+
end
|
39
|
+
else
|
40
|
+
buffer = Buffer.current
|
41
|
+
name = buffer.current_symbol
|
42
|
+
if name.nil?
|
43
|
+
raise EditorError, "No name found at point"
|
44
|
+
end
|
45
|
+
candidates = tags[name]
|
46
|
+
if candidates.empty?
|
47
|
+
raise EditorError, "Tag not found: #{name}"
|
48
|
+
end
|
49
|
+
CTAGS[:name] = name
|
50
|
+
CTAGS[:candidates] = candidates
|
51
|
+
index = 0
|
52
|
+
end
|
53
|
+
tag_mark_stack = CTAGS[:tag_mark_stack]
|
54
|
+
if tag_mark_stack.size == TAG_MARK_LIMIT
|
55
|
+
mark = tag_mark_stack.shift
|
56
|
+
mark.delete
|
57
|
+
end
|
58
|
+
tag_mark_stack.push(Buffer.current.new_mark)
|
59
|
+
file, addr, n = candidates[index]
|
60
|
+
find_file(file)
|
61
|
+
case addr
|
62
|
+
when /\A\d+\z/
|
63
|
+
goto_line(addr.to_i)
|
64
|
+
when %r'\A/\^(.*)\$/\z'
|
65
|
+
beginning_of_buffer
|
66
|
+
n.times do
|
67
|
+
re_search_forward("^" + Regexp.quote($1) + "$")
|
68
|
+
end
|
69
|
+
beginning_of_line
|
70
|
+
when %r'\A\?\^(.*)\$\?\z'
|
71
|
+
end_of_buffer
|
72
|
+
n.times do
|
73
|
+
re_search_backward("^" + Regexp.quote($1) + "$")
|
74
|
+
end
|
75
|
+
else
|
76
|
+
raise EditorError, "Invalid address: #{addr}"
|
77
|
+
end
|
78
|
+
CTAGS[:index] = index
|
79
|
+
Window.current.recenter_if_needed
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_tags
|
83
|
+
path = File.expand_path("tags")
|
84
|
+
mtime = File.mtime(path)
|
85
|
+
if CTAGS[:path] != path || CTAGS[:tags_mtime] != mtime
|
86
|
+
CTAGS[:path] = path
|
87
|
+
tags = Hash.new { |h, k| h[k] = [] }
|
88
|
+
File.read(path).scan(/^(.*?)\t(.*?)\t(.*?)(?:;".*)?$/) do
|
89
|
+
|name, file, addr|
|
90
|
+
n = tags[name].count { |f,| f == file } + 1
|
91
|
+
tags[name].push([file, addr, n])
|
92
|
+
end
|
93
|
+
CTAGS[:tags] = tags
|
94
|
+
CTAGS[:tags_mtime] = mtime
|
95
|
+
message("Loaded #{path}")
|
96
|
+
end
|
97
|
+
CTAGS[:tags]
|
98
|
+
end
|
99
|
+
|
100
|
+
define_command(:pop_tag_mark) do
|
101
|
+
tag_mark_stack = CTAGS[:tag_mark_stack]
|
102
|
+
if tag_mark_stack.empty?
|
103
|
+
raise EditorError, "No previous locations"
|
104
|
+
end
|
105
|
+
mark = tag_mark_stack.pop
|
106
|
+
begin
|
107
|
+
switch_to_buffer(mark.buffer)
|
108
|
+
mark.buffer.point_to_mark(mark)
|
109
|
+
ensure
|
110
|
+
mark.delete
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Textbringer
|
4
|
+
using Module.new {
|
5
|
+
refine Buffer do
|
6
|
+
def dabbrev_expand(contd)
|
7
|
+
if contd && self[:dabbrev_stem]
|
8
|
+
buffers = self[:dabbrev_buffers]
|
9
|
+
buffer = self[:dabbrev_buffer]
|
10
|
+
stem = self[:dabbrev_stem]
|
11
|
+
pos = self[:dabbrev_pos]
|
12
|
+
direction = self[:dabbrev_direction]
|
13
|
+
candidates = self[:dabbrev_candidates]
|
14
|
+
else
|
15
|
+
buffers = Buffer.to_a
|
16
|
+
buffer = buffers.pop
|
17
|
+
stem = save_excursion {
|
18
|
+
pos = point
|
19
|
+
backward_word(regexp: /[\p{Letter}\p{Number}_\-]/)
|
20
|
+
substring(point, pos)
|
21
|
+
}
|
22
|
+
if stem.empty?
|
23
|
+
raise "No possible abbreviation"
|
24
|
+
end
|
25
|
+
pos = point
|
26
|
+
direction = :backward
|
27
|
+
candidates = []
|
28
|
+
end
|
29
|
+
candidates_exclusion = candidates.empty? ? "" :
|
30
|
+
"(?!(?:" + candidates.map { |s|
|
31
|
+
Regexp.quote(s)
|
32
|
+
}.join("|") + ")\\b)"
|
33
|
+
re = /\b#{Regexp.quote(stem)}#{candidates_exclusion}
|
34
|
+
([\p{Letter}\p{Number}_\-]+)/x
|
35
|
+
candidate = nil
|
36
|
+
loop do
|
37
|
+
pos, candidate = buffer.dabbrev_search(re, pos, direction)
|
38
|
+
if pos
|
39
|
+
break
|
40
|
+
else
|
41
|
+
if direction == :backward
|
42
|
+
pos = buffer.point
|
43
|
+
direction = :forward
|
44
|
+
else
|
45
|
+
buffer = buffers.pop
|
46
|
+
if buffer
|
47
|
+
pos = buffer.point
|
48
|
+
direction = :backward
|
49
|
+
else
|
50
|
+
break
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
if !candidates.empty?
|
56
|
+
undo
|
57
|
+
end
|
58
|
+
if candidate
|
59
|
+
candidates.push(candidate)
|
60
|
+
insert(candidate)
|
61
|
+
else
|
62
|
+
self[:dabbrev_stem] = nil
|
63
|
+
raise EditorError, "No more abbreviation"
|
64
|
+
end
|
65
|
+
self[:dabbrev_buffers] = buffers
|
66
|
+
self[:dabbrev_buffer] = buffer
|
67
|
+
self[:dabbrev_stem] = stem
|
68
|
+
self[:dabbrev_pos] = pos
|
69
|
+
self[:dabbrev_direction] = direction
|
70
|
+
self[:dabbrev_candidates] = candidates
|
71
|
+
end
|
72
|
+
|
73
|
+
def dabbrev_search(re, pos, direction)
|
74
|
+
re_search_method = direction == :forward ?
|
75
|
+
:re_search_forward : :re_search_backward
|
76
|
+
save_excursion do
|
77
|
+
goto_char(pos)
|
78
|
+
if send(re_search_method, re, raise_error: false)
|
79
|
+
[point, match_string(1)]
|
80
|
+
else
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
}
|
87
|
+
|
88
|
+
module Commands
|
89
|
+
GLOBAL_MAP.define_key("\e/", :dabbrev_expand_command)
|
90
|
+
|
91
|
+
define_command(:dabbrev_expand_command) do
|
92
|
+
contd = Controller.current.last_command == :dabbrev_expand_command
|
93
|
+
Buffer.current.dabbrev_expand(contd)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/textbringer/config.rb
CHANGED
@@ -2,9 +2,11 @@
|
|
2
2
|
|
3
3
|
module Textbringer
|
4
4
|
CONFIG = {
|
5
|
-
|
5
|
+
default_file_encoding: Encoding::UTF_8,
|
6
|
+
default_file_format: :unix,
|
6
7
|
tab_width: 8,
|
7
8
|
indent_tabs_mode: false,
|
8
|
-
case_fold_search: true
|
9
|
+
case_fold_search: true,
|
10
|
+
buffer_dump_dir: File.expand_path("~/.textbringer/buffer_dump")
|
9
11
|
}
|
10
12
|
end
|
data/lib/textbringer/keymap.rb
CHANGED
@@ -85,7 +85,7 @@ module Textbringer
|
|
85
85
|
end
|
86
86
|
GLOBAL_MAP.define_key(?\t, :self_insert)
|
87
87
|
GLOBAL_MAP.define_key(?\C-q, :quoted_insert)
|
88
|
-
GLOBAL_MAP.define_key("\C- ", :
|
88
|
+
GLOBAL_MAP.define_key("\C- ", :set_mark_command)
|
89
89
|
GLOBAL_MAP.define_key("\C-x\C-x", :exchange_point_and_mark)
|
90
90
|
GLOBAL_MAP.define_key("\ew", :copy_region)
|
91
91
|
GLOBAL_MAP.define_key(?\C-w, :kill_region)
|
@@ -121,6 +121,7 @@ module Textbringer
|
|
121
121
|
GLOBAL_MAP.define_key(?\C-g, :keyboard_quit)
|
122
122
|
GLOBAL_MAP.define_key(?\C-s, :isearch_forward)
|
123
123
|
GLOBAL_MAP.define_key(?\C-r, :isearch_backward)
|
124
|
+
GLOBAL_MAP.define_key("\e%", :query_replace_regexp)
|
124
125
|
GLOBAL_MAP.define_key("\e!", :shell_execute)
|
125
126
|
GLOBAL_MAP.handle_undefined_key do |key|
|
126
127
|
if key.is_a?(String) && /[\0-\x7f]/ !~ key
|
data/lib/textbringer/mode.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Textbringer
|
4
|
-
class ProgrammingMode <
|
4
|
+
class ProgrammingMode < FundamentalMode
|
5
5
|
# abstract mode
|
6
6
|
undefine_command(:programming_mode)
|
7
7
|
|
@@ -10,6 +10,7 @@ module Textbringer
|
|
10
10
|
define_generic_command :forward_definition
|
11
11
|
define_generic_command :backward_definition
|
12
12
|
define_generic_command :compile
|
13
|
+
define_generic_command :toggle_test
|
13
14
|
|
14
15
|
PROGRAMMING_MODE_MAP = Keymap.new
|
15
16
|
PROGRAMMING_MODE_MAP.define_key("\t", :indent_line_command)
|
@@ -17,6 +18,7 @@ module Textbringer
|
|
17
18
|
PROGRAMMING_MODE_MAP.define_key("\C-c\C-n", :forward_definition_command)
|
18
19
|
PROGRAMMING_MODE_MAP.define_key("\C-c\C-p", :backward_definition_command)
|
19
20
|
PROGRAMMING_MODE_MAP.define_key("\C-c\C-c", :compile_command)
|
21
|
+
PROGRAMMING_MODE_MAP.define_key("\C-ct", :toggle_test_command)
|
20
22
|
|
21
23
|
def initialize(buffer)
|
22
24
|
super(buffer)
|
@@ -8,7 +8,8 @@ module Textbringer
|
|
8
8
|
|
9
9
|
class RubyMode < ProgrammingMode
|
10
10
|
self.file_name_pattern = /\A(?:.*\.(?:rb|ru|rake|thor)|
|
11
|
-
(?:Gem|Rake|Cap|Thor|Vagrant|Guard|Pod)file)\z/
|
11
|
+
(?:Gem|Rake|Cap|Thor|Vagrant|Guard|Pod)file)\z/ix
|
12
|
+
self.interpreter_name_pattern = /ruby/i
|
12
13
|
|
13
14
|
def initialize(buffer)
|
14
15
|
super(buffer)
|
@@ -49,11 +50,11 @@ module Textbringer
|
|
49
50
|
tokens = Ripper.lex(@buffer.to_s)
|
50
51
|
@buffer.forward_line
|
51
52
|
n.times do |i|
|
52
|
-
tokens = tokens.drop_while { |(l,
|
53
|
+
tokens = tokens.drop_while { |(l, _), e, t|
|
53
54
|
l < @buffer.current_line ||
|
54
55
|
e != :on_kw || /\A(?:class|module|def)\z/ !~ t
|
55
56
|
}
|
56
|
-
(line,
|
57
|
+
(line,), = tokens.first
|
57
58
|
if line.nil?
|
58
59
|
@buffer.end_of_buffer
|
59
60
|
break
|
@@ -70,11 +71,11 @@ module Textbringer
|
|
70
71
|
tokens = Ripper.lex(@buffer.to_s).reverse
|
71
72
|
@buffer.beginning_of_line
|
72
73
|
n.times do |i|
|
73
|
-
tokens = tokens.drop_while { |(l,
|
74
|
+
tokens = tokens.drop_while { |(l, _), e, t|
|
74
75
|
l >= @buffer.current_line ||
|
75
76
|
e != :on_kw || /\A(?:class|module|def)\z/ !~ t
|
76
77
|
}
|
77
|
-
(line,
|
78
|
+
(line,), = tokens.first
|
78
79
|
if line.nil?
|
79
80
|
@buffer.beginning_of_buffer
|
80
81
|
break
|
@@ -93,6 +94,10 @@ module Textbringer
|
|
93
94
|
backtrace_mode
|
94
95
|
end
|
95
96
|
|
97
|
+
def symbol_pattern
|
98
|
+
/[\p{Letter}\p{Number}_$@!?]/
|
99
|
+
end
|
100
|
+
|
96
101
|
private
|
97
102
|
|
98
103
|
def calculate_indentation
|
@@ -104,7 +109,7 @@ module Textbringer
|
|
104
109
|
bol_pos = @buffer.point
|
105
110
|
tokens = Ripper.lex(@buffer.substring(@buffer.point_min,
|
106
111
|
@buffer.point))
|
107
|
-
line, column, event,
|
112
|
+
line, column, event, = find_nearest_beginning_token(tokens)
|
108
113
|
if event == :on_lparen
|
109
114
|
return column + 1
|
110
115
|
end
|
@@ -188,5 +193,46 @@ module Textbringer
|
|
188
193
|
"ruby " + @buffer.file_name
|
189
194
|
end
|
190
195
|
end
|
196
|
+
|
197
|
+
def toggle_test
|
198
|
+
case @buffer.file_name
|
199
|
+
when %r'(.*)/test/(.*/)?test_(.*?)\.rb\z'
|
200
|
+
base = $1
|
201
|
+
namespace = $2
|
202
|
+
name = $3
|
203
|
+
if namespace
|
204
|
+
paths = Dir.glob("#{base}/{lib,app}/**/#{namespace}#{name}.rb")
|
205
|
+
if !paths.empty?
|
206
|
+
find_file(paths.first)
|
207
|
+
return
|
208
|
+
end
|
209
|
+
end
|
210
|
+
paths = Dir.glob("#{base}/{lib,app}/**/#{name}.rb")
|
211
|
+
if !paths.empty?
|
212
|
+
find_file(paths.first)
|
213
|
+
return
|
214
|
+
end
|
215
|
+
raise EditorError, "Test subject not found"
|
216
|
+
when %r'(.*)/(?:lib|app)/(.*/)?(.*?)\.rb\z'
|
217
|
+
base = $1
|
218
|
+
namespace = $2
|
219
|
+
name = $3
|
220
|
+
if namespace
|
221
|
+
paths = Dir.glob("#{base}/test/**/#{namespace}test_#{name}.rb")
|
222
|
+
if !paths.empty?
|
223
|
+
find_file(paths.first)
|
224
|
+
return
|
225
|
+
end
|
226
|
+
end
|
227
|
+
paths = Dir.glob("#{base}/test/**/#{name}.rb")
|
228
|
+
if !paths.empty?
|
229
|
+
find_file(paths.first)
|
230
|
+
return
|
231
|
+
end
|
232
|
+
raise EditorError, "Test not found"
|
233
|
+
else
|
234
|
+
raise EditorError, "Unknown file type"
|
235
|
+
end
|
236
|
+
end
|
191
237
|
end
|
192
238
|
end
|
data/lib/textbringer/version.rb
CHANGED
data/lib/textbringer/window.rb
CHANGED
@@ -137,6 +137,7 @@ module Textbringer
|
|
137
137
|
end
|
138
138
|
|
139
139
|
def self.redisplay
|
140
|
+
return if Window.current.has_input?
|
140
141
|
@@windows.each do |window|
|
141
142
|
window.redisplay unless window.current?
|
142
143
|
end
|
@@ -316,7 +317,6 @@ module Textbringer
|
|
316
317
|
end
|
317
318
|
|
318
319
|
def redisplay
|
319
|
-
return if has_input?
|
320
320
|
return if @buffer.nil?
|
321
321
|
redisplay_mode_line
|
322
322
|
@buffer.save_point do |saved|
|
@@ -537,13 +537,9 @@ module Textbringer
|
|
537
537
|
n.nonzero? || tw
|
538
538
|
end
|
539
539
|
|
540
|
-
def beginning_of_line_and_count(max_lines)
|
540
|
+
def beginning_of_line_and_count(max_lines, columns = @columns)
|
541
541
|
e = @buffer.point
|
542
542
|
@buffer.beginning_of_line
|
543
|
-
if e - @buffer.point < @columns
|
544
|
-
return 0
|
545
|
-
end
|
546
|
-
s = @buffer.substring(@buffer.point, e)
|
547
543
|
bols = [@buffer.point]
|
548
544
|
column = 0
|
549
545
|
while @buffer.point < e
|
@@ -555,14 +551,14 @@ module Textbringer
|
|
555
551
|
str = escape(c)
|
556
552
|
end
|
557
553
|
column += Buffer.display_width(str)
|
558
|
-
if column >
|
559
|
-
# Don't forward_char if column >
|
554
|
+
if column > columns
|
555
|
+
# Don't forward_char if column > columns
|
560
556
|
# to handle multibyte characters across the end of lines.
|
561
557
|
bols.push(@buffer.point)
|
562
558
|
column = 0
|
563
559
|
else
|
564
560
|
@buffer.forward_char
|
565
|
-
if column ==
|
561
|
+
if column == columns
|
566
562
|
bols.push(@buffer.point)
|
567
563
|
column = 0
|
568
564
|
end
|
@@ -664,8 +660,11 @@ module Textbringer
|
|
664
660
|
if @message
|
665
661
|
@window.addstr(escape(@message))
|
666
662
|
else
|
667
|
-
|
668
|
-
@
|
663
|
+
prompt = escape(@prompt)
|
664
|
+
@window.addstr(prompt)
|
665
|
+
y = x = 0
|
666
|
+
columns = @columns - Buffer.display_width(prompt)
|
667
|
+
beginning_of_line_and_count(1, columns)
|
669
668
|
while !@buffer.end_of_buffer?
|
670
669
|
if @buffer.point_at_mark?(saved)
|
671
670
|
y, x = @window.cury, @window.curx
|
@@ -675,6 +674,7 @@ module Textbringer
|
|
675
674
|
break
|
676
675
|
end
|
677
676
|
@window.addstr(escape(c))
|
677
|
+
break if @window.curx == @columns
|
678
678
|
@buffer.forward_char
|
679
679
|
end
|
680
680
|
if @buffer.point_at_mark?(saved)
|
data/textbringer.gemspec
CHANGED
@@ -29,4 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_development_dependency "simplecov"
|
30
30
|
spec.add_development_dependency "test-unit"
|
31
31
|
spec.add_development_dependency "bundler-audit"
|
32
|
+
spec.add_development_dependency "guard"
|
33
|
+
spec.add_development_dependency "guard-shell"
|
34
|
+
spec.add_development_dependency "ripper-tags"
|
32
35
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: textbringer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shugo Maeda
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-01-
|
11
|
+
date: 2017-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: curses
|
@@ -108,6 +108,48 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: guard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: guard-shell
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: ripper-tags
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
111
153
|
description: Textbringer is a member of a demon race that takes on the form of a text
|
112
154
|
editor.
|
113
155
|
email:
|
@@ -121,14 +163,18 @@ files:
|
|
121
163
|
- ".travis.yml"
|
122
164
|
- CHANGES.md
|
123
165
|
- Gemfile
|
166
|
+
- Guardfile
|
124
167
|
- LICENSE.txt
|
125
168
|
- README.md
|
126
169
|
- Rakefile
|
170
|
+
- appveyor.yml
|
127
171
|
- bin/console
|
128
172
|
- exe/textbringer
|
129
173
|
- lib/textbringer.rb
|
130
174
|
- lib/textbringer/buffer.rb
|
131
175
|
- lib/textbringer/commands.rb
|
176
|
+
- lib/textbringer/commands/ctags.rb
|
177
|
+
- lib/textbringer/commands/dabbrev.rb
|
132
178
|
- lib/textbringer/config.rb
|
133
179
|
- lib/textbringer/controller.rb
|
134
180
|
- lib/textbringer/errors.rb
|