textbringer 26 → 28

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: e2675c1e38b9c3e24666f87ebf58f4cede5bca7dfec811eec58cee700a500f33
4
- data.tar.gz: b1835247ba5c703983dea5b80a84fb26b0f43954ee1cb9b737ac82e23a843278
3
+ metadata.gz: a93c844e10f5ad02a288323d8090d5dedea44b7f6050b054f9adc100e2bac129
4
+ data.tar.gz: 6a54096a278fba823491914ea9b184f1c640d64f5234192dabf64afad7ad3ce1
5
5
  SHA512:
6
- metadata.gz: 7bb45438e1cf3912540446ceeed8dcbbbfc1ac4c796242a7d63783e0741223051d4edd200aaa2aa5a1f0c8d01c0b7a8c94f3a9f576b303e29f6303357ae3f2b8
7
- data.tar.gz: 30ed7d5c79192c5bfdf81122d018ae9269e30087b24be951a7d795c5a0e92aaf84aa7a9f2d58d476ed241e57b44d39ff15284549249127ac3ab811e79ce096ac
6
+ metadata.gz: f19c746e74c12884bd99d827faaf0eee55a2dca803c1a2518f857286c00c91b0d7ad7d3516ce3e7e659cabd32db841674d9f42158ce01381ad9ea0b0901d71f0
7
+ data.tar.gz: 9aaf8575f47fd4e20bcd9cd868cad9b232bc7444fc29311c68d844058ab620d94a732b84ade0b527ee1ba2122f2a50f42981d17353de5c78f2c51c4535f24cd9
data/README.md CHANGED
@@ -31,6 +31,10 @@ Install ncursesw before installing curses.gem, on which textbringer depends.
31
31
  $ sudo apt-get install libncursesw5-dev
32
32
  $ gem install curses
33
33
 
34
+ You need ffi to use clipboard commands on Windows.
35
+
36
+ $ gem install ffi
37
+
34
38
  ## Usage
35
39
 
36
40
  $ txtb
@@ -131,7 +135,9 @@ set_background_color "default"
131
135
 
132
136
  After checking out the repo, run `bundle install` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
133
137
 
