textbringer 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Textbringer
4
+ class HelpMode < Mode
5
+ define_generic_command :jump_to_link
6
+
7
+ HELP_MODE_MAP = Keymap.new
8
+ HELP_MODE_MAP.define_key(?\C-m, :jump_to_link_command)
9
+ HELP_MODE_MAP.define_key(?l, :help_go_back)
10
+ HELP_MODE_MAP.define_key("\C-c\C-b", :help_go_back)
11
+ HELP_MODE_MAP.define_key(?r, :help_go_forward)
12
+ HELP_MODE_MAP.define_key("\C-c\C-f", :help_go_forward)
13
+
14
+ define_syntax :link, /
15
+ (?: ^\S*?:\d+$ ) |
16
+ (?: \[[_a-zA-Z][_a-zA-Z0-9]*\] )
17
+ /x
18
+
19
+ def initialize(buffer)
20
+ super(buffer)
21
+ buffer.keymap = HELP_MODE_MAP
22
+ end
23
+
24
+ def jump_to_link
25
+ @buffer.save_excursion do
26
+ @buffer.skip_re_backward(/[_a-zA-Z0-9]/)
27
+ if @buffer.char_before == ?[ &&
28
+ @buffer.looking_at?(/([_a-zA-Z][_a-zA-Z0-9]*)\]/)
29
+ describe_command(match_string(1))
30
+ else
31
+ @buffer.beginning_of_line
32
+ if @buffer.looking_at?(/^(\S*?):(\d+)$/)
33
+ file_name = @buffer.match_string(1)
34
+ line_number = @buffer.match_string(2).to_i
35
+ find_file(file_name)
36
+ goto_line(line_number)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -6,7 +6,8 @@ module Textbringer
6
6
  undefine_command(:programming_mode)
7
7
 
8
8
  define_generic_command :indent_line
9
- define_generic_command :newline_and_reindent
9
+ define_generic_command :reindent_then_newline_and_indent
10
+ define_generic_command :indent_region
10
11
  define_generic_command :forward_definition
11
12
  define_generic_command :backward_definition
12
13
  define_generic_command :compile
@@ -14,7 +15,9 @@ module Textbringer
14
15
 
15
16
  PROGRAMMING_MODE_MAP = Keymap.new
16
17
  PROGRAMMING_MODE_MAP.define_key("\t", :indent_line_command)
17
- PROGRAMMING_MODE_MAP.define_key("\C-m", :newline_and_reindent_command)
18
+ PROGRAMMING_MODE_MAP.define_key("\C-m",
19
+ :reindent_then_newline_and_indent_command)
20
+ PROGRAMMING_MODE_MAP.define_key("\e\C-\\", :indent_region_command)
18
21
  PROGRAMMING_MODE_MAP.define_key("\C-c\C-n", :forward_definition_command)
19
22
  PROGRAMMING_MODE_MAP.define_key("\C-c\C-p", :backward_definition_command)
20
23
  PROGRAMMING_MODE_MAP.define_key("\C-c\C-c", :compile_command)
@@ -32,18 +35,16 @@ module Textbringer
32
35
  return result if level.nil?
33
36
  @buffer.save_excursion do
34
37
  @buffer.beginning_of_line
35
- has_space = @buffer.looking_at?(/[ \t]+/)
36
- if has_space
37
- s = @buffer.match_string(0)
38
- break if /\t/ !~ s && s.size == level
39
- @buffer.delete_region(@buffer.match_beginning(0),
40
- @buffer.match_end(0))
41
- else
42
- break if level == 0
43
- end
44
- @buffer.indent_to(level)
45
- if has_space
46
- @buffer.merge_undo(2)
38
+ @buffer.composite_edit do
39
+ if @buffer.looking_at?(/[ \t]+/)
40
+ s = @buffer.match_string(0)
41
+ break if /\t/ !~ s && s.size == level
42
+ @buffer.delete_region(@buffer.match_beginning(0),
43
+ @buffer.match_end(0))
44
+ else
45
+ break if level == 0
46
+ end
47
+ @buffer.indent_to(level)
47
48
  end
48
49
  result = true
49
50
  end
@@ -56,24 +57,30 @@ module Textbringer
56
57
  result
57
58
  end
58
59
 
