textbringer 23 → 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: 0fe4d897c03de987989c81559c4c7d354f9d2afd9ec8cc7ff819c5da0a2c1bb4
4
- data.tar.gz: c687cc3726ea8d9ccfd9efcb235590f9d4d447145cb9a0a6eaeb103b133f671f
3
+ metadata.gz: 32054ff4c06164a8e49a6d82bde0d32fb2aed2e7fa1c842fa908fbb57abd5a6f
4
+ data.tar.gz: 7cebf42f75471bce99359f177344d0ce141cf617e9f350ac341a06253d48a4e8
5
5
  SHA512:
6
- metadata.gz: c1885974de9ae18238d0a49f436e3300d5971c06b7d3b16fb9c21cfc36c42b15e1beb22ee4608a80252100e202cef01d0943fb8d68a3db6ec9884773aff02093
7
- data.tar.gz: 75bdc11e89da85249a54568e8d23ecd0979934143604918b292c01f4630a59386fd2965c7a260d9fc9b05c8a5f81695ecf23e9306fcb5df04535f97d65a927b7
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
@@ -0,0 +1,31 @@
1
+ require "fileutils"
2
+
3
+ module Textbringer
4
+ module Commands
5
+ define_command(:dired, doc: "Open a directory browser.") do
6
+ |dir = read_file_name("Dired: ",
7
+ default: (Buffer.current.file_name ?
8
+ File.dirname(Buffer.current.file_name) : Dir.pwd) + "/")|
9
+ dir = File.expand_path(dir)
10
+ raise EditorError, "#{dir} is not a directory" unless File.directory?(dir)
11
+ buf_name = "*Dired: #{dir}*"
12
+ buffer = Buffer.find_or_new(buf_name, undo_limit: 0, read_only: true)
13
+ buffer[:dired_directory] = dir
14
+ buffer.apply_mode(DiredMode) unless buffer.mode.is_a?(DiredMode)
15
+ if buffer.bytesize == 0
16
+ buffer.read_only_edit do
17
+ buffer.insert(DiredMode.generate_listing(dir))
18
+ buffer.beginning_of_buffer
19
+ buffer.forward_line
20
+ until buffer.end_of_buffer?
21
+ buffer.beginning_of_line
22
+ break unless buffer.looking_at?(/^[D ] \S+\s+\d+\s+[\d-]+\s+[\d:]+\s+\.\.?\/$/)
23
+ buffer.forward_line
24
+ end
25
+ end
26
+ end
27
+ switch_to_buffer(buffer)
28
+ dired_move_to_filename_command
29
+ end
30
+ end
31
+ end
@@ -5,6 +5,10 @@ module Textbringer
5
5
  module Commands
6
6
  define_command(:find_file, doc: "Open or create a file.") do
7
7
  |file_name = read_file_name("Find file: ", default: (Buffer.current.file_name ? File.dirname(Buffer.current.file_name) : Dir.pwd) + "/")|
8
+ if File.directory?(file_name)
9
+ dired(file_name)
10
+ next
11
+ end
8
12
  config = EditorConfig.load_file(file_name)
9
13
  buffer = Buffer.find_file(file_name)
10
14
  if buffer.new_file?
