textbringer 24 → 25

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
  SHA256:
3
- metadata.gz: d7426dda13275f37439bdcc360a55971b34cc4b62916eeaee734b127ab9a8a88
4
- data.tar.gz: fcc6391ac0697659a8b85005766186502731e8473928a5e5165d7c0a97d533f1
3
+ metadata.gz: 32054ff4c06164a8e49a6d82bde0d32fb2aed2e7fa1c842fa908fbb57abd5a6f
4
+ data.tar.gz: 7cebf42f75471bce99359f177344d0ce141cf617e9f350ac341a06253d48a4e8
5
5
  SHA512:
6
- metadata.gz: be219b93c87935b98780c5d41430dd3d0760b839e20977dc843fa88aa8253614fb0e68dc7f90e37c08de6a23722daec928294f9702114908707011c33c766dc6
7
- data.tar.gz: 6f61d4ed9ce65308bf150f24c886e05a1743af8bd38913c80f750db77c64a90b543ae95f60774b9d8ae52953cfa91e5f36e62665e0830b9aab6f6d97925ab289
6
+ metadata.gz: a96f17982fa5b370357fc97bfd5e14617a0af7df230e5ad40071f0b751e82860ea0a01f025f8279aa6f38847d59484f159fa9244ed057f084d537c8f3c5741c7
7
+ data.tar.gz: 0fd05fb7d8aa16e51550e5276d549547b2023fad1b06aadc278fc41215da145a249fe868f632371811a10ab7680e4e9aa74e4db653665c733f2132a61e58456a
data/exe/txtb CHANGED
@@ -33,6 +33,7 @@ begin
33
33
  transient_mark_mode(true)
34
34
  Window.echo_area.clear_message
35
35
  Plugin.load_plugins
36
+ Theme.load_default if Window.has_colors?
36
37
  load_user_config("~/.textbringer.rb")
37
38
  ruby_mode
38
39
  if ARGV.size > 0
@@ -9,7 +9,8 @@ module Textbringer
9
9
  extend Enumerable
10
10
 
11
11
  attr_accessor :mode, :keymap
12
- attr_reader :name, :file_name, :file_encoding, :file_format, :point, :marks
12
+ attr_reader :name, :file_name, :file_encoding, :file_format, :point, :marks,
13
+ :file_version, :version
13
14
  attr_reader :current_line, :current_column, :visible_mark, :isearch_mark, :mark_active
14
15
  attr_reader :last_match
15
16
  attr_reader :input_method
@@ -31,7 +32,7 @@ module Textbringer
31
32
  @@auto_detect_encodings = [
32
33
  Encoding::UTF_8,
33
34
  Encoding::EUC_JP,
34
- Encoding::Windows_31J
35
+ Encoding::WINDOWS_31J
35
36
  ]
36
37
 
37
38
  DEFAULT_DETECT_ENCODING = ->(s) {
@@ -225,6 +226,7 @@ module Textbringer
225
226
  file_encoding: CONFIG[:default_file_encoding],
226
227
  file_mtime: nil, new_file: true, undo_limit: UNDO_LIMIT,
227
228
  read_only: false)
229
+ @version = 0
228
230
  set_contents(s, file_encoding)
229
231
  @name = name
230
232
  @file_name = file_name
@@ -247,7 +249,7 @@ module Textbringer
247
249
  @undoing = false
248
250
  @composite_edit_level = 0
249
251
  @composite_edit_actions = []
250
- @version = 0
252
+ @file_version = 0
251
253
  @modified = false
252
254
  @mode = FundamentalMode.new(self)
253
255
  @minor_modes = []
@@ -454,7 +456,7 @@ module Textbringer
454
456
  if file_name != @file_name
455
457
  self.file_name = file_name
456
458
  end
457
- @version += 1
459
+ @file_version += 1
458
460
  @modified = false
459
461
  @new_file = false
460
462
  @read_only = false
@@ -605,6 +607,7 @@ module Textbringer
605
607
  end
606
608
  end
607
609
  @point = @gap_start += size
610
+ @version += 1
608
611
  update_line_and_column(pos, @point)
609
612
  unless @undoing
610
613
  if merge_undo && @undo_stack.last.is_a?(InsertAction)
@@ -645,6 +648,7 @@ module Textbringer
645
648
  s = @point
646
649
  pos = get_pos(@point, n)
647
650
  if n > 0
651
+ @version += 1
648
652
  str = substring(s, pos)
649
653
  # fill the gap with NUL to avoid invalid byte sequence in UTF-8
650
654
  @contents.bytesplice(@gap_end...user_to_gap(pos), "\0" * (pos - @point))
