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 +4 -4
- data/exe/txtb +1 -0
- data/lib/textbringer/buffer.rb +19 -11
- data/lib/textbringer/commands/misc.rb +19 -0
- data/lib/textbringer/commands/windows.rb +14 -0
- data/lib/textbringer/config.rb +2 -1
- data/lib/textbringer/face.rb +71 -15
- data/lib/textbringer/gamegrid.rb +2 -6
- data/lib/textbringer/highlight_context.rb +21 -0
- data/lib/textbringer/mode.rb +25 -0
- data/lib/textbringer/modes/gamegrid_mode.rb +6 -1
- data/lib/textbringer/modes/ruby_mode.rb +298 -181
- data/lib/textbringer/theme.rb +180 -0
- data/lib/textbringer/themes/catppuccin.rb +105 -0
- data/lib/textbringer/themes/github.rb +89 -0
- data/lib/textbringer/themes/gruvbox.rb +84 -0
- data/lib/textbringer/themes/molokai.rb +67 -0
- data/lib/textbringer/themes/sonokai.rb +63 -0
- data/lib/textbringer/themes/tokyonight.rb +70 -0
- data/lib/textbringer/version.rb +1 -1
- data/lib/textbringer/window.rb +28 -41
- data/lib/textbringer.rb +2 -0
- data/textbringer.gemspec +1 -0
- metadata +23 -5
- data/lib/textbringer/faces/basic.rb +0 -8
- data/lib/textbringer/faces/completion.rb +0 -4
- data/lib/textbringer/faces/dired.rb +0 -6
- data/lib/textbringer/faces/programming.rb +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 32054ff4c06164a8e49a6d82bde0d32fb2aed2e7fa1c842fa908fbb57abd5a6f
|
|
4
|
+
data.tar.gz: 7cebf42f75471bce99359f177344d0ce141cf617e9f350ac341a06253d48a4e8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a96f17982fa5b370357fc97bfd5e14617a0af7df230e5ad40071f0b751e82860ea0a01f025f8279aa6f38847d59484f159fa9244ed057f084d537c8f3c5741c7
|
|
7
|
+
data.tar.gz: 0fd05fb7d8aa16e51550e5276d549547b2023fad1b06aadc278fc41215da145a249fe868f632371811a10ab7680e4e9aa74e4db653665c733f2132a61e58456a
|
data/exe/txtb
CHANGED
data/lib/textbringer/buffer.rb
CHANGED
|
@@ -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::
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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.
|
|
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.
|
|
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.
|
|
1757
|
+
if action.file_version == @file_version
|
|
1750
1758
|
@modified = false
|
|
1751
|
-
action.
|
|
1759
|
+
action.file_version = nil
|
|
1752
1760
|
elsif !was_modified
|
|
1753
|
-
action.
|
|
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 :
|
|
1835
|
+
attr_accessor :file_version
|
|
1828
1836
|
attr_reader :location
|
|
1829
1837
|
|
|
1830
1838
|
def initialize(buffer, location)
|
|
1831
|
-
@
|
|
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
|
data/lib/textbringer/config.rb
CHANGED
data/lib/textbringer/face.rb
CHANGED
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
data/lib/textbringer/gamegrid.rb
CHANGED
|
@@ -63,9 +63,7 @@ module Textbringer
|
|
|
63
63
|
}.join("\n")
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
def
|
|
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
|
-
|
|
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
|
data/lib/textbringer/mode.rb
CHANGED
|
@@ -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]
|