-
Notifications
You must be signed in to change notification settings - Fork 0
Fix 5 TUI bugs: layout, viewport, history, completion, help #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,13 +4,37 @@ | |
| require "bubbles" | ||
| require "io/console" | ||
|
|
||
| begin | ||
| require "bubblezone" | ||
| rescue LoadError | ||
| # bubblezone gem ships versioned native extensions (e.g. 4.0/bubblezone.bundle) | ||
| # but its loader may not route by Ruby version. Patch and retry. | ||
| begin | ||
| spec = Gem::Specification.find_by_name("bubblezone") | ||
| major, minor, = RUBY_VERSION.split(".") | ||
| versioned_dir = File.join(spec.gem_dir, "lib", "bubblezone", "#{major}.#{minor}") | ||
| if File.directory?(versioned_dir) | ||
| # Add versioned dir so require_relative "bubblezone/bubblezone" resolves | ||
| target = File.join(spec.gem_dir, "lib", "bubblezone", "bubblezone.bundle") | ||
| source = File.join(versioned_dir, "bubblezone.bundle") | ||
| unless File.exist?(target) | ||
| File.symlink(source, target) | ||
| end | ||
| require "bubblezone" | ||
| end | ||
| rescue LoadError, Gem::MissingSpecError, Errno::EEXIST, Errno::EACCES | ||
| # mouse click zones will be disabled but scroll/drag still work. | ||
| end | ||
| end | ||
|
|
||
| module Claw | ||
| module TUI | ||
| # MVU Model — central state for the TUI application. | ||
| # Implements Bubbletea's init/update/view protocol. | ||
| class Model | ||
| attr_reader :runtime, :chat_history, :mode, :chat_viewport, :executor, :textarea, | ||
| :baseline_methods, :input_history | ||
| :baseline_methods, :input_history, :zone | ||
| attr_accessor :chat_ratio, :dragging_divider | ||
|
|
||
| def initialize(caller_binding) | ||
| @caller_binding = caller_binding | ||
|
|
@@ -22,6 +46,11 @@ def initialize(caller_binding) | |
| @input_history = [] | ||
| @history_index = nil | ||
| @saved_input = +"" | ||
| @chat_ratio = 0.70 | ||
| @dragging_divider = false | ||
| @view_width = 80 | ||
| @view_height = 24 | ||
| @zone = defined?(Bubblezone::Manager) ? Bubblezone::Manager.new : nil | ||
| @baseline_methods = begin | ||
| caller_binding.eval("methods").dup | ||
| rescue | ||
|
|
@@ -60,6 +89,8 @@ def update(msg) | |
| cmd = case msg | ||
| when Bubbletea::KeyMessage | ||
| return handle_key(msg) | ||
| when Bubbletea::MouseMessage | ||
| return handle_mouse(msg) | ||
| when Bubbles::Spinner::TickMessage | ||
| @spinner, spinner_cmd = @spinner.update(msg) | ||
| Bubbletea.batch(spinner_cmd, Bubbletea.tick(1.0) { TickMsg.new(time: Time.now) }) | ||
|
|
@@ -91,6 +122,8 @@ def update(msg) | |
| when StateChangeMsg | ||
| Bubbletea.none | ||
| when Bubbletea::WindowSizeMessage | ||
| @view_width = msg.width | ||
| @view_height = msg.height | ||
| Bubbletea.none | ||
| else | ||
| Bubbletea.none | ||
|
|
@@ -99,7 +132,8 @@ def update(msg) | |
| end | ||
|
|
||
| def view | ||
| h, w = IO.console&.winsize || [24, 80] | ||
| w = @view_width | ||
| h = @view_height | ||
| w = 80 if w < 40 | ||
| h = 24 if h < 12 | ||
| Layout.render(self, w, h) | ||
|
|
@@ -123,6 +157,79 @@ def spinner_view = @spinner.view | |
|
|
||
| private | ||
|
|
||
| def handle_mouse(msg) | ||
| w = @view_width | ||
| h = @view_height | ||
| divider_x = (w * @chat_ratio).to_i | ||
|
|
||
| if msg.wheel? | ||
| if msg.button == Bubbletea::MouseMessage::BUTTON_WHEEL_UP | ||
| @chat_viewport.scroll_up(3) | ||
| @scrolled_up = true | ||
| else | ||
| @chat_viewport.scroll_down(3) | ||
| @scrolled_up = @chat_viewport.at_bottom? ? false : true | ||
| end | ||
| elsif msg.press? | ||
| if msg.left? | ||
| # Click near divider (±2 cols) and below status bar → start drag | ||
| if (msg.x - divider_x).abs <= 2 && msg.y > 1 | ||
| @dragging_divider = true | ||
| else | ||
| handle_mouse_click(msg, w, h) | ||
| end | ||
| end | ||
| elsif msg.motion? | ||
| if @dragging_divider | ||
| @chat_ratio = (msg.x.to_f / w).clamp(0.3, 0.85) | ||
| end | ||
| elsif msg.release? | ||
| @dragging_divider = false | ||
| end | ||
|
|
||
| [self, Bubbletea.none] | ||
| end | ||
|
|
||
| def handle_mouse_click(msg, width, height) | ||
| return unless @zone | ||
| hit = @zone.find_in_bounds(msg.x, msg.y) | ||
| return unless hit | ||
| zone_id, _zone_info = hit | ||
|
|
||
| case zone_id | ||
| when /\Asnap_(\d+)\z/ | ||
| snap_id = $1.to_i | ||
| @chat_history << { | ||
| role: :system, | ||
| content: "Snapshot ##{snap_id} selected. Type /rollback #{snap_id} to restore." | ||
| } | ||
|
|
||
| when /\Amem_(\d+)\z/ | ||
| mem_id = $1.to_i | ||
| fact = Claw.memory&.long_term&.find { |m| m[:id] == mem_id } | ||
| if fact | ||
| @chat_history << { role: :system, content: "Memory ##{mem_id}: #{fact[:content]}" } | ||
| end | ||
|
|
||
| when /\Atool_(.+)\z/ | ||
| tool_name = $1 | ||
| @chat_history << { | ||
| role: :system, | ||
| content: "Tool: #{tool_name}. Use /forge #{tool_name} to promote." | ||
| } | ||
|
|
||
| when /\Afold_(\d+)\z/ | ||
| idx = $1.to_i | ||
| if idx < @chat_history.size | ||
| m = @chat_history[idx] | ||
| if m[:folded_full] | ||
| m[:content] = m[:folded_full] | ||
| m.delete(:folded_full) | ||
| end | ||
| end | ||
| end | ||
|
Comment on lines
+221
to
+230
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# 搜索 Folding 模块的完整定义
rg -n "module Folding" --type ruby -A 50Repository: twokidsCarl/ruby-claw Length of output: 3431 🏁 Script executed: # Search for all calls to Folding.fold_text
rg -n "fold_text" --type ruby -B 2 -A 2Repository: twokidsCarl/ruby-claw Length of output: 814 🏁 Script executed: # Check chat_panel.rb around lines 61-64
fd "chat_panel.rb" --type f | head -1 | xargs wc -lRepository: twokidsCarl/ruby-claw Length of output: 94 🏁 Script executed: # Once we know file size, examine chat_panel.rb lines around 61-64
fd "chat_panel.rb" --type f | head -1 | xargs cat -n | sed -n '55,75p'Repository: twokidsCarl/ruby-claw Length of output: 1152 🏁 Script executed: # Also check model.rb around lines 221-230 to see full context
fd "model.rb" --path "*/tui/*" --type f | head -1 | xargs cat -n | sed -n '215,240p'Repository: twokidsCarl/ruby-claw Length of output: 280 🏁 Script executed: # Get model.rb file and show lines around 221-230
rg -n "fold_" lib/claw/tui/model.rb -B 3 -A 8 | head -50Repository: twokidsCarl/ruby-claw Length of output: 473 🏁 Script executed: # Check if there are multiple fold_text method definitions
rg -n "def.*fold_text" --type rubyRepository: twokidsCarl/ruby-claw Length of output: 137
需要更新 🤖 Prompt for AI Agents |
||
| end | ||
|
|
||
| def handle_key(msg) | ||
| key = msg.to_s | ||
|
|
||
|
|
@@ -159,15 +266,15 @@ def handle_key(msg) | |
| return submit_textarea | ||
| end | ||
| when "up" | ||
| if @textarea.line_count <= 1 | ||
| if @textarea.line_count <= 1 || @textarea.row == 0 | ||
| navigate_history(:up) | ||
| return [self, Bubbletea.none] | ||
| else | ||
| @textarea, ta_cmd = @textarea.update(msg) | ||
| return [self, ta_cmd] | ||
| end | ||
| when "down" | ||
| if @textarea.line_count <= 1 | ||
| if @textarea.line_count <= 1 || @textarea.row == @textarea.line_count - 1 | ||
| navigate_history(:down) | ||
| return [self, Bubbletea.none] | ||
| else | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,5 +2,5 @@ | |||||
|
|
||||||
| module Claw | ||||||
| VERSION = "0.2.2" | ||||||
| BUILD = "20260407-011" | ||||||
| BUILD = "20260407-014" | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BUILD 日期已过时。 根据编码指南,BUILD 常量应使用 🔧 建议修复- BUILD = "20260407-014"
+ BUILD = "20260408-001"根据编码指南: 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| end | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: twokidsCarl/ruby-claw
Length of output: 439
🏁 Script executed:
Repository: twokidsCarl/ruby-claw
Length of output: 4104
🏁 Script executed:
Repository: twokidsCarl/ruby-claw
Length of output: 1131
Folding.fold_text方法签名不匹配 — 将导致运行时ArgumentError。根据
lib/claw/tui/folding.rb:15,Folding.fold_text方法签名为:但在
lib/claw/tui/chat_panel.rb:61处调用时传递了zone:和fold_id:参数,这些参数在方法定义中不存在:会导致运行时
ArgumentError: unknown keywords: zone, fold_id。请移除这两个参数或更新fold_text方法签名以接受它们。🤖 Prompt for AI Agents