@@ -660,6 +664,7 @@ module Textbringer
660
664
  self.modified = true
661
665
  Utils.run_hooks(:after_change_functions, s, s, str) unless @undoing || !current?
662
666
  elsif n < 0
667
+ @version += 1
663
668
  str = substring(pos, s)
664
669
  update_line_and_column(@point, pos)
665
670
  # fill the gap with NUL to avoid invalid byte sequence in UTF-8
@@ -1014,6 +1019,7 @@ module Textbringer
1014
1019
  update_line_and_column(old_pos, s)
1015
1020
  save_point do
1016
1021
  str = substring(s, e)
1022
+ @version += 1
1017
1023
  @point = s
1018
1024
  adjust_gap
1019
1025
  len = e - s
@@ -1052,6 +1058,7 @@ module Textbringer
1052
1058
 
1053
1059
  def clear
1054
1060
  check_read_only_flag
1061
+ @version += 1
1055
1062
  @contents = String.new
1056
1063
  @point = @gap_start = @gap_end = 0
1057
1064
  @marks.each do |m|
@@ -1309,7 +1316,7 @@ module Textbringer
1309
1316
  @composite_edit_actions.each do |i|
1310
1317
  action.add_action(i)
1311
1318
  end
1312
- action.version = @composite_edit_actions.first.version
1319
+ action.file_version = @composite_edit_actions.first.file_version
1313
1320
  push_undo(action)
1314
1321
  @composite_edit_actions.clear
1315
1322
  end
@@ -1516,6 +1523,7 @@ module Textbringer
1516
1523
  private
1517
1524
 
1518
1525
  def set_contents(s, enc)
1526
+ @version += 1
1519
1527
  case s.encoding
1520
1528
  when Encoding::UTF_8, Encoding::ASCII_8BIT
1521
1529
  @contents = +s
@@ -1723,7 +1731,7 @@ module Textbringer
1723
1731
  def push_undo(action)
1724
1732
  return if @undoing || @undo_limit == 0
1725
1733
  if !modified?
1726
- action.version = @version
1734
+ action.file_version = @file_version
1727
1735
  end
1728
1736
  if @composite_edit_level > 0
1729
1737
  @composite_edit_actions.push(action)
@@ -1746,11 +1754,11 @@ module Textbringer
1746
1754
  begin
1747
1755
  was_modified = @modified
1748
1756
  action.send(op)
1749
- if action.version == @version
1757
+ if action.file_version == @file_version
1750
1758
  @modified = false
1751
- action.version = nil
1759
+ action.file_version = nil
1752
1760
  elsif !was_modified
1753
- action.version = @version
1761
+ action.file_version = @file_version
1754
1762
  end
1755
1763
  to_stack.push(action)
1756
1764
  ensure
@@ -1824,11 +1832,11 @@ module Textbringer
1824
1832
  KILL_RING = Ring.new
1825
1833
 
1826
1834
  class UndoableAction
1827
- attr_accessor :version
1835
+ attr_accessor :file_version
1828
1836
  attr_reader :location
1829
1837
 
1830
1838
  def initialize(buffer, location)
1831
- @version = nil
1839
+ @file_version = nil
1832
1840
  @buffer = buffer
1833
1841
  @location = location
1834
1842
  end
@@ -376,5 +376,24 @@ module Textbringer
376
376
  describe_char
377
377
  end
378
378
  end
379
+ define_command(:load_theme,
380
+ doc: "Load and activate a theme by name.") do
381
+ |name = read_theme_name("Load theme: ")|
382
+ Theme.load(name)
383
+ Window.redisplay
384
+ message("Loaded theme: #{name}")
385
+ end
386
+
387
+ def read_theme_name(prompt)
388
+ builtin = Dir.glob(
389
+ File.expand_path("../../themes/*.rb", __FILE__)
390
+ ).map { |f| File.basename(f, ".rb") }
391
+ user = Dir.glob(
392
+ File.expand_path("~/.textbringer/themes/*.rb")
393
+ ).map { |f| File.basename(f, ".rb") }
394
+ names = (builtin + user).uniq.sort
395
+ f = ->(s) { complete_for_minibuffer(s, names) }
396
+ read_from_minibuffer(prompt, completion_proc: f)
397
+ end
379
398
  end
380
399
  end
@@ -84,6 +84,20 @@ module Textbringer
84
84
  end
85
85
  end
86
86
 
