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 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