59
- def newline_and_reindent
60
- n = 1
61
- if indent_line
62
- n += 1
60
+ def reindent_then_newline_and_indent
61
+ @buffer.composite_edit do
62
+ indent_line
63
+ @buffer.save_excursion do
64
+ pos = @buffer.point
65
+ @buffer.beginning_of_line
66
+ if /\A[ \t]+\z/ =~ @buffer.substring(@buffer.point, pos)
67
+ @buffer.delete_region(@buffer.point, pos)
68
+ end
69
+ end
70
+ @buffer.insert("\n")
71
+ indent_line
63
72
  end
73
+ end
74
+
75
+ def indent_region(s = @buffer.mark, e = @buffer.point)
76
+ s, e = Buffer.region_boundaries(s, e)
64
77
  @buffer.save_excursion do
65
- pos = @buffer.point
66
- @buffer.beginning_of_line
67
- if /\A[ \t]+\z/ =~ @buffer.substring(@buffer.point, pos)
68
- @buffer.delete_region(@buffer.point, pos)
69
- n += 1
78
+ @buffer.goto_char(s)
79
+ while @buffer.point < e
80
+ indent_line
81
+ @buffer.forward_line
70
82
  end
71
83
  end
72
- @buffer.insert("\n")
73
- if indent_line
74
- n += 1
75
- end
76
- @buffer.merge_undo(n) if n > 1
77
84
  end
78
85
 
79
86
  private
@@ -27,13 +27,24 @@ module Textbringer
27
27
  /x
28
28
 
29
29
  define_syntax :string, /
