Glimmer DSL for LibUI Code Area (Ruby Tooling Future)
Tags: ruby, desktop, gui, libui
Published: 2022-03-26 19:00:00

[Brandon Weaver](https://github.com/baweaver) has recently contacted me on the [Glimmer Gitter](https://gitter.im/AndyObtiva/glimmer) to ask questions about [Glimmer DSL for LibUI](https://github.com/AndyObtiva/glimmer-dsl-libui). He also mentioned the node pattern tool written by [Marc-André Lafortune](https://github.com/marcandre) (a fellow Rubyist I know in Montreal), which is [hosted on Heroku](https://nodepattern.herokuapp.com/). [Brandon](https://github.com/baweaver) said he was excited about the possibility of implementing something similar in pure Ruby using [Glimmer DSL for LibUI](https://github.com/AndyObtiva/glimmer-dsl-libui) by leveraging the [rouge](https://rubygems.org/gems/rouge) syntax highlighting gem. He has even blogged about the Ruby Tooling subject in the past with the title ["Future of Ruby - AST Tooling"](https://dev.to/baweaver/future-of-ruby-ast-tooling-9i1), which Matz (creator of Ruby) has alluded to before. 

Now comes the good news! The latest versions of [Glimmer DSL for LibUI](https://github.com/AndyObtiva/glimmer-dsl-libui) added support for the following features to facilitate pursuing the Ruby Tooling vision:
- [Class-Based Custom Controls](https://github.com/AndyObtiva/glimmer-dsl-libui#class-based-custom-controls): enable building custom controls (aka widgets) as reusable components that are maintained cleanly in separate classes.
- Class-Based [Custom Windows](https://github.com/AndyObtiva/glimmer-dsl-libui#custom-keywords)/[Applications](https://github.com/AndyObtiva/glimmer-dsl-libui#usage): facilitate building applications and reusable custom windows with less boilerplate code.
- [`code_area`](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/lib/glimmer/libui/custom_control/code_area.rb) Custom Control: renders syntax highlighted code in an [`area`](https://github.com/AndyObtiva/glimmer-dsl-libui#area-api) control.

Given the great productivity benefits of [Glimmer DSL for LibUI](https://github.com/AndyObtiva/glimmer-dsl-libui), I was able to piece together the [`code_area`](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/lib/glimmer/libui/custom_control/code_area.rb) custom control using the [rouge](https://rubygems.org/gems/rouge) gem in less than an hour. 

Here is how the [`code_area`](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/lib/glimmer/libui/custom_control/code_area.rb) control looks like:

![basic code area](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-basic-code-area.png)

[`code_area`](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/lib/glimmer/libui/custom_control/code_area.rb) opens the doors to so many Ruby Tooling exciting possibilities in [Glimmer DSL for LibUI](https://github.com/AndyObtiva/glimmer-dsl-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](https://github.com/AndyObtiva/glimmer-dsl-libui#basic-code-area) code:

```ruby
# 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
```

Here is the [`code_area` implementation code](https://github.com/AndyObtiva/glimmer-dsl-libui/blob/master/lib/glimmer/libui/custom_control/code_area.rb) (not very long, eh!):

```ruby
# 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
```

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

```ruby
# 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
```

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

![class-based custom controls](https://raw.githubusercontent.com/AndyObtiva/glimmer-dsl-libui/master/images/glimmer-dsl-libui-mac-method-based-custom-keyword.png)

Happy [Glimmering](https://github.com/AndyObtiva/glimmer-dsl-libui)!