DEV Community

Cover image for Glimmer DSL for LibUI Code Area (Ruby Tooling Future)
Andy Maleh
Andy Maleh

Posted on • Edited on

Glimmer DSL for LibUI Code Area (Ruby Tooling Future)

Brandon Weaver has recently contacted me on the Glimmer Gitter to ask questions about Glimmer DSL for LibUI. He also mentioned the node pattern tool written by Marc-André Lafortune (a fellow Rubyist I know in Montreal), which is hosted on Heroku. Brandon said he was excited about the possibility of implementing something similar in pure Ruby using Glimmer DSL for LibUI by leveraging the rouge syntax highlighting gem. He has even blogged about the Ruby Tooling subject in the past with the title "Future of Ruby - AST Tooling", which Matz (creator of Ruby) has alluded to before.

Now comes the good news! The latest versions of Glimmer DSL for LibUI added support for the following features to facilitate pursuing the Ruby Tooling vision:

  • Class-Based Custom Controls: enable building custom controls (aka widgets) as reusable components that are maintained cleanly in separate classes.
  • Class-Based Custom Windows/Applications: facilitate building applications and reusable custom windows with less boilerplate code.
  • code_area Custom Control: renders syntax highlighted code in an area control.

Given the great productivity benefits of Glimmer DSL for LibUI, I was able to piece together the code_area custom control using the rouge gem in less than an hour.

Here is how the code_area control looks like:

basic code area

code_area opens the doors to so many Ruby Tooling exciting possibilities in Glimmer DSL for LibUI. This is the Ruby community members' chance to be first movers and build Ruby tooling libraries that are scriptable in Ruby (unlike current popular editors).

Here is the examples/basic_code_area.rb code:

# From: https://github.com/AndyObtiva/glimmer-dsl-libui#basic-code-area require 'glimmer-dsl-libui' class BasicCodeArea include Glimmer::LibUI::Application before_body do @code = <<~CODE # Greets target with greeting def greet(greeting: 'Hello', target: 'World') puts "\#{greeting}, \#{target}!" end greet greet(target: 'Robert') greet(greeting: 'Aloha') greet(greeting: 'Aloha', target: 'Nancy') greet(greeting: 'Howdy', target: 'Doodle')  CODE end body { window('Basic Code Area', 400, 300) { margined true code_area(language: 'ruby', code: @code) } } end BasicCodeArea.launch 
Enter fullscreen mode Exit fullscreen mode

Here is the code_area implementation code (not very long, eh!):

# From: https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/lib/glimmer/libui/custom_control/code_area.rb require 'glimmer/libui/custom_control' module Glimmer module LibUI module CustomControl class CodeArea class << self def languages require 'rouge' Rouge::Lexer.all.map {|lexer| lexer.tag}.sort end def lexers require 'rouge' Rouge::Lexer.all.sort_by(&:title) end end include Glimmer::LibUI::CustomControl REGEX_COLOR_HEX6 = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/ option :language, default: 'ruby' option :theme, default: 'glimmer' option :code body { area { rectangle(0, 0, 8000, 8000) { fill :white } text { default_font family: OS.mac? ? 'Consolas' : 'Courier', size: 13, weight: :medium, italic: :normal, stretch: :normal syntax_highlighting(code).each do |token| style_data = Rouge::Theme.find(theme).new.style_for(token[:token_type]) string(token[:token_text]) { color style_data[:fg] || :black background style_data[:bg] || :white } end } } } def lexer require 'rouge' require 'glimmer-dsl-libui/ext/rouge/theme/glimmer' # TODO Try to use Rouge::Lexer.find_fancy('guess', code) in the future to guess the language or otherwise detect it from file extension @lexer ||= Rouge::Lexer.find_fancy(language) @lexer ||= Rouge::Lexer.find_fancy('ruby') # default to Ruby if no lexer is found end def syntax_highlighting(text) return [] if text.to_s.strip.empty? @syntax_highlighting ||= {} unless @syntax_highlighting.keys.include?(text) lex = lexer.lex(text).to_a text_size = 0 @syntax_highlighting[text] = lex.map do |pair| {token_type: pair.first, token_text: pair.last} end.each do |hash| hash[:token_index] = text_size text_size += hash[:token_text].size end end @syntax_highlighting[text] end end end end end 
Enter fullscreen mode Exit fullscreen mode

Here is how to build class-based custom controls (address_form and address_view):

# From: https://github.com/AndyObtiva/glimmer-dsl-libui#class-based-custom-controls require 'glimmer-dsl-libui' require 'facets' Address = Struct.new(:street, :p_o_box, :city, :state, :zip_code) class FormField include Glimmer::LibUI::CustomControl options :model, :attribute body { entry { |e| label attribute.to_s.underscore.split('_').map(&:capitalize).join(' ') text <=> [model, attribute] } } end class AddressForm include Glimmer::LibUI::CustomControl options :address body { form { form_field(model: address, attribute: :street) form_field(model: address, attribute: :p_o_box) form_field(model: address, attribute: :city) form_field(model: address, attribute: :state) form_field(model: address, attribute: :zip_code) } } end class LabelPair include Glimmer::LibUI::CustomControl options :model, :attribute, :value body { horizontal_box { label(attribute.to_s.underscore.split('_').map(&:capitalize).join(' ')) label(value.to_s) { text <= [model, attribute] } } } end class AddressView include Glimmer::LibUI::CustomControl options :address body { vertical_box { address.each_pair do |attribute, value| label_pair(model: address, attribute: attribute, value: value) end } } end class ClassBasedCustomControls include Glimmer::LibUI::Application # alias: Glimmer::LibUI::CustomWindow before_body do @address1 = Address.new('123 Main St', '23923', 'Denver', 'Colorado', '80014') @address2 = Address.new('2038 Park Ave', '83272', 'Boston', 'Massachusetts', '02101') end body { window('Class-Based Custom Keyword') { margined true horizontal_box { vertical_box { label('Address 1') { stretchy false } address_form(address: @address1) horizontal_separator { stretchy false } label('Address 1 (Saved)') { stretchy false } address_view(address: @address1) } vertical_separator { stretchy false } vertical_box { label('Address 2') { stretchy false } address_form(address: @address2) horizontal_separator { stretchy false } label('Address 2 (Saved)') { stretchy false } address_view(address: @address2) } } } } end ClassBasedCustomControls.launch 
Enter fullscreen mode Exit fullscreen mode

And, here is how reusable custom controls look like (two address forms and two address views):

class-based custom controls

Happy Glimmering!

Top comments (0)