30
- (?: (?<! [a-zA-Z] ) \? (:? [^\\\s] | \\ . ) ) |
31
- (?: %[qQrwWsix]?\{ (?: [^\\}] | \\ . )* \} ) |
32
- (?: %[qQrwWsix]?\( (?: [^\\)] | \\ . )* \) ) |
33
- (?: %[qQrwWsix]?\[ (?: [^\\\]] | \\ . )* \] ) |
34
- (?: %[qQrwWsix]?< (?: [^\\>] | \\ . )* > ) |
30
+ (?: (?<! [a-zA-Z] ) \?
31
+ (:?
32
+ [^\\\s] |
33
+ \\ [0-7]{1,3} |
34
+ \\x [0-9a-fA-F]{2} |
35
+ \\u [0-9a-fA-F]{4} |
36
+ \\u \{ [0-9a-fA-F]+ \} |
37
+ \\C - . |
38
+ \\M - . |
39
+ \\ .
40
+ )
41
+ ) |
42
+ (?: %[qQrwWsiIx]?\{ (?: [^\\}] | \\ . )* \} ) |
43
+ (?: %[qQrwWsiIx]?\( (?: [^\\)] | \\ . )* \) ) |
44
+ (?: %[qQrwWsiIx]?\[ (?: [^\\\]] | \\ . )* \] ) |
45
+ (?: %[qQrwWsiIx]?< (?: [^\\>] | \\ . )* > ) |
35
46
  (?:
36
- %[qQrwWsix]?
47
+ %[qQrwWsiIx]?
37
48
  (?<string_delimiter>[^{(\[<a-zA-Z0-9\s\u{0100}-\u{10ffff}])
38
49
  (?: (?! \k<string_delimiter> ) [^\\] | \\ . )*
39
50
  \k<string_delimiter>
@@ -61,13 +72,18 @@ module Textbringer
61
72
  ) |
62
73
  (?:
63
74
  (?<! class | class \s | [\]})"'.] | :: | \w )
64
- <<-?(?<heredoc_quote>['"`]?)
75
+ <<[\-~]?(?<heredoc_quote>['"`]?)
65
76
  (?<heredoc_terminator>
66
- [_a-zA-Z\u{0100}-\u{10ffff}]
67
- [_a-zA-Z0-9\u{0100}-\u{10ffff}]*
77
+ (?> [_a-zA-Z\u{0100}-\u{10ffff}]
78
+ [_a-zA-Z0-9\u{0100}-\u{10ffff}]* )
68
79
  )
69
80
  \k<heredoc_quote>
70
- (?> (?:.|\n)*? \k<heredoc_terminator> )
81
+ (?> (?:.|\n)*? ^ [\ \t]* \k<heredoc_terminator> $ )
82
+ ) |
83
+ (?:
84
+ (?<! : ) :
85
+ [_a-zA-Z\u{0100}-\u{10ffff}]
86
+ [_a-zA-Z0-9\u{0100}-\u{10ffff}]*
71
87
  )
72
88
  /x
73
89
 
@@ -256,6 +272,8 @@ module Textbringer
256
272
  (line, column), event, text = tokens[i]
257
273
  case event
258
274
  when :on_kw
275
+ _, prev_event, _ = tokens[i - 1]
276
+ next if prev_event == :on_symbeg
259
277
  case text
260
278
  when "class", "module", "def", "if", "unless", "case",
261
279
  "do", "for", "while", "until", "begin"
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Textbringer
4
+ module Plugin
5
+ def self.load_plugins
6
+ files = Gem.find_files("textbringer_plugin.rb")
7
+ files.group_by { |file|
8
+ file.slice(/([^\/]+)-[\w.]+\/lib\/textbringer_plugin\.rb\z/, 1)
9
+ }.map { |gem, versions|
10
+ versions.sort_by { |version|
11
+ v = version.slice(/[^\/]+-([\w.]+)\/lib\/textbringer_plugin\.rb\z/,
12
+ 1)
13
+ Gem::Version.create(v)
14
+ }.last
15
+ }.each do |file|
16
+ load(file)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Textbringer
4
+ class Ring
5
+ include Enumerable
6
+
7
+ def initialize(max = 30, on_delete: ->(x) {})
8
+ @max = max
9
+ @ring = []
10
+ @current = -1
11
+ @on_delete = on_delete
12
+ end
13
+
14
+ def clear
15
+ @ring.clear
16
+ @current = -1
17
+ end
18
+
19
+ def push(obj)
20
+ @current += 1
21
+ if @ring.size < @max
22
+ @ring.insert(@current, obj)
23
+ else
24
+ if @current == @max
25
+ @current = 0
26
+ end
27
+ @on_delete.call(@ring[@current])
28
+ @ring[@current] = obj
29
+ end
30
+ end
31
+
32
+ def pop
33
+ x = @ring[@current]
34
+ rotate(1)
35
+ x
36
+ end
37
+
38
+ def current
39
+ if @ring.empty?
40
+ raise EditorError, "Ring is empty"
41
+ end
42
+ @ring[@current]
43
+ end
44
+
45
+ def rotate(n)
46
+ @current = get_index(n)
47
+ @ring[@current]
48
+ end
49
+
50
+ def [](n = 0)
51
+ @ring[get_index(n)]
52
+ end
53
+
54
+ def empty?
55
+ @ring.empty?
56
+ end
57
+
58
+ def size
59
+ @ring.size
60
+ end
61
+
62
+ def each(&block)
63
+ @ring.each(&block)
64
+ end
65
+
66
+ def to_a
67
+ @ring.to_a
68
+ end
69
+
70
+ private
71
+
72
+ def get_index(n)
73
+ if @ring.empty?
74
+ raise EditorError, "Ring is empty"
75
+ end
76
+ i = @current - n
77
+ if 0 <= i && i < @ring.size
78
+ i
79
+ else
80
+ i % @ring.size
81
+ end
82
+ end
83
+ end
84
+ end
@@ -231,6 +231,34 @@ module Textbringer
231
231
  read_from_minibuffer(prompt + " (#{char_options}) ", keymap: map)
232
232
  end
233
233
 
234
+ def read_key_sequence(prompt)
235
+ buffer = Buffer.current
236
+ key_sequence = []
237
+ map = Keymap.new
238
+ map.define_key("\C-g", :abort_recursive_edit)
239
+ map.handle_undefined_key do |key|
240
+ -> {
241
+ key_sequence.push(key)
242
+ cmd = buffer.keymap&.lookup(key_sequence) ||
243
+ GLOBAL_MAP.lookup(key_sequence)
244
+ if !cmd.is_a?(Keymap)
245
+ exit_recursive_edit
246
+ end
247
+ Buffer.current.clear
248
+ keys = Keymap.key_sequence_string(key_sequence)
249
+ Buffer.current.insert("#{keys}-")
250
+ }
251
+ end
252
+ read_from_minibuffer(prompt, keymap: map)
253
+ if buffer.keymap&.lookup(key_sequence) ||
254
+ GLOBAL_MAP.lookup(key_sequence)
255
+ key_sequence
256
+ else
257
+ keys = Keymap.key_sequence_string(key_sequence)
258
+ raise EditorError, "#{keys} is undefined"
259
+ end
260
+ end
261
+
234
262
  HOOKS = Hash.new { |h, k| h[k] = [] }
235
263
 
236
264
  def add_hook(name, func)
@@ -1,3 +1,3 @@
1
1
  module Textbringer
2
- VERSION = "0.1.8"
2
+ VERSION = "0.1.9"
3
3
  end
@@ -184,6 +184,7 @@ module Textbringer
184
184
  end
185
185
 
186
186
  def self.redisplay
187
+ return if Controller.current.executing_keyboard_macro?
187
188
  return if Window.current.has_input?
188
189
  @@windows.each do |window|
189
190
  window.redisplay unless window.current?
@@ -615,6 +616,25 @@ module Textbringer
615
616
  end
616
617
  end
617
618
 
619
+ def shrink(n)
620
+ enlarge(-n)
621
+ end
622
+
623
+ def shrink_if_larger_than_buffer
624
+ @buffer.save_point do
625
+ @buffer.end_of_buffer
626
+ @buffer.skip_re_backward(/\s/)
627
+ count = beginning_of_line_and_count(Window.lines) + 1
628
+ while !@buffer.beginning_of_buffer?
629
+ @buffer.backward_char
630
+ count += beginning_of_line_and_count(Window.lines) + 1
631
+ end
632
+ if lines - 1 > count
633
+ shrink(lines - 1 - count)
634
+ end
635
+ end
636
+ end
637
+
618
638
  private
619
639
 
620
640
  def initialize_window(num_lines, num_columns, y, x)
@@ -802,6 +822,7 @@ module Textbringer
802
822
 
803
823
  def clear
804
824
  @buffer.clear
825
+ @top_of_window.location = @buffer.point_min
805
826
  @message = nil
806
827
  @prompt = ""
807
828
  end
@@ -824,19 +845,25 @@ module Textbringer
824
845
  else
825
846
  prompt = escape(@prompt)
826
847
  @window.addstr(prompt)
848
+ framer
849
+ @buffer.point_to_mark(@top_of_window)
827
850
  y = x = 0
828
- columns = @columns - Buffer.display_width(prompt)
829
- beginning_of_line_and_count(1, columns)
830
851
  while !@buffer.end_of_buffer?
852
+ cury, curx = @window.cury, @window.curx
831
853
  if @buffer.point_at_mark?(saved)
832
- y, x = @window.cury, @window.curx
854
+ y, x = cury, curx
833
855
  end
834
856
  c = @buffer.char_after
835
857
  if c == "\n"
836
858
  break
837
859
  end
838
- @window.addstr(escape(c))
839
- break if @window.curx == @columns
860
+ s = escape(c)
861
+ newx = curx + Buffer.display_width(s)
862
+ if newx > @columns
863
+ break
864
+ end
865
+ @window.addstr(s)
866
+ break if newx >= @columns
840
867
  @buffer.forward_char
841
868
  end
842
869
  if @buffer.point_at_mark?(saved)
@@ -869,5 +896,32 @@ module Textbringer
869
896
  def initialize_window(num_lines, num_columns, y, x)
870
897
  @window = Curses::Window.new(num_lines, num_columns, y, x)
871
898
  end
899
+
900
+ def escape(s)
901
+ super(s).gsub(/\t/, "^I")
902
+ end
903
+
904
+ def framer
905
+ @buffer.save_point do |saved|
906
+ max_width = @columns - @window.curx
907
+ width = 0
908
+ loop do
909
+ c = @buffer.char_after
910
+ if c.nil?
911
+ width += 1
912
+ else
913
+ width += Buffer.display_width(escape(c))
914
+ end
915
+ if width > max_width
916
+ @buffer.forward_char
917
+ break
918
+ elsif width == max_width || @buffer.beginning_of_line?
919
+ break
920
+ end
921
+ @buffer.backward_char
922
+ end
923
+ @top_of_window.location = @buffer.point
924
+ end
925
+ end
872
926
  end
873
927
  end