shoko 0.1.1 → 0.1.3

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: e7637c59f48a8e0b58e7e5497a188c54dc523dd364ec25fffadacbd909b01542
4
- data.tar.gz: 96e4d52bd193d140919ac2fba904b279803b32e15ce12f477c7c22523f562815
3
+ metadata.gz: 5060375164152dd31808e0548f1a525246af0d4708e037eb3590423730d4d6c6
4
+ data.tar.gz: a71558d1f38594e40ffe44d80f4d05dcdd1d1c43a23bb003ab42f74c9ffffeec
5
5
  SHA512:
6
- metadata.gz: b288a0f6d5ae08ccd1d4c15a6b8d052468e2565008e3bfd6a548982c8144a95ff836118649b5337a3e8741ac88d9d5d6a483948b93e2be2f271a84331d3d7c83
7
- data.tar.gz: dce4bdcf953cb80acd4a01b53dc5d7e6267f2fe896db869d4f7fe162f578d198268b31261e9ee843985cec8aa21d29618938074603f9690de1b874a882a82e69
6
+ metadata.gz: 89e5372ed7369bb5631b4c190311303bbe8285602d5d7520060dc91f3c176c903f269a491d8b436f2c1d280717ddffd8b3498560aab076d7f7b7598ff76be0e8
7
+ data.tar.gz: b6ca5f48134e099a7b112c517bfa7efeb683606e3d25e66cdc27c8e6c6711615f49a7d2d7c4c75b7ef801e2d4286b3be8f3911dd00d726598093ee583c562198
data/.bundle/config CHANGED
@@ -1,4 +1,3 @@
1
1
  ---
2
- BUNDLE_BIN: "bin"
3
2
  BUNDLE_PATH: "vendor/bundle"
4
- BUNDLE_WITHOUT: "development:test"
3
+ BUNDLE_WITHOUT: "development"
data/bin/shoko CHANGED
@@ -1,15 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
4
+ app_root = File.expand_path('..', __dir__)
5
+ lib_dir = File.join(app_root, 'lib')
6
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
5
7
 
6
8
  begin
7
- require "bundler/setup"
9
+ gemspec = Dir[File.join(app_root, '*.gemspec')].first
10
+ if gemspec && File.exist?(File.join(app_root, 'Gemfile'))
11
+ require 'bundler/setup'
12
+ end
8
13
  rescue LoadError
9
- # Bundler isn't required when running from a packaged gem.
14
+ # Running without Bundler (e.g., installed gem).
10
15
  end
11
16
 
12
- $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
13
- require "shoko"
17
+ require 'shoko'
14
18
 
15
- Shoko::CLI.run(ARGV)
19
+ Shoko::CLI.run
data/bin/start CHANGED
@@ -1,15 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
4
+ app_root = File.expand_path('..', __dir__)
5
+ lib_dir = File.join(app_root, 'lib')
6
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
5
7
 
6
8
  begin
7
- require "bundler/setup"
9
+ gemspec = Dir[File.join(app_root, '*.gemspec')].first
10
+ if gemspec && File.exist?(File.join(app_root, 'Gemfile'))
11
+ require 'bundler/setup'
12
+ end
8
13
  rescue LoadError
9
- # Bundler isn't required when running from a packaged gem.
14
+ # Running without Bundler (e.g., installed gem).
10
15
  end
11
16
 
12
- $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
13
- require "shoko"
17
+ require 'shoko'
14
18
 
15
- Shoko::CLI.run(ARGV)
19
+ Shoko::CLI.run
@@ -4,22 +4,34 @@ module Shoko
4
4
  module Adapters::Input::Annotations
5
5
  # Handles mouse events for text selection in the reader
6
6
  class MouseHandler