87
+ define_command(:set_foreground_color, doc: <<~EOD) do
88
+ Set the default foreground color.
89
+ EOD
90
+ |color = read_from_minibuffer("Foreground color: ")|
91
+ Window.set_default_colors(color, nil)
92
+ end
93
+
94
+ define_command(:set_background_color, doc: <<~EOD) do
95
+ Set the default background color.
96
+ EOD
97
+ |color = read_from_minibuffer("Background color: ")|
98
+ Window.set_default_colors(nil, color)
99
+ end
100
+
87
101
  define_command(:list_buffers, doc: <<~EOD) do |buffers = Buffer.list|
88
102
  List the existing buffers.
89
103
  EOD
@@ -18,6 +18,7 @@ module Textbringer
18
18
  ispell_command: "aspell -a",
19
19
  fill_column: 70,
20
20
  read_file_name_completion_ignore_case: RUBY_PLATFORM.match?(/darwin/),
21
- default_input_method: "t_code"
21
+ default_input_method: "t_code",
22
+ background_mode: nil
22
23
  }
23
24
  end
@@ -6,6 +6,7 @@ module Textbringer
6
6
 
7
7
  @@face_table = {}
8
8
  @@next_color_pair = 1
9
+ @@color_pair_cache = {}
9
10
 
10
11
  def self.[](name)
11
12
  @@face_table[name]
@@ -17,6 +18,8 @@ module Textbringer
17
18
  else
18
19
  @@face_table[name] = new(name, **opts)
19
20
  end
21
+ resolve_dependents(name)
22
+ @@face_table[name]
20
23
  end
21
24
 
22
25
  def self.delete(name)
@@ -25,26 +28,79 @@ module Textbringer
25
28
 
26
29
  def initialize(name, **opts)
27
30
  @name = name
28
- @color_pair = @@next_color_pair
29
- @@next_color_pair += 1
30
31
  update(**opts)
31
32
  end
32
33
 
33
- def update(foreground: -1, background: -1,
34
- bold: false, underline: false, reverse: false)
35
- @foreground = foreground
36
- @background = background
37
- @bold = bold
38
- @underline = underline
39
- @reverse = reverse
40
- Curses.init_pair(@color_pair,
41
- Color[foreground], Color[background])
34
+ UNSET = Object.new.freeze
35
+ private_constant :UNSET
36
+
37
+ def update(foreground: nil, background: nil,
38
+ bold: nil, underline: nil, reverse: nil,
39
+ inherit: UNSET)
40
+ unless inherit.equal?(UNSET)
41
+ if inherit && !inherit.is_a?(Symbol)
42
+ raise EditorError,
43
+ "Face inherit: must be a Symbol, got #{inherit.inspect}"
44
+ end
45
+ if inherit && cyclic_inheritance?(@name, inherit)
46
+ raise EditorError,
47
+ "Cyclic face inheritance: #{@name} inherits from #{inherit}"
48
+ end
49
+ @inherit = inherit
50
+ end
51
+ @explicit_foreground = foreground
52
+ @explicit_background = background
53
+ @explicit_bold = bold
54
+ @explicit_underline = underline
55
+ @explicit_reverse = reverse
56
+ resolve_inheritance
57
+ self
58
+ end
59
+
60
+ private
61
+
62
+ def resolve_inheritance
63
+ parent = @inherit ? self.class[@inherit] : nil
64
+ @foreground = @explicit_foreground || parent&.instance_variable_get(:@foreground) || -1
65
+ @background = @explicit_background || parent&.instance_variable_get(:@background) || -1
66
+ @bold = @explicit_bold.nil? ? (parent&.instance_variable_get(:@bold) || false) : @explicit_bold
67
+ @underline = @explicit_underline.nil? ? (parent&.instance_variable_get(:@underline) || false) : @explicit_underline
68
+ @reverse = @explicit_reverse.nil? ? (parent&.instance_variable_get(:@reverse) || false) : @explicit_reverse
69
+ fg_num = Color[@foreground]
70
+ bg_num = Color[@background]
71
+ key = [fg_num, bg_num]
72
+ unless @@color_pair_cache.key?(key)
73
+ @@color_pair_cache[key] = @@next_color_pair
74
+ Curses.init_pair(@@next_color_pair, fg_num, bg_num)
75
+ @@next_color_pair += 1
76
+ end
77
+ @color_pair = @@color_pair_cache[key]
42
78
  @text_attrs = 0
43
- @text_attrs |= Curses::A_BOLD if bold
44
- @text_attrs |= Curses::A_UNDERLINE if underline
45
- @text_attrs |= Curses::A_REVERSE if reverse
79
+ @text_attrs |= Curses::A_BOLD if @bold
80
+ @text_attrs |= Curses::A_UNDERLINE if @underline
81
+ @text_attrs |= Curses::A_REVERSE if @reverse
46
82
  @attributes = Curses.color_pair(@color_pair) | @text_attrs
