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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 47f6bdcfb6e14bb36f75020192ade797779ea21a
4
- data.tar.gz: 3e6b0199920134c9cbf6a88997496b14e5c5184c
3
+ metadata.gz: c4c0499915d9509f0f2ef8d8b30b8a3c87955c9c
4
+ data.tar.gz: 017b6339f497de93887ee252df2b1da9b3c20bae
5
5
  SHA512:
6
- metadata.gz: e35304f6ffb881675a412b3d9b89cbe0841b6fb8092c59817391711b18115f73e6840e54f78f652347ebd2e22980f9067f45fa23407891626ea82cd08e5bbbe4
7
- data.tar.gz: 3d30200f3833a1686a9ef6f33efec6e613bb247c4b446cae42fe3f31bc59e4c698431ce43f1a0e033303bd881e63df002f97845f7c4c938b263f94da9b8a1723
6
+ metadata.gz: 813608d1b0cd0f5a120256a49f5509a75f0d4c8a05c1f803e3b6625f8161f3a249a944fc447e16dc50eb9fb9754f2e8b6ab2d55bca0df4ef9a45887151d04637
7
+ data.tar.gz: a3b1267f21be52ef1e9136cc530cf7b0f0cd158d636940d0143980b79a54ef96cefbb354aae2e9eb877942097ee90044e2e8d4991cbfb55671c5f049fc8bb365
data/CHANGES.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.1.4
2
+
3
+ * Add dabbrev_expand (M-/).
4
+ * Add find_tag (M-.) and pop_tag_mark (M-*).
5
+ * Add toggle_test_command (C-c t).
6
+ * Force binmode for Windows.
7
+
1
8
  ## 0.1.3
2
9
 
3
10
  * Fix an infinite loop bug on Windows.
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
@@ -6,4 +6,5 @@ Rake::TestTask.new do |t|
6
6
  t.libs << "test"
7
7
  t.test_files = FileList["test/**/test_*.rb"]
8
8
  t.verbose = true
9
+ t.warning = true
9
10
  end
data/appveyor.yml ADDED
@@ -0,0 +1,13 @@
1
+ ---
2
+ clone_depth: 10
3
+ install:
4
+ - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
5
+ - gem install bundler --no-document
6
+ - bundle install
7
+ build: off
8
+ test_script:
9
+ - rake
10
+ deploy: off
11
+ environment:
12
+ matrix:
13
+ - ruby_version: "23-x64"
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"
@@ -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 { |buffer|
126
- buffer.file_name == file_name
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
- @@table.each_value(&block)
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, file_encoding: Encoding::UTF_8,
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 = :unix
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 = nil
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) { |f|
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", external_encoding: @file_encoding) do |f|
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? && /\p{Letter}|\p{Number}/ !~ char_after
571
+ while !end_of_buffer? && regexp !~ char_after
555
572
  forward_char
556
573
  end
557
- while !end_of_buffer? && /\p{Letter}|\p{Number}/ =~ char_after
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? && /\p{Letter}|\p{Number}/ !~ char_after
584
+ while !beginning_of_buffer? && regexp !~ char_after
568
585
  backward_char
569
586
  end
570
- while !beginning_of_buffer? && /\p{Letter}|\p{Number}/ =~ char_after
587
+ while !beginning_of_buffer? && regexp =~ char_after
571
588
  backward_char
572
589
  end
573
- if /\p{Letter}|\p{Number}/ !~ char_after
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
- raise SearchError, "Search failed"
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
- raise SearchError, "Search failed"
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
- raise SearchError, "Search failed"
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.write(path, to_s)
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.write(path + ".metadata", metadata.to_json)
1135
+ File.binwrite(path + ".metadata", metadata.to_json)
1107
1136
  end
1108
1137
 
1109
1138
  def self.load(path)
1110
- buffer = Buffer.new(File.read(path))
1111
- metadata = JSON.parse(File.read(path + ".metadata"))
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)
@@ -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("Query replace regexp #{regexp} with: ")|
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
- mode = Mode.list.find { |mode|
259
- mode.file_name_pattern &&
260
- mode.file_name_pattern =~ File.basename(buffer.file_name)
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
- Open3.popen2e(cmd, pgroup: true) do |input, output, wait_thread|
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
@@ -2,9 +2,11 @@
2
2
 
3
3
  module Textbringer
4
4
  CONFIG = {
5
- buffer_dump_dir: File.expand_path("~/.textbringer/buffer_dump"),
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
@@ -58,7 +58,7 @@ module Textbringer
58
58
  end
59
59
  else
60
60
  if cmd.nil?
61
- keys = @key_sequence.map { |c| key_name(c) }.join(" ")
61
+ keys = @key_sequence.map { |ch| key_name(ch) }.join(" ")
62
62
  @key_sequence.clear
63
63
  Window.echo_area.show("#{keys} is undefined")
64
64
  end
@@ -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- ", :set_mark)
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
@@ -16,6 +16,7 @@ module Textbringer
16
16
  attr_accessor :command_name
17
17
  attr_accessor :hook_name
18
18
  attr_accessor :file_name_pattern
19
+ attr_accessor :interpreter_name_pattern
19
20
  end
20
21
 
21
22
  def self.define_generic_command(name)
@@ -2,5 +2,8 @@
2
2
 
3
3
  module Textbringer
4
4
  class FundamentalMode < Mode
5
+ def symbol_pattern
6
+ /[\p{Letter}\p{Number}_]/
7
+ end
5
8
  end
6
9
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Textbringer
4
- class ProgrammingMode < Mode
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/x
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, c), e, t|
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, column), event, text = tokens.first
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, c), e, t|
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, column), event, text = tokens.first
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, text = find_nearest_beginning_token(tokens)
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
@@ -1,3 +1,3 @@
1
1
  module Textbringer
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -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 > @columns
559
- # Don't forward_char if column > @window.columns
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 == @columns
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
- @window.addstr(escape(@prompt))
668
- @buffer.beginning_of_line
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.3
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-27 00:00:00.000000000 Z
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