7
+ SGR_REGEX = /\e\[<(\d+);(\d+);(\d+)([Mm])/
8
+
7
9
  attr_reader :selection_start, :selection_end, :selecting
8
10
 
9
11
  def initialize
10
12
  reset
11
13
  end
12
14
 
15
+ def mouse_sequence?(input)
16
+ !parse_mouse_event(input).nil?
17
+ end
18
+
19
+ def mouse_prefix?(input)
20
+ bytes = input.to_s.b
21
+ return false if bytes.bytesize < 2
22
+ return false unless bytes.getbyte(0) == 0x1B && bytes.getbyte(1) == 0x5B
23
+
24
+ return true if bytes.bytesize == 2
25
+
26
+ [0x3C, 0x4D].include?(bytes.getbyte(2))
27
+ end
28
+
13
29
  # Parse ANSI mouse event
14
30
  def parse_mouse_event(input)
15
- return nil unless input =~ /\e\[<(\d+);(\d+);(\d+)([Mm])/
31
+ return parse_sgr_mouse_event(input) if sgr_mouse_sequence?(input)
32
+ return parse_x10_mouse_event(input) if x10_mouse_sequence?(input)
16
33
 
17
- {
18
- button: ::Regexp.last_match(1).to_i,
19
- x: ::Regexp.last_match(2).to_i - 1, # Convert to 0-based
20
- y: ::Regexp.last_match(3).to_i - 1,
21
- released: ::Regexp.last_match(4) == 'm',
22
- }
34
+ nil
23
35
  end
24
36
 
25
37
  # Handle mouse event and update selection state
@@ -63,6 +75,44 @@ module Shoko
63
75
 
64
76
  private
65
77
 
78
+ def sgr_mouse_sequence?(input)
79
+ bytes = input.to_s.b
80
+ bytes.bytesize >= 4 && bytes.getbyte(0) == 0x1B && bytes.getbyte(1) == 0x5B && bytes.getbyte(2) == 0x3C
81
+ end
82
+
83
+ def x10_mouse_sequence?(input)
84
+ bytes = input.to_s.b
85
+ bytes.bytesize >= 6 && bytes.getbyte(0) == 0x1B && bytes.getbyte(1) == 0x5B && bytes.getbyte(2) == 0x4D
86
+ end
87
+
88
+ def parse_sgr_mouse_event(input)
89
+ match = SGR_REGEX.match(input)
90
+ return nil unless match
91
+
92
+ {
93
+ button: match[1].to_i,
94
+ x: match[2].to_i - 1, # Convert to 0-based
95
+ y: match[3].to_i - 1,
96
+ released: match[4] == 'm',
97
+ }
98
+ end
99
+
100
+ def parse_x10_mouse_event(input)
101
+ bytes = input.to_s.b
102
+ cb = bytes.getbyte(3) - 32
103
+ cx = bytes.getbyte(4) - 33
104
+ cy = bytes.getbyte(5) - 33
105
+
106
+ return nil if cb.negative? || cx.negative? || cy.negative?
107
+
108
+ {
109
+ button: cb,
110
+ x: cx,
111
+ y: cy,
112
+ released: (cb & 3) == 3,
113
+ }
114
+ end
115
+
66
116
  def start_selection(col, row)
67
117
  @selecting = true
68
118
  @selection_start = { x: col, y: row }
@@ -302,6 +302,13 @@ module Shoko
302
302
  end
303
303
 
304
304
  def parse_csi_sequence(prefix_bytes:, output_prefix:)
305
+ if x10_mouse_prefix?(prefix_bytes)
306
+ min_length = prefix_bytes == 1 ? 5 : 6
307
+ return nil if @buffer.bytesize < min_length
308
+
309
+ return parse_x10_mouse_sequence(prefix_bytes)
310
+ end
311
+
305
312
  return nil unless (final_index = DecoderScanner.new(@buffer).csi_final_index(prefix_bytes))
306
313
 
307
314
  end_index = final_index + 1
@@ -326,6 +333,33 @@ module Shoko
326
333
  prefix ? "#{prefix}#{char}" : char
327
334
  end
328
335
 
336
+ def x10_mouse_prefix?(prefix_bytes)
337
+ case prefix_bytes
338
+ when 2
339
+ return false unless @buffer.bytesize >= 3
340
+
341
+ @buffer.getbyte(0) == ESC && @buffer.getbyte(1) == 0x5B && @buffer.getbyte(2) == 0x4D
342
+ when 1
343
+ return false unless @buffer.bytesize >= 2
344
+
345
+ @buffer.getbyte(0) == CSI_8BIT && @buffer.getbyte(1) == 0x4D
346
+ else
347
+ false
348
+ end
349
+ end
350
+
351
+ def parse_x10_mouse_sequence(prefix_bytes)
352
+ length = prefix_bytes + 1 + 3
353
+ raw = @buffer.byteslice(0, length)
354
+ consume_and_clear(length)
355
+ if prefix_bytes == 1
356
+ coords = raw.byteslice(2, 3) || ''.b
357
+ return "\e[M".b + coords
358
+ end
359
+
360
+ raw.force_encoding(Encoding::BINARY)
361
+ end
362
+
329
363
  def degrade_pending_token
330
364
  lead_byte = @buffer.getbyte(0)
331
365
  consume_and_clear(1)
@@ -23,6 +23,7 @@ module Shoko
23
23
  @coordinate_service = dependencies.resolve(:coordinate_service)
24
24
 
25
25
  @mouse_handler = Shoko::Adapters::Input::Annotations::MouseHandler.new
26
+ @mouse_input_buffer = nil
26
27
  @sidebar_scroll_drag_active = false
27
28
  state.dispatch(Application::Actions::ClearPopupMenuAction.new)
28
29
  @selected_text = nil
@@ -42,17 +43,60 @@ module Shoko
42
43
  key = terminal_service.read_input_with_mouse(timeout: timeout)
43
44
  return [] unless key
44
45
 
45
- if key.start_with?("\e[<")
46
- handle_mouse_input(key)
47
- return []
48
- end
49
-
50
46
  keys = [key]
51
47
  while (extra = terminal_service.read_key)
52
48
  keys << extra
53
49
  break if keys.size > 10
54
50
  end
55
- keys
51
+
52
+ filter_mouse_sequences(keys)
53
+ end
54
+
55
+ def filter_mouse_sequences(keys)
56
+ remaining = []
57
+ saw_mouse = false
58
+ saw_mouse_prefix = false
59
+
60
+ keys.each do |token|
61
+ if @mouse_input_buffer
62
+ @mouse_input_buffer << token
63
+ if @mouse_handler.mouse_sequence?(@mouse_input_buffer)
64
+ handle_mouse_input(@mouse_input_buffer)
65
+ @mouse_input_buffer = nil
66
+ saw_mouse = true
67
+ next
68
+ end
69
+
70
+ if @mouse_handler.mouse_prefix?(@mouse_input_buffer)
71
+ saw_mouse_prefix = true
72
+ next
73
+ end
74
+
75
+ remaining << @mouse_input_buffer
76
+ @mouse_input_buffer = nil
77
+ next
78
+ end
79
+
80
+ if @mouse_handler.mouse_sequence?(token)
81
+ handle_mouse_input(token)
82
+ saw_mouse = true
83
+ next
84
+ end
85
+
86
+ if @mouse_handler.mouse_prefix?(token)
87
+ @mouse_input_buffer = String(token)
88
+ saw_mouse_prefix = true
89
+ next
90
+ end
91
+
92
+ if saw_mouse || saw_mouse_prefix
93
+ next if token == 'q' || token == "\e"
94
+ end
95
+
96
+ remaining << token
97
+ end
98
+
99
+ remaining
56
100
  end
57
101
 
58
102
  def handle_mouse_input(input)
@@ -122,10 +122,14 @@ module Shoko
122
122
  rescue StandardError
123
123
  nil
124
124
  end
125
+
125
126
  lines = registry&.lines
126
- return lines if lines && !lines.empty?
127
+ return lines if lines.is_a?(Hash)
128
+
129
+ fallback = state.get(%i[reader rendered_lines])
130
+ return {} if fallback == :render_registry
127
131
 
128
- state.get(%i[reader rendered_lines]) || {}
132
+ fallback.is_a?(Hash) ? fallback : {}
129
133
  end
130
134
 
131
135
  def self.popup_menu(state)
@@ -238,7 +238,7 @@ module Shoko
238
238
  end
239
239
 
240
240
  def geometry_index_by_row(rendered_lines)
241
- return {} unless rendered_lines
241
+ return {} unless rendered_lines.respond_to?(:each_value)
242
242
 
243
243
  key = rendered_lines.object_id
244
244
  if @geometry_index_key != key
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Shoko
4
- VERSION = '0.1.1'
4
+ VERSION = '0.1.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoko
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shoko
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: rexml
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -344,7 +358,6 @@ files:
344
358
  - lib/shoko/test_support/terminal_double.rb
345
359
  - lib/shoko/test_support/test_mode.rb
346
360
  - lib/zip.rb
347
- - shoko-0.1.0.gem
348
361
  - zip.rb
349
362
  homepage: https://sr.ht/~shayan/Shoko/
350
363
  licenses:
data/shoko-0.1.0.gem DELETED
Binary file