134
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
138
+ To run the tests, run `bundle exec rake test`. To install this gem onto your local machine, run `bundle exec rake install`.
139
+
140
+ To release a new version, run `bundle exec rake bump` on a clean `main` branch. It increments the version number in `version.rb`, commits and pushes the change, and pushes a `v<N>` tag. The tag triggers the `push_gem.yml` workflow, which publishes the gem to [rubygems.org](https://rubygems.org) via trusted publishing and creates a GitHub release. Do not run `bundle exec rake release`; it would try to push the gem from your machine as well.
135
141
 
136
142
  ## Contributing
137
143
 
@@ -2,12 +2,15 @@ module Clipboard
2
2
  @implementation = nil
3
3
  end
4
4
 
5
- require "clipboard"
6
-
7
5
  module Textbringer
8
6
  module Commands
9
- CLIPBOARD_AVAILABLE =
10
- Clipboard.implementation.name != "Clipboard::File"
7
+ begin
8
+ require "clipboard"
9
+ CLIPBOARD_AVAILABLE =
10
+ Clipboard.implementation.name != "Clipboard::File"
11
+ rescue LoadError
12
+ CLIPBOARD_AVAILABLE = false
13
+ end
11
14
 
12
15
  if CLIPBOARD_AVAILABLE
13
16
  GLOBAL_MAP.define_key("\M-w", :clipboard_copy_region)
@@ -2,32 +2,151 @@ require "stringio"
2
2
 
3
3
  module Textbringer
4
4
  module FillExtension
5
+ class Filler
6
+ GRAPH = /(?=[\u{0000}-\u{00FF}])[[:graph:]]/
7
+
8
+ def initialize(column, prefix)
9
+ @column = column
10
+ @prefix = prefix
11
+ @prefix_width = Buffer.display_width(prefix)
12
+ @fill_column = CONFIG[:fill_column]
13
+ @output = +""
14
+ @line_start = 0
15
+ @prev_c = nil
16
+ @bol = column == 0
17
+ end
18
+
19
+ # head is kept as it is at the start of the first line; body is the
20
+ # text to fill, with the prefix already removed from each line.
21
+ def fill(head, body)
22
+ if !head.empty?
23
+ @output << head
24
+ @line_start = head.size
25
+ @column += Buffer.display_width(head)
26
+ @bol = false
27
+ end
28
+ input = StringIO.new(body)
29
+ while c = input.getc
30
+ if c == "\n"
31
+ skip_blanks(input)
32
+ if input.eof?
33
+ insert_final_newline
34
+ elsif @column < @fill_column
35
+ if GRAPH.match?(@prev_c)
36
+ insert_space_between_words(input)
37
+ end
38
+ next
39
+ else
40
+ insert_newline(@prefix)
41
+ end
42
+ else
43
+ w = Buffer.display_width(c)
44
+ if @column + w > @fill_column || @column >= @fill_column
45
+ if /\w/.match?(@prev_c) && /\w/.match?(c)
46
+ insert_newline_before_word
47
+ else
48
+ insert_newline(@prefix)
49
+ end
50
+ end
51
+ insert_char(c, w)
52
+ end
53
+ @prev_c = c
54
+ end
55
+ @output
56
+ end
57
+
58
+ private
59
+
60
+ def skip_blanks(input)
61
+ while c = input.getc
62
+ if !/[ \t]/.match?(c)
63
+ input.ungetc(c)
64
+ break
65
+ end
66
+ end
67
+ end
68
+
69
+ def insert_space_between_words(input)
70
+ c = input.getc
71
+ input.ungetc(c)
72
+ if GRAPH.match?(c)
73
+ @output << " "
74
+ @column += 1
75
+ end
76
+ end
77
+
78
+ def insert_newline_before_word
79
+ m = @output.match(/(?:([^\w \t\n])|(\w)[ \t]+)(\w*)\z/, @line_start)
80
+ return if m.nil?
81
+ word = m[3]
82
+ @output[m.begin(0)..] = "#{m[1]}#{m[2]}\n#{@prefix}#{word}"
83
+ @line_start = @output.size - word.size
84
+ @column = @prefix_width + Buffer.display_width(word)
85
+ @bol = false
86
+ end
87
+
88
+ def insert_newline(prefix)
89
+ return if @bol
90
+ @output.sub!(/[ \t]+\z/, "")
91
+ @output << "\n" << prefix
92
+ @line_start = @output.size
93
+ @column = Buffer.display_width(prefix)
94
+ @bol = true
95
+ end
96
+
97
+ # The newline that ends the text is kept even on an otherwise empty
98
+ # line, and no prefix follows it.
99
+ def insert_final_newline
100
+ @output.sub!(/[ \t]+\z/, "")
101
+ @output << "\n"
102
+ @line_start = @output.size
103
+ @column = 0
104
+ @bol = true
105
+ end
106
+
107
+ def insert_char(c, w)
108
+ if @bol && /[ \t]/.match?(c)
109
+ return
110
+ end
111
+ @output << c
112
+ @column += w
113
+ @bol = false
114
+ end
115
+ end
116
+
5
117
  refine Buffer do
6
118
  def fill_region(s = Buffer.current.point, e = Buffer.current.mark)
7
119
  s, e = Buffer.region_boundaries(s, e)
8
120
  save_excursion do
9
- str = substring(s, e)
10
121
  goto_char(s)
11
122
  pos = point
12
123
  beginning_of_line
13
124
  column = Buffer.display_width(substring(point, pos))
14
- replace(fill_string(str, column), start: s, end: e)
125
+ prefix_re = fill_prefix_regexp(comment_line?)
126
+ prefix = fill_prefix(substring(point, e), prefix_re)
127
+ lines = substring(s, e).lines
128
+ head = column == 0 && lines[0] ? lines[0][prefix_re] : ""
129
+ body = lines.each_with_index.map { |line, i|
130
+ i == 0 ? line[head.size..] : line.sub(prefix_re, "")
131
+ }.join
132
+ replace(Filler.new(column, prefix).fill(head, body),
133
+ start: s, end: e)
15
134
  end
16
135
  end
17
136
 
18
137
  def fill_paragraph
19
138
  beginning_of_line
20
- while !beginning_of_buffer? &&
21
- !looking_at?(/^[ \t]*$/)
139
+ separator = fill_paragraph_separator(comment_line?)
140
+ while !beginning_of_buffer? && !looking_at?(separator)
22
141
  backward_line
23
142
  end
24
- while looking_at?(/^[ \t]*$/)
143
+ while !end_of_buffer? && looking_at?(separator)
25
144
  forward_line
26
145
  end
27
146
  s = point
28
147
  begin
29
148
  forward_line
30
- end while !end_of_buffer? && !looking_at?(/^[ \t]*$/)
149
+ end while !end_of_buffer? && !looking_at?(separator)
31
150
  if beginning_of_line?
32
151
  backward_char
33
152
  end
@@ -36,69 +155,43 @@ module Textbringer
36
155
 
37
156
  private
38
157
 
39
- def fill_string(str, column)
40
- input = StringIO.new(str)
41
- output = +""
42
- fill_column = CONFIG[:fill_column]
43
- prev_c = nil
44
- while c = input.getc
45
- if c == "\n"
46
- if column < fill_column && !input.eof?
47
- if /(?=[\u{0000}-\u{00FF}])[[:graph:]]/.match?(prev_c)
48
- column = insert_space_between_words(input, output, column)
49
- end
50
- next
51
- end
52
- column = insert_newline(output)
53
- else
54
- w = Buffer.display_width(c)
55
- if column + w > fill_column || column >= fill_column
56
- if /\w/.match?(prev_c) && /\w/.match?(c)
57
- column = insert_newline_before_word(output, column)
58
- else
59
- column = insert_newline(output)
60
- end
61
- end
62
- column = insert_char(output, column, c, w)
63
- end
64
- prev_c = c
65
- end
66
- output
158
+ # Whether the current line is a comment line of the buffer's mode.
159
+ # Point must be at the beginning of the line.
160
+ def comment_line?
161
+ re = mode&.comment_prefix_regexp
162
+ !re.nil? && looking_at?(re)
67
163
  end
68
164
 
69
- def insert_space_between_words(input, output, column)
70
- c = input.getc
71
- input.ungetc(c)
72
- if /(?=[\u{0000}-\u{00FF}])[[:graph:]]/.match?(c)
73
- output << " "
74
- column + 1
165
+ # A line that ends the paragraph: a blank line, or, when filling a
166
+ # comment, any line that is not a comment with something in it.
167
+ def fill_paragraph_separator(comment)
168
+ if comment
169
+ /^(?!#{mode.comment_prefix_regexp}[^ \t\n])/
75
170
  else
76
- column
171
+ /^[ \t]*$/
77
172
  end
78
173
  end
79
174
 
80
- def insert_newline_before_word(output, column)
81
- if output.sub!(/(?:([^\w \t\n])|(\w)[ \t]+)(\w*)\z/, "\\1\\2\n")
82
- output << $3
83
- Buffer.display_width($3)
175
+ # What is stripped from the start of each line before filling and
176
+ # put back by the prefix: indentation, and the comment leader when
177
+ # filling a comment.
178
+ def fill_prefix_regexp(comment)
179
+ if comment
180
+ /\A(?:#{mode.comment_prefix_regexp}|[ \t]*)/
84
181
  else
85
- column
182
+ /\A[ \t]*/
86
183
  end
87
184
  end
88
185
 
89
- def insert_newline(output)
90
- output.sub!(/[ \t]\z/, "")
91
- output << "\n"
92
- 0
93
- end
94
-
95
- def insert_char(output, column, c, w)
96
- if column == 0 && /[ \t]/.match?(c)
97
- column
98
- else
99
- output << c
100
- column + w
101
- end
186
+ # The prefix of the second line, or of the first line when there is
187
+ # no second line, is the prefix of every line after the first, as
188
+ # with adaptive-fill-mode in Emacs. The first line keeps its own
189
+ # prefix.
190
+ def fill_prefix(str, prefix_re)
191
+ first, second = str.lines
192
+ line = second && !second.sub(prefix_re, "").match?(/\A\n?\z/) ?
193
+ second : first
194
+ line ? line[prefix_re] : ""
102
195
  end
103
196
  end
104
197
  end
@@ -354,6 +354,36 @@ module Textbringer
354
354
  RubyVM::MJIT.resume
355
355
  end
356
356
 
357
+ define_command(:forward_page,
358
+ doc: "Move forward to page boundary.") do
359
+ |n = number_prefix_arg|
360
+ buffer = Buffer.current
361
+ n.times do
362
+ unless buffer.re_search_forward(/^\f/, raise_error: false)
363
+ buffer.end_of_buffer
364
+ break
365
+ end
366
+ end
367
+ end
368
+
369
+ define_command(:backward_page,
370
+ doc: "Move backward to page boundary.") do
371
+ |n = number_prefix_arg|
372
+ buffer = Buffer.current
373
+ n.times do
374
+ if buffer.point == 0
375
+ break
376
+ end
377
+ buffer.backward_char
378
+ if buffer.re_search_backward(/^\f/, raise_error: false)
379
+ buffer.forward_char
380
+ else
381
+ buffer.beginning_of_buffer
382
+ break
383
+ end
384
+ end
385
+ end
386
+
357
387
  define_command(:what_cursor_position,
358
388
  doc: "Print info on cursor position.") do
359
389
  |arg = current_prefix_arg|
@@ -7,6 +7,8 @@ module Textbringer
7
7
  @@face_table = {}
8
8
  @@next_color_pair = 1
9
9
  @@color_pair_cache = {}
10
+ @@default_foreground = -1
11
+ @@default_background = -1
10
12
 
11
13
  def self.[](name)
12
14
  @@face_table[name]
@@ -26,6 +28,20 @@ module Textbringer
26
28
  @@face_table.delete(name)
27
29
  end
28
30
 
31
+ # Set the concrete color numbers substituted for -1 (default color).
32
+ # PDCurses resolves -1 when init_pair is called and
33
+ # assume_default_colors only affects color pair 0, so faces must be
34
+ # re-resolved with concrete colors to get the new default colors.
35
+ def self.set_default_color_numbers(fg_num, bg_num)
36
+ return if fg_num == @@default_foreground &&
37
+ bg_num == @@default_background
38
+ @@default_foreground = fg_num
39
+ @@default_background = bg_num
40
+ @@face_table.each_value do |face|
41
+ face.send(:resolve_inheritance)
42
+ end
43
+ end
44
+
29
45
  def initialize(name, **opts)
30
46
  @name = name
31
47
  update(**opts)
@@ -68,6 +84,8 @@ module Textbringer
68
84
  @reverse = @explicit_reverse.nil? ? (parent&.instance_variable_get(:@reverse) || false) : @explicit_reverse
69
85
  fg_num = Color[@foreground]
70
86
  bg_num = Color[@background]
87
+ fg_num = @@default_foreground if fg_num == -1
88
+ bg_num = @@default_background if bg_num == -1
71
89
  key = [fg_num, bg_num]
72
90
  unless @@color_pair_cache.key?(key)
73
91
  @@color_pair_cache[key] = @@next_color_pair