textbringer 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/textbringer.svg)](https://badge.fury.io/rb/textbringer)
|
3
4
|
[![Build Status](https://travis-ci.org/shugo/textbringer.svg?branch=master)](https://travis-ci.org/shugo/textbringer)
|
5
|
+
[![Build status](https://ci.appveyor.com/api/projects/status/n20vtpfgcgii5jtc?svg=true)](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
|
[![asciicast](https://asciinema.org/a/100156.png)](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
|