47
- self
48
83
  end
84
+
85
+ def cyclic_inheritance?(name, inherit)
86
+ current = inherit
87
+ while current
88
+ return true if current == name
89
+ face = self.class[current]
90
+ current = face&.instance_variable_get(:@inherit)
91
+ end
92
+ false
93
+ end
94
+
95
+ def self.resolve_dependents(name, visited = {})
96
+ visited[name] = true
97
+ @@face_table.each_value do |face|
98
+ if face.instance_variable_get(:@inherit) == name
99
+ face.send(:resolve_inheritance)
100
+ resolve_dependents(face.name, visited) unless visited.key?(face.name)
101
+ end
102
+ end
103
+ end
104
+ private_class_method :resolve_dependents
49
105
  end
50
106
  end
@@ -63,9 +63,7 @@ module Textbringer
63
63
  }.join("\n")
64
64
  end
65
65
 
66
- def face_map
67
- highlight_on = {}
68
- highlight_off = {}
66
+ def apply_highlights(ctx)
69
67
  offset = 0
70
68
  @height.times do |y|
71
69
  offset += @margin_left
@@ -80,16 +78,14 @@ module Textbringer
80
78
  if face_name
81
79
  face = Face[face_name]
82
80
  if face
83
- highlight_on[offset] = face
84
81
  char_len = cell_char(value).bytesize
85
- highlight_off[offset + char_len] = true
82
+ ctx.highlight(offset, offset + char_len, face)
86
83
  end
87
84
  end
88
85
  offset += cell_char(value).bytesize
89
86
  end
90
87
  offset += 1 # newline
91
88
  end
92
- [highlight_on, highlight_off]
93
89
  end
94
90
 
95
91
  # Timer
@@ -0,0 +1,21 @@
1
+ module Textbringer
2
+ class HighlightContext
3
+ attr_reader :buffer, :highlight_start, :highlight_end
4
+
5
+ def initialize(buffer:, highlight_start:, highlight_end:,
6
+ highlight_on:, highlight_off:)
7
+ @buffer = buffer
8
+ @highlight_start = highlight_start
9
+ @highlight_end = highlight_end
10
+ @highlight_on = highlight_on
11
+ @highlight_off = highlight_off
12
+ end
13
+
14
+ def highlight(start_offset, end_offset, face)
15
+ start_offset = @highlight_start if start_offset < @highlight_start &&
16
+ @highlight_start < end_offset
17
+ @highlight_on[start_offset] = face
18
+ @highlight_off[end_offset] = true
19
+ end
20
+ end
21
+ end
@@ -81,5 +81,30 @@ module Textbringer
81
81
  def syntax_table
82
82
  self.class.syntax_table
83
83
  end
84
+
85
+ def highlight(ctx)
86
+ syntax_table = self.class.syntax_table || DEFAULT_SYNTAX_TABLE
87
+ if ctx.buffer.bytesize < CONFIG[:highlight_buffer_size_limit]
88
+ base_pos = ctx.buffer.point_min
89
+ s = ctx.buffer.to_s
90
+ else
91
+ base_pos = ctx.highlight_start
92
+ s = ctx.buffer.substring(ctx.highlight_start,
93
+ ctx.highlight_end).scrub("")
94
+ end
95
+ return if !s.valid_encoding?
96
+ re_str = syntax_table.map { |name, re|
97
+ "(?<#{name}>#{re})"
98
+ }.join("|")
99
+ re = Regexp.new(re_str)
100
+ names = syntax_table.keys
101
+ s.scan(re) do
102
+ b = base_pos + $`.bytesize
103
+ e = b + $&.bytesize
104
+ name = names.find { |n| $~[n] }
105
+ face = Face[name]
106
+ ctx.highlight(b, e, face) if face
107
+ end
108
+ end
84
109
  end
85
110
  end
@@ -21,10 +21,15 @@ module Textbringer
21
21
  grid = Gamegrid.new(width, height, margin_left: margin_left)
22
22
  @buffer[:gamegrid] = grid
23
23
  @buffer.read_only = true
24
- @buffer[:highlight_override] = -> { grid.face_map }
25
24
  grid
26
25
  end
27
26
 
27
+ def highlight(ctx)
28
+ grid = @buffer[:gamegrid]
29
+ return unless grid
30
+ grid.apply_highlights(ctx)
31
+ end
32
+
28
33
  define_local_command(:gamegrid_refresh,
29
34
  doc: "Refresh the gamegrid display.") do
30
35
  grid = @buffer[:gamegrid]