Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
# v0.1.1 - 28 September 2026
# Unreleased

* Added `Input::RubyText` - a syntax-highlighted Ruby input control
* Subclasses `Text` with minimal code changes
* Uses `dr-parser-rb` for tokenization
* Default theme with customizable colors
* Exposes `tokens` for displaying parse statistics
* Caches parse results using `@value_changed` flag
* Fixed bug where `value=` setter did not set `@value_changed = true`
* Updated console to use `RubyText` for syntax-highlighted prompt
* Added tests for `@value_changed` flag behavior

# v0.1.1 - 28 September 2025

* Removed a left over debugging `puts`. (Also allows me to test `Input::DEVELOPMENT`)

# v0.1.0 - 28 September 2026
# v0.1.0 - 28 September 2025

* Updated release workflow to use maintained actions.
* Removed references to $clipboard.
Expand Down
1 change: 1 addition & 0 deletions app/main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
require 'app/book_sample.rb'
# require 'app/log_sample.rb'
# require 'app/menu_sample.rb'
# require 'app/ruby_text_sample.rb'
105 changes: 105 additions & 0 deletions app/ruby_text_sample.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
require 'lib/input.rb'

def tick(args)
if args.tick_count == 0
Input.replace_console!

args.state.ruby_code ||= 'class Foo; def bar!(x); $gtk.args.outputs.labels << "hello #{x}"; end; end'

args.state.ruby_input = Input::RubyText.new(
x: 40,
y: 560,
w: 1200,
padding: 10,
prompt: 'Enter Ruby code...',
value: args.state.ruby_code,
size_px: 20,
selection_color: { r: 80, g: 80, b: 100 },
cursor_color: 0xAAAAAA,
cursor_width: 2,
background_color: [40, 44, 52],
blurred_background_color: [50, 54, 62]
)
args.state.ruby_input.focus
end

# Set background color (dark theme)
args.outputs.background_color = [30, 34, 42]

# Title
args.outputs.labels << {
x: 640,
y: 680,
text: 'Ruby Syntax Highlighter Input Demo',
size_px: 30,
alignment_enum: 1,
r: 200, g: 200, b: 200
}

# Instructions
args.outputs.labels << {
x: 640,
y: 640,
text: 'Type or edit Ruby code to see syntax highlighting in real-time',
size_px: 16,
alignment_enum: 1,
r: 150, g: 150, b: 150
}

args.state.ruby_input.tick
args.outputs.primitives << args.state.ruby_input

# Token statistics
y = 540
args.outputs.labels << {
x: 40,
y: y,
text: 'Token Breakdown:',
size_px: 16,
r: 200, g: 200, b: 200
}

y -= 25
token_types = args.state.ruby_input.tokens.map { |t| t[:type] }.uniq
token_types.each do |type|
count = args.state.ruby_input.tokens.count { |t| t[:type] == type }

args.outputs.labels << {
x: 40,
y: y,
text: "#{type}: #{count}",
size_px: 12,
r: 150, g: 150, b: 150
}
y -= 20
end

# Info text
bottom_y = 100
args.outputs.labels << {
x: 40,
y: bottom_y + 20,
text: "Input supports: keywords, strings, symbols, numbers, constants, comments, interpolation, and more!",
size_px: 14,
r: 150, g: 150, b: 150
}

# Example code suggestions
args.outputs.labels << {
x: 40,
y: bottom_y,
text: 'Try typing: def foo(bar) @baz = "hello #{bar}" end',
size_px: 12,
r: 120, g: 120, b: 120
}

# Footer
args.outputs.labels << {
x: 640,
y: 40,
text: "Press ESC to quit | FPS: #{args.gtk.current_framerate.to_i}",
size_px: 16,
alignment_enum: 1,
r: 150, g: 150, b: 150
}
end
1 change: 1 addition & 0 deletions lib/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ def value=(text)
@value.replace(val)
@selection_start = @selection_start.lesser(val.length)
@selection_end = @selection_end.lesser(val.length)
@value_changed = true
end

def size_enum
Expand Down
28 changes: 22 additions & 6 deletions lib/console.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ def self.replace_console!
GTK::Console.prepend(Input::Console)
end

class Prompt < Text
class Prompt < RubyText
def render(args, x:, y:)
@x = x
@y = y
Expand Down Expand Up @@ -53,7 +53,11 @@ def process_inputs args
# is the cursor before the period?
if @autocomplete_period_index && @autocomplete_period_index >= @prompt.selection_end
autocomplete_clear
elsif @prompt.value_changed? || @prompt.selection_end != @autocomplete_selection_end
elsif @prompt.value_changed?
# User typed - just update the filter, don't modify their input
autocomplete_prefix
elsif @prompt.selection_end != @autocomplete_selection_end
# Cursor moved (arrow keys) - update suggestion
autocomplete_prefix
autocomplete_next(0)
end
Expand Down Expand Up @@ -151,9 +155,11 @@ def autocomplete

autocomplete_prefix
autocomplete_next(0)
@autocomplete_selection_end = @prompt.selection_end # Track the selection we just set
@prompt.instance_variable_set(:@value_changed, false) # Reset flag so next tick doesn't think user typed
rescue Exception => e
puts "* BUG: Tab autocompletion failed. Let us know about this.\n#{e}"
puts e.backtrace
puts "* BUG: Tab autocompletion failed: #{e.class.name}: #{e.message}"
puts e.backtrace.join("\n")
end

def autocomplete_prefix
Expand All @@ -174,10 +180,20 @@ def autocomplete_prefix

def autocomplete_next(dir)
return unless @autocompleting
return if @autocomplete_menu.items.empty?

@autocomplete_menu.selected_index -= dir
@prompt.value = display_autocomplete_candidate(@autocomplete_menu.value)
@prompt.selection_start = @prompt.value.length
candidate_value = @autocomplete_menu.value
return unless candidate_value

candidate = display_autocomplete_candidate(candidate_value)
@prompt.value = candidate
# Set selection to highlight the autocompleted suffix
selection_pos = @autocomplete_period_index ? @autocomplete_period_index + 1 + @autocomplete_prefix.length : @autocomplete_prefix.length
@prompt.selection_start = selection_pos
@prompt.selection_end = candidate.length
@autocomplete_selection_end = @prompt.selection_end # Track the selection we just set
@prompt.instance_variable_set(:@value_changed, false) # Reset flag so next tick doesn't think user typed
rescue => e
puts "Error in #autocomplete_next(#{dir}): <#{e.class.name}> #{e.message}"
end
Expand Down
1 change: 1 addition & 0 deletions lib/input.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require_relative 'base.rb'
require_relative 'update.rb'
require_relative 'text.rb'
require_relative 'ruby_text.rb'
require_relative 'multiline.rb'
require_relative 'menu.rb'
require_relative 'console.rb'
Expand Down
Loading
Loading