@@ -0,0 +1,25 @@
1
+ module Textbringer
2
+ module Commands
3
+ define_command(:gamegrid_show_scores,
4
+ doc: "Display high scores for a game.") do
5
+ |game_name = read_from_minibuffer("Game name: ")|
6
+ scores = Gamegrid.load_scores(game_name)
7
+ buffer = Buffer.find_or_new("*Scores*", undo_limit: 0)
8
+ buffer.read_only_edit do
9
+ buffer.clear
10
+ buffer.insert("High Scores for #{game_name}\n")
11
+ buffer.insert("=" * 40 + "\n\n")
12
+ if scores.empty?
13
+ buffer.insert("No scores recorded.\n")
14
+ else
15
+ scores.each_with_index do |entry, i|
16
+ buffer.insert(
17
+ "#{i + 1}. #{entry[:score]} #{entry[:player]} #{entry[:time]}\n"
18
+ )
19
+ end
20
+ end
21
+ end
22
+ switch_to_buffer(buffer)
23
+ end
24
+ end
25
+ 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
@@ -0,0 +1,10 @@
1
+ module Textbringer
2
+ module Commands
3
+ define_command(:tetris, doc: "Play Tetris.") do
4
+ buffer = Buffer.find_or_new("*Tetris*", undo_limit: 0)
5
+ buffer.apply_mode(TetrisMode) unless buffer.mode.is_a?(TetrisMode)
6
+ switch_to_buffer(buffer)
7
+ buffer.mode.tetris_new_game
8
+ end
9
+ end
10
+ 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
@@ -0,0 +1,23 @@
1
+ module Textbringer
2
+ # Foreground color faces for gamegrid
3
+ Face.define :gamegrid_red, foreground: "red"
4
+ Face.define :gamegrid_green, foreground: "green"
5
+ Face.define :gamegrid_blue, foreground: "blue"
6
+ Face.define :gamegrid_yellow, foreground: "yellow"
7
+ Face.define :gamegrid_cyan, foreground: "cyan"
8
+ Face.define :gamegrid_magenta, foreground: "magenta"
9
+ Face.define :gamegrid_white, foreground: "white"
10
+
11
+ # Block faces (solid background) for Tetris-style solid blocks
12
+ Face.define :gamegrid_block_red, background: "red", foreground: "red"
13
+ Face.define :gamegrid_block_green, background: "green", foreground: "green"
14
+ Face.define :gamegrid_block_blue, background: "blue", foreground: "blue"
15
+ Face.define :gamegrid_block_yellow, background: "yellow", foreground: "yellow"
16
+ Face.define :gamegrid_block_cyan, background: "cyan", foreground: "cyan"
17
+ Face.define :gamegrid_block_magenta, background: "magenta", foreground: "magenta"
18
+ Face.define :gamegrid_block_white, background: "white", foreground: "white"
19
+
20
+ # Utility faces
21
+ Face.define :gamegrid_border, foreground: "white", bold: true
22
+ Face.define :gamegrid_score, foreground: "yellow", bold: true
23
+ end
@@ -0,0 +1,160 @@
1
+ require "fileutils"
2
+ require "time"
3
+
4
+ module Textbringer
5
+ class Gamegrid
6
+ attr_reader :width, :height
7
+ attr_accessor :score
8
+
9
+ def initialize(width, height, margin_left: 0)
10
+ @width = width
11
+ @height = height
12
+ @margin_left = margin_left
13
+ @grid = Array.new(height) { Array.new(width, 0) }
14
+ @faces = Array.new(height) { Array.new(width, nil) }
15
+ @display_options = {}
16
+ @score = 0
17
+ @timer_thread = nil
18
+ end
19
+
20
+ # Cell API
21
+
22
+ def set_cell(x, y, value)
23
+ check_bounds(x, y)
24
+ @grid[y][x] = value
25
+ end
26
+
27
+ def get_cell(x, y)
28
+ check_bounds(x, y)
29
+ @grid[y][x]
30
+ end
31
+
32
+ def set_face(x, y, face_name)
33
+ check_bounds(x, y)
34
+ @faces[y][x] = face_name
35
+ end
36
+
37
+ def get_face(x, y)
38
+ check_bounds(x, y)
39
+ @faces[y][x]
40
+ end
41
+
42
+ def set_display_option(value, char:, face: nil)
43
+ @display_options[value] = { char: char, face: face }
44
+ end
45
+
46
+ def fill(value)
47
+ @height.times do |y|
48
+ @width.times do |x|
49
+ @grid[y][x] = value
50
+ @faces[y][x] = nil
51
+ end
52
+ end
53
+ end
54
+
55
+ # Rendering
56
+
57
+ def render
58
+ margin = " " * @margin_left
59
+ @height.times.map { |y|
60
+ margin + @width.times.map { |x|
61
+ cell_char(@grid[y][x])
62
+ }.join
63
+ }.join("\n")
64
+ end
65
+
66
+ def apply_highlights(ctx)
67
+ offset = 0
68
+ @height.times do |y|
69
+ offset += @margin_left
70
+ @width.times do |x|
71
+ value = @grid[y][x]
72
+ # Priority: explicit set_face > display_option face > nil
73
+ face_name = @faces[y][x]
74
+ if face_name.nil?
75
+ opt = @display_options[value]
76
+ face_name = opt[:face] if opt
77
+ end
78
+ if face_name
79
+ face = Face[face_name]
80
+ if face
81
+ char_len = cell_char(value).bytesize
82
+ ctx.highlight(offset, offset + char_len, face)
83
+ end
84
+ end
85
+ offset += cell_char(value).bytesize
86
+ end
87
+ offset += 1 # newline
88
+ end
89
+ end
90
+
91
+ # Timer
92
+
93
+ def start_timer(interval, &callback)
94
+ stop_timer
95
+ @timer_thread = Thread.new do
96
+ loop do
97
+ sleep(interval)
98
+ Controller.current.next_tick(&callback)
99
+ rescue ThreadError
100
+ break
101
+ end
102
+ end
103
+ end
104
+
105
+ def stop_timer
106
+ if @timer_thread
107
+ @timer_thread.kill
108
+ @timer_thread = nil
109
+ end
110
+ end
111
+
112
+ def timer_active?
113
+ !@timer_thread.nil? && @timer_thread.alive?
114
+ end
115
+
116
+ # Score persistence
117
+
118
+ def self.score_file_path(game_name)
119
+ safe_name = File.basename(game_name).gsub(/[^A-Za-z0-9_\-]/, "_")
120
+ File.expand_path("~/.textbringer/scores/#{safe_name}.scores")
121
+ end
122
+
123
+ def self.add_score(game_name, score, player_name: "anonymous")
124
+ path = score_file_path(game_name)
125
+ FileUtils.mkdir_p(File.dirname(path))
126
+ File.open(path, "a") do |f|
127
+ f.puts("#{score}\t#{player_name}\t#{Time.now.iso8601}")
128
+ end
129
+ end
130
+
131
+ def self.load_scores(game_name, limit: 10)
132
+ path = score_file_path(game_name)
133
+ return [] unless File.exist?(path)
134
+ lines = File.readlines(path, chomp: true)
135
+ lines.map { |line|
136
+ parts = line.split("\t")
137
+ { score: parts[0].to_i, player: parts[1], time: parts[2] }
138
+ }.sort_by { |h| -h[:score] }.first(limit)
139
+ end
140
+
141
+ private
142
+
143
+ def check_bounds(x, y)
144
+ if x < 0 || x >= @width || y < 0 || y >= @height
145
+ raise ArgumentError, "coordinates (#{x}, #{y}) out of bounds"
146
+ end
147
+ end
148
+
149
+ def cell_char(value)
150
+ opt = @display_options[value]
151
+ if opt
152
+ opt[:char]
153
+ elsif value.is_a?(String)
154
+ value
155
+ else
156
+ " "
157
+ end
158
+ end
159
+ end
160
+ end
@@ -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
@@ -177,6 +177,7 @@ module Textbringer
177
177
  GLOBAL_MAP.define_key("\C-z", :suspend_textbringer)
178
178
  GLOBAL_MAP.define_key("\C-x\C-f", :find_file)
179
179
  GLOBAL_MAP.define_key("\C-x\C-r", :find_file_read_only)
180
+ GLOBAL_MAP.define_key("\C-xd", :dired)
180
181
  GLOBAL_MAP.define_key("\C-x\C-v", :find_alternate_file)
181
182
  GLOBAL_MAP.define_key("\C-xb", :switch_to_buffer)
182
183
  GLOBAL_MAP.define_key("\C-x\C-b", :list_buffers)
@@ -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