diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7f0e74a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +.git* export-ignore +*.yml export-ignore +README.md export-ignore +Gemfile* export-ignore diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml new file mode 100644 index 0000000..79b9ea4 --- /dev/null +++ b/.github/workflows/rubocop.yml @@ -0,0 +1,47 @@ +name: Rubocop +on: + push: + paths: + - "**/*.rb" + - "profanity.rb" + pull_request: + paths: + - "**/*.rb" + - "profanity.rb" + +jobs: + rubocop: + runs-on: ubuntu-latest + strategy: + matrix: + ruby: ['3.3'] + name: Run Rubocop on Ruby ${{ matrix.ruby }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v44 + with: + files: | + **/*.rb + profanity.rb + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: false + - name: Install ruby gem dependencies with bundler + run: | + gem install bundler + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + + - run: bundle install + - name: Rubocop + run: | + for file in ${{ steps.changed-files.outputs.all_changed_files }}; do + bundle exec rubocop $file + done diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..0d3131e --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,69 @@ +AllCops: + TargetRubyVersion: 3.3 + NewCops: disable + Include: + - '**/*.lic' + - '**/*.rb' + +Naming: + Enabled: false + +Style: + Enabled: false + +Layout/EmptyLineAfterGuardClause: + Enabled: false + +Layout/EndOfLine: + Enabled: false + +Layout/FirstHashElementIndentation: + EnforcedStyle: consistent + +Layout/HashAlignment: + EnforcedHashRocketStyle: table + +Layout/LineLength: + Enabled: false + +Layout/HeredocIndentation: + Enabled: false + +Layout/ClosingHeredocIndentation: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Metrics/BlockLength: + Enabled: false + +Metrics/BlockNesting: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/CyclomaticComplexity: + Enabled: false + +Metrics/MethodLength: + Enabled: false + +Metrics/ModuleLength: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false + +Metrics/ParameterLists: + Enabled: false + +Security/Eval: + Enabled: false + +Security/MarshalLoad: + Enabled: false + +Lint/ScriptPermission: + Enabled: false diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..5773b4b --- /dev/null +++ b/Gemfile @@ -0,0 +1,8 @@ +source "https://rubygems.org" + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +group :development do + gem "rspec" + gem 'rubocop' +end diff --git a/README.md b/README.md index 19693a4..293cc0d 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,83 @@ -ProfanityFE -=========== - +# ProfanityFE A terminal frontend for Simutronics games based on Ruby and Ncurses. + +## Installation +1. Install Ruby on machine +2. Git Clone the Lich5 repository +3. Git Clone the ProfanityFE repository +4. Configure the template file for your character in the ProfanityFE\templates folder, otherwise use default.xml +5. Open Lich5 and save a character entry, alternatively do this on another machine with a GUI interface and copy Lich5\data\entry.dat +6. Launch Lich5 via `ruby ~/lich-5/lich.rbw --login Rinualdo --without-frontend --detachable-client=8000 &` or similar. +7. Launch Profanity via `ruby ~/ProfanityFE/profanity.rb --port=8000 --char=Rinualdo` + +## Profanity CLI Options +* --port= +* --default-color-id= +* --default-background-color-id= +* --custom-colors= +* --settings-file= +* --char= +* --no-status do not redraw the process title with status updates +* --links enable links to be shown by default, otherwise can enable via .links command +* --speech-ts display timestamps on speech, familiar and thought window +* --remote-url display LaunchURLs on screen, used for remote environments +* --template= filename of template to use in templates subdirectory + +## Sample Scripts +Here's a sample login script written for Linux, usage syntax would be `.\gemstone.sh ` + +Do note that some of these settings will need to be adjusted based on the terminal being used (e.g. xterm-256color vs screen-256color) +```bash +#!/bin/bash +set -e + +port=8000 +CHAR=$1 +LICH_BIN=~/lich-5/lich.rbw +PROFANITY_BIN=~/ProfanityFE/profanity.rb + +export TERM=screen-256color + +lookup_char_port () { + local char=$1 + port=$(ps a | egrep -0 "\-\-login $char \-\-detachable-client=([0-9]+)" | egrep -o "[0-9]+" | sort | tail -n1) +} + +if [[ -z $CHAR ]]; then + echo "Usage: gemstone.sh {{character_name}}" + exit +fi + +if [[ -z $DISPLAY ]]; then + echo "Detected empty DISPLAY setting, defaulting to :0" +fi + +echo "Attempting to login as $CHAR..." + +if ps aux | \grep [l]ich | \grep -i $CHAR; then + lookup_char_port $CHAR + echo "Detecting existing connection on port $port" +else + if ps a | \grep [d]etachable-client; then + max_port=$(ps a | grep -Eo "\-\-detachable-client=([0-9]+)" | egrep -o "[0-9]+" | sort | tail -n1) + port=$(expr $max_port + 1) + fi + echo "Detecting existing clients but no connection for this character. Using Port[$port]" + echo "ruby $LICH_BIN --login $CHAR --detachable-client=$port --without-frontend 2> /dev/null &" + + ruby $LICH_BIN --login $CHAR --detachable-client=$port --without-frontend 2> /dev/null & + sleep 4 +fi + +for i in {1..10}; do + echo "Attempting to connect to lich process... " + echo "ruby $PROFANITY_BIN --port=$port --char=$CHAR" + if ruby $PROFANITY_BIN --port=$port --char=$CHAR; then + echo "Done" + break + else + echo "Failed to establish connection, trying again in 3 seconds..." + sleep 3 + fi +done +``` diff --git a/ext/string.rb b/ext/string.rb index 9df0d56..240c591 100644 --- a/ext/string.rb +++ b/ext/string.rb @@ -1,28 +1,28 @@ class String - def alnum? - !!match(/^[[:alnum:]]+$/) - end - + def alnum? + !!match(/^[[:alnum:]]+$/) + end + def digits? - !!match(/^[[:digit:]]+$/) - end - + !!match(/^[[:digit:]]+$/) + end + def punct? - !!match(/^[[:punct:]]+$/) - end - + !!match(/^[[:punct:]]+$/) + end + def space? - !!match(/^[[:space:]]+$/) + !!match(/^[[:space:]]+$/) end - + def &(other) - shortest, longest = [self, other].sort { |a, b| a.size - b.size } + shortest, longest = [self, other].sort { |a, b| a.size - b.size } - shortest.each_char.to_a - .zip(longest.each_char.to_a) - .take_while { |a, b| a == b } - .transpose - .first - .join("") - end -end \ No newline at end of file + shortest.each_char.to_a + .zip(longest.each_char.to_a) + .take_while { |a, b| a == b } + .transpose + .first + .join("") + end +end diff --git a/hilite/hilite.rb b/hilite/hilite.rb index 9cebebf..4b5e0ac 100644 --- a/hilite/hilite.rb +++ b/hilite/hilite.rb @@ -9,11 +9,11 @@ module Hilite def self.load(file:, flush: true) Hilite.fetch(file: file, flush: flush) do |xml| store.clear() if flush - + xml.elements - .select do |ele| ele.name.eql?("highlight") end - .each do |highlight| Hilite.parse(highlight: highlight, parent: file) end - + .select do |ele| ele.name.eql?("highlight") end + .each do |highlight| Hilite.parse(highlight: highlight, parent: file) end + return xml end end @@ -26,17 +26,19 @@ def self.parse(highlight:, parent:) def self.inherit(file:, parent:) Profanity.log("[Settings] inheriting #{file} from #{parent}") if Opts.debug Hilite.load( - file: File.join(File.dirname(parent), file), - flush: false) + file: File.join(File.dirname(parent), file), + flush: false + ) end def self.add_highlight(highlight) begin pattern = %r{#{highlight.text.strip}} - Hilite.put(pattern, [ - highlight.attributes["fg"], - highlight.attributes["bg"], - highlight.attributes["ul"]]) + Hilite.put(pattern, [ + highlight.attributes["fg"], + highlight.attributes["bg"], + highlight.attributes["ul"] + ]) rescue => exception # todo: write useful error/backtrace to UI Profanity.log(highlight.text) @@ -44,4 +46,4 @@ def self.add_highlight(highlight) Profanity.log(exception.backtrace) end end -end \ No newline at end of file +end diff --git a/layout/layout.rb b/layout/layout.rb index e9b688f..5308c5a 100644 --- a/layout/layout.rb +++ b/layout/layout.rb @@ -4,4 +4,4 @@ module Layout extend KVStore extend Loader -end \ No newline at end of file +end diff --git a/plugin/autocomplete.rb b/plugin/autocomplete.rb index dc0b119..b7faad2 100644 --- a/plugin/autocomplete.rb +++ b/plugin/autocomplete.rb @@ -1,46 +1,48 @@ class Autocomplete - HIGHLIGHT = "a6e22e" + HIGHLIGHT = "a6e22e" - @in_menu = false + @in_menu = false - def self.consume(key_code, history:, buffer:) - Autocomplete.wrap do - return @in_menu = true if key_code == 9 # tab - return unless @in_menu - end - end - ## - ## @brief checks to see if the historical command is a possible - ## completion of the current state of the command buffer - ## - ## @param current String The current command string - ## @param historical String The historical command string - ## - ## @return Boolean if it is a possible completion - ## - def self.compare(current, historical) - current = current.split("") - historical = historical.split("") - current.each_with_index.map { |char, i| char == historical[i] ? 1 : 0 } - .reduce(&:+) == current.size - end - ## - ## @brief finds the first divergence in an array of Strings that should - ## - ## @param suggestions Array(String) The suggestions - ## - ## @return String a String<0..n> of which the characters exist in all suggestions - ## - def self.find_branch(suggestions) - suggestions.reduce(&:&) - end + def self.consume(key_code) + Autocomplete.wrap do + return @in_menu = true if key_code == 9 # tab + return unless @in_menu + end + end - def self.wrap() - begin - yield - rescue Exception => e - Profanity.log("[autocomplete error #{Time.now}] #{$e.message}") - e.backtrace[0...4].each do |ln| Profanity.log(ln) end - end - end -end \ No newline at end of file + ## + ## @brief checks to see if the historical command is a possible + ## completion of the current state of the command buffer + ## + ## @param current String The current command string + ## @param historical String The historical command string + ## + ## @return Boolean if it is a possible completion + ## + def self.compare(current, historical) + current = current.split("") + historical = historical.split("") + current.each_with_index.map { |char, i| char == historical[i] ? 1 : 0 } + .reduce(&:+) == current.size + end + + ## + ## @brief finds the first divergence in an array of Strings that should + ## + ## @param suggestions Array(String) The suggestions + ## + ## @return String a String<0..n> of which the characters exist in all suggestions + ## + def self.find_branch(suggestions) + suggestions.reduce(&:&) + end + + def self.wrap() + begin + yield + rescue StandardError => e + Profanity.log("[autocomplete error #{Time.now}] #{$e.message}") + e.backtrace[0...4].each do |ln| Profanity.log(ln) end + end + end +end diff --git a/profanity.rb b/profanity.rb index 3a4bd08..0b75901 100644 --- a/profanity.rb +++ b/profanity.rb @@ -1,33 +1,31 @@ #!/usr/bin/env ruby # encoding: US-ASCII -# vim: set sts=2 noet ts=2: + =begin - ProfanityFE v0.4 - Copyright (C) 2013 Matthew Lowe + ProfanityFE + Copyright (C) 2013 Matthew Lowe - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - matt@lichproject.org + matt@lichproject.org =end -$version = 0.4 require 'json' require 'benchmark' -require 'thread' require 'socket' require 'rexml/document' require 'curses' @@ -42,71 +40,73 @@ require_relative "./ui/indicator.rb" require_relative "./ui/progress.rb" require_relative "./ui/text.rb" +require_relative "./ui/exp.rb" +require_relative "./ui/perc.rb" require_relative "./plugin/autocomplete.rb" require_relative "./settings/settings.rb" require_relative "./hilite/hilite.rb" module Profanity - LOG_FILE = Settings.file("debug.log") - SETTINGS_FILE = Settings.file("default.xml") - - def self.log_file - return File.open(LOG_FILE, 'a') { |file| yield file } if block_given? - end - - @title = nil - @status = nil - @char = Opts.char.capitalize - @state = {} - - def self.fetch(key, _default = nil) - @state.fetch(key, _default) - end - - def self.put(**args) - @state.merge!(args) - end - - def self.set_terminal_title(title) - return if @title.eql?(title) # noop - @title = title - @title.untaint - system("printf \"\033]0;#{title}\007\"") - Process.setproctitle(title) - end - - def self.app_title(*parts) - return if @status == parts.join("") - @status = parts.join("") - return set_terminal_title(@char) if @status.empty? - set_terminal_title([@char, "[#{parts.reject(&:empty?).join(":")}]"].join(" ").gsub(">", "")) - end - - def self.update_process_title() - return if Opts["no-status"] - app_title(Profanity.fetch(:prompt, ""), Profanity.fetch(:room, "")) - end - - def self.log(str) - log_file { |f| f.puts str } - end - - def self.help_menu() - puts <<~HELP - - Profanity FrontEnd v#{$version} - - --port= - --default-color-id= - --default-background-color-id= - --custom-colors= - --settings-file= - --char= - --no-status do not redraw the process title with status updates - HELP - exit - end + LOG_FILE = Settings.file("debug.log") + + def self.log_file + return File.open(LOG_FILE, 'a') { |file| yield file } if block_given? + end + + @title = nil + @status = nil + @char = Opts.char.nil? ? "Unknown" : Opts.char.capitalize + @state = {} + + def self.fetch(key, default = nil) + @state.fetch(key, default) + end + + def self.put(**args) + @state.merge!(args) + end + + def self.set_terminal_title(title) + return if @title.eql?(title) # noop + @title = title + system("printf \"\033]0;#{title}\007\"") + Process.setproctitle(title) + end + + def self.app_title(*parts) + return if @status == parts.join("") + @status = parts.join("") + return set_terminal_title(@char) if @status.empty? + set_terminal_title([@char, "[#{parts.reject(&:empty?).join(":")}]"].join(" ").gsub(">", "")) + end + + def self.update_process_title() + return if Opts["no-status"] + app_title(Profanity.fetch(:prompt, ""), Profanity.fetch(:room, "")) + end + + def self.log(str) + log_file { |f| f.puts str } + end + + def self.help_menu() + puts <<~HELP + + Profanity FrontEnd + #{' '} + --port= the port to connect to Lich on + --default-color-id= optional override, a terminal palette color number + --default-background-color-id= optional override, a terminal palette color number + --char= character name used in Lich + --no-status do not redraw the process title with status updates + --links enable links to be shown by default, otherwise can enable via .links command + --speech-ts display timestamps on speech, familiar and thought window + --remote-url display LaunchURLs on screen, used for remote environments + --template= filename of template to use in templates subdirectory + HELP + exit + end end Curses.init_screen @@ -120,7 +120,6 @@ def self.help_menu() command_buffer_offset = 0 command_history = Array.new command_history_pos = 0 -max_command_history = 20 min_cmd_length_for_history = 4 $server_time_offset = 0 skip_server_time_offset = false @@ -134,6 +133,7 @@ def self.help_menu() countdown_handler = Hash.new command_window = nil command_window_layout = nil +blue_links = (Opts["links"] ? true : false) # We need a mutex for the settings because highlights can be accessed during a # reload. For now, it is just used to protect access to HIGHLIGHT, but if we # ever support reloading other settings in the future it will have to protect @@ -145,169 +145,199 @@ def self.help_menu() LAYOUT = Hash.new WINDOWS = Hash.new SCROLL_WINDOW = Array.new -PORT = (Opts.port || 8000).to_i -HOST = (Opts.host || "127.0.0.1") -DEFAULT_COLOR_ID = (Opts.color_id || 7).to_i -DEFAULT_BACKGROUND_COLOR_ID = (Opts.background_color_id || 0).to_i -SETTINGS_FILENAME = Settings.file(Opts.char.downcase + ".xml") if Opts.char - -def add_prompt(window, prompt_text, cmd="") - window.add_string("#{prompt_text}#{cmd}", [ h={ :start => 0, :end => (prompt_text.length + cmd.length), :fg => '555555' } ]) +PORT = (Opts.port || 8000).to_i +HOST = (Opts.host || "127.0.0.1") +DEFAULT_COLOR_ID = (Opts["default-color-id"] || 7).to_i +DEFAULT_BACKGROUND_COLOR_ID = (Opts["default-background-color-id"] || 0).to_i +if Opts.char + if Opts.template + if File.exist?(File.join(File.expand_path(File.dirname(__FILE__)), 'templates', Opts.template.downcase)) + SETTINGS_FILENAME = File.join(File.expand_path(File.dirname(__FILE__)), 'templates', Opts.template.downcase) + else + raise StandardError, <<~ERROR + You specified --template=#{Opts.template} but it doesn't exist. + Please try again! + ERROR + end + else + if File.exist?(Settings.file(Opts.char.downcase + ".xml")) + SETTINGS_FILENAME = Settings.file(Opts.char.downcase + ".xml") + elsif File.exist?(File.join(File.expand_path(File.dirname(__FILE__)), 'templates', Opts.char.downcase + ".xml")) + SETTINGS_FILENAME = File.join(File.expand_path(File.dirname(__FILE__)), 'templates', Opts.char.downcase + ".xml") + else + SETTINGS_FILENAME = File.join(File.expand_path(File.dirname(__FILE__)), 'templates', 'default.xml') + end + end +else + SETTINGS_FILENAME = File.join(File.expand_path(File.dirname(__FILE__)), 'templates', 'default.xml') end -fix_setting = { 'on' => true, 'yes' => true, 'off' => false, 'no' => false } +def add_prompt(window, prompt_text, cmd = "") + window.add_string("#{prompt_text}#{cmd}", [{ :start => 0, :end => (prompt_text.length + cmd.length), :fg => '555555' }]) +end unless defined?(SETTINGS_FILENAME) - raise Exception, <<~ERROR - you pust pass --char= - #{Opts.parse()} - ERROR + raise StandardError, <<~ERROR + you must pass --char= or --template= + #{Opts.parse()} + ERROR end -Profanity.set_terminal_title(Opts.char.capitalize) +Profanity.set_terminal_title((Opts.char.nil? ? "Unknown" : Opts.char.capitalize)) unless defined?(CUSTOM_COLORS) - CUSTOM_COLORS = Curses.can_change_color? + CUSTOM_COLORS = Curses.can_change_color? end -DEFAULT_COLOR_CODE = Curses.color_content(DEFAULT_COLOR_ID).collect { |num| ((num/1000.0)*255).round.to_s(16) }.join('').rjust(6, '0') -DEFAULT_BACKGROUND_COLOR_CODE = Curses.color_content(DEFAULT_BACKGROUND_COLOR_ID).collect { |num| ((num/1000.0)*255).round.to_s(16) }.join('').rjust(6, '0') +DEFAULT_COLOR_CODE = Curses.color_content(DEFAULT_COLOR_ID).collect { |num| ((num / 1000.0) * 255).round.to_s(16) }.join('').rjust(6, '0') +DEFAULT_BACKGROUND_COLOR_CODE = Curses.color_content(DEFAULT_BACKGROUND_COLOR_ID).collect { |num| ((num / 1000.0) * 255).round.to_s(16) }.join('').rjust(6, '0') xml_escape_list = { - '<' => '<', - '>' => '>', - '"' => '"', - ''' => "'", - '&' => '&', -# ' ' => "\n", + '<' => '<', + '>' => '>', + '"' => '"', + ''' => "'", + '&' => '&', + # ' ' => "\n", } key_name = { - 'ctrl+a' => 1, - 'ctrl+b' => 2, -# 'ctrl+c' => 3, - 'ctrl+d' => 4, - 'ctrl+e' => 5, - 'ctrl+f' => 6, - 'ctrl+g' => 7, - 'ctrl+h' => 8, - 'win_backspace' => 8, - 'ctrl+i' => 9, - 'tab' => 9, - 'ctrl+j' => 10, - 'enter' => 10, - 'ctrl+k' => 11, - 'ctrl+l' => 12, - 'return' => 13, - 'ctrl+m' => 13, - 'ctrl+n' => 14, - 'ctrl+o' => 15, - 'ctrl+p' => 16, -# 'ctrl+q' => 17, - 'ctrl+r' => 18, -# 'ctrl+s' => 19, - 'ctrl+t' => 20, - 'ctrl+u' => 21, - 'ctrl+v' => 22, - 'ctrl+w' => 23, - 'ctrl+x' => 24, - 'ctrl+y' => 25, - 'ctrl+z' => 26, - 'alt' => 27, - 'escape' => 27, - 'ctrl+?' => 127, - 'down' => 258, - 'up' => 259, - 'left' => 260, - 'right' => 261, - 'home' => 262, - 'backspace' => 263, - 'f1' => 265, - 'f2' => 266, - 'f3' => 267, - 'f4' => 268, - 'f5' => 269, - 'f6' => 270, - 'f7' => 271, - 'f8' => 272, - 'f9' => 273, - 'f10' => 274, - 'f11' => 275, - 'f12' => 276, - 'delete' => 330, - 'insert' => 331, - 'page_down' => 338, - 'page_up' => 339, - 'end' => 360, - 'resize' => 410, - 'ctrl+delete' => 513, - 'alt+down' => 517, - 'ctrl+down' => 519, - 'alt+left' => 537, - 'ctrl+left' => 539, - 'alt+page_down' => 542, - 'alt+page_up' => 547, - 'alt+right' => 552, - 'ctrl+right' => 554, - 'alt+up' => 558, - 'ctrl+up' => 560, + 'ctrl+a' => 1, + 'ctrl+b' => 2, + # 'ctrl+c' => 3, + 'ctrl+d' => 4, + 'ctrl+e' => 5, + 'ctrl+f' => 6, + 'ctrl+g' => 7, + 'ctrl+h' => 8, + 'win_backspace' => 8, + 'ctrl+i' => 9, + 'tab' => 9, + 'ctrl+j' => 10, + 'enter' => 10, + 'ctrl+k' => 11, + 'ctrl+l' => 12, + 'return' => 13, + 'ctrl+m' => 13, + 'ctrl+n' => 14, + 'ctrl+o' => 15, + 'ctrl+p' => 16, + # 'ctrl+q' => 17, + 'ctrl+r' => 18, + # 'ctrl+s' => 19, + 'ctrl+t' => 20, + 'ctrl+u' => 21, + 'ctrl+v' => 22, + 'ctrl+w' => 23, + 'ctrl+x' => 24, + 'ctrl+y' => 25, + 'ctrl+z' => 26, + 'alt' => 27, + 'escape' => 27, + 'ctrl+?' => 127, + 'down' => 258, + 'up' => 259, + 'left' => 260, + 'right' => 261, + 'home' => 262, + 'backspace' => 263, + 'f1' => 265, + 'f2' => 266, + 'f3' => 267, + 'f4' => 268, + 'f5' => 269, + 'f6' => 270, + 'f7' => 271, + 'f8' => 272, + 'f9' => 273, + 'f10' => 274, + 'f11' => 275, + 'f12' => 276, + 'delete' => 330, + 'insert' => 331, + 'page_down' => 338, + 'page_up' => 339, + 'end' => 360, + 'resize' => 410, + 'num_7' => 449, + 'num_8' => 450, + 'num_9' => 451, + 'num_4' => 452, + 'num_5' => 453, + 'num_6' => 454, + 'num_1' => 455, + 'num_2' => 456, + 'num_3' => 457, + 'num_enter' => 459, + 'ctrl+delete' => 513, + 'alt+down' => 517, + 'ctrl+down' => 519, + 'alt+left' => 537, + 'ctrl+left' => 539, + 'alt+page_down' => 542, + 'alt+page_up' => 547, + 'alt+right' => 552, + 'ctrl+right' => 554, + 'alt+up' => 558, + 'ctrl+up' => 560, } if CUSTOM_COLORS - COLOR_ID_LOOKUP = Hash.new - COLOR_ID_LOOKUP[DEFAULT_COLOR_CODE] = DEFAULT_COLOR_ID - COLOR_ID_LOOKUP[DEFAULT_BACKGROUND_COLOR_CODE] = DEFAULT_BACKGROUND_COLOR_ID - COLOR_ID_HISTORY = Array.new - for num in 0...Curses.colors - unless (num == DEFAULT_COLOR_ID) or (num == DEFAULT_BACKGROUND_COLOR_ID) - COLOR_ID_HISTORY.push(num) - end - end - - def get_color_id(code) - if color_id = COLOR_ID_LOOKUP[code] - color_id - else - color_id = COLOR_ID_HISTORY.shift - COLOR_ID_LOOKUP.delete_if { |k,v| v == color_id } - sleep 0.01 # somehow this keeps Curses.init_color from failing sometimes - Curses.init_color(color_id, ((code[0..1].to_s.hex/255.0)*1000).round, ((code[2..3].to_s.hex/255.0)*1000).round, ((code[4..5].to_s.hex/255.0)*1000).round) - COLOR_ID_LOOKUP[code] = color_id - COLOR_ID_HISTORY.push(color_id) - color_id - end - end + COLOR_ID_LOOKUP = Hash.new + # e.g. code 000000 = id 0 + COLOR_ID_LOOKUP[DEFAULT_COLOR_CODE] = DEFAULT_COLOR_ID + COLOR_ID_LOOKUP[DEFAULT_BACKGROUND_COLOR_CODE] = DEFAULT_BACKGROUND_COLOR_ID + COLOR_ID_HISTORY = Array.new + for num in 0...Curses.colors + unless (num == DEFAULT_COLOR_ID) or (num == DEFAULT_BACKGROUND_COLOR_ID) + COLOR_ID_HISTORY.push(num) + end + end + + def get_color_id(code) + if (color_id = COLOR_ID_LOOKUP[code]) + color_id + else + color_id = COLOR_ID_HISTORY.shift + COLOR_ID_LOOKUP.delete_if { |_k, v| v == color_id } + sleep 0.01 # somehow this keeps Curses.init_color from failing sometimes + Curses.init_color(color_id, ((code[0..1].to_s.hex / 255.0) * 1000).round, ((code[2..3].to_s.hex / 255.0) * 1000).round, ((code[4..5].to_s.hex / 255.0) * 1000).round) + COLOR_ID_LOOKUP[code] = color_id + COLOR_ID_HISTORY.push(color_id) + color_id + end + end else - COLOR_CODE = [ '000000', '800000', '008000', '808000', '000080', '800080', '008080', 'c0c0c0', '808080', 'ff0000', '00ff00', 'ffff00', '0000ff', 'ff00ff', '00ffff', 'ffffff', '000000', '00005f', '000087', '0000af', '0000d7', '0000ff', '005f00', '005f5f', '005f87', '005faf', '005fd7', '005fff', '008700', '00875f', '008787', '0087af', '0087d7', '0087ff', '00af00', '00af5f', '00af87', '00afaf', '00afd7', '00afff', '00d700', '00d75f', '00d787', '00d7af', '00d7d7', '00d7ff', '00ff00', '00ff5f', '00ff87', '00ffaf', '00ffd7', '00ffff', '5f0000', '5f005f', '5f0087', '5f00af', '5f00d7', '5f00ff', '5f5f00', '5f5f5f', '5f5f87', '5f5faf', '5f5fd7', '5f5fff', '5f8700', '5f875f', '5f8787', '5f87af', '5f87d7', '5f87ff', '5faf00', '5faf5f', '5faf87', '5fafaf', '5fafd7', '5fafff', '5fd700', '5fd75f', '5fd787', '5fd7af', '5fd7d7', '5fd7ff', '5fff00', '5fff5f', '5fff87', '5fffaf', '5fffd7', '5fffff', '870000', '87005f', '870087', '8700af', '8700d7', '8700ff', '875f00', '875f5f', '875f87', '875faf', '875fd7', '875fff', '878700', '87875f', '878787', '8787af', '8787d7', '8787ff', '87af00', '87af5f', '87af87', '87afaf', '87afd7', '87afff', '87d700', '87d75f', '87d787', '87d7af', '87d7d7', '87d7ff', '87ff00', '87ff5f', '87ff87', '87ffaf', '87ffd7', '87ffff', 'af0000', 'af005f', 'af0087', 'af00af', 'af00d7', 'af00ff', 'af5f00', 'af5f5f', 'af5f87', 'af5faf', 'af5fd7', 'af5fff', 'af8700', 'af875f', 'af8787', 'af87af', 'af87d7', 'af87ff', 'afaf00', 'afaf5f', 'afaf87', 'afafaf', 'afafd7', 'afafff', 'afd700', 'afd75f', 'afd787', 'afd7af', 'afd7d7', 'afd7ff', 'afff00', 'afff5f', 'afff87', 'afffaf', 'afffd7', 'afffff', 'd70000', 'd7005f', 'd70087', 'd700af', 'd700d7', 'd700ff', 'd75f00', 'd75f5f', 'd75f87', 'd75faf', 'd75fd7', 'd75fff', 'd78700', 'd7875f', 'd78787', 'd787af', 'd787d7', 'd787ff', 'd7af00', 'd7af5f', 'd7af87', 'd7afaf', 'd7afd7', 'd7afff', 'd7d700', 'd7d75f', 'd7d787', 'd7d7af', 'd7d7d7', 'd7d7ff', 'd7ff00', 'd7ff5f', 'd7ff87', 'd7ffaf', 'd7ffd7', 'd7ffff', 'ff0000', 'ff005f', 'ff0087', 'ff00af', 'ff00d7', 'ff00ff', 'ff5f00', 'ff5f5f', 'ff5f87', 'ff5faf', 'ff5fd7', 'ff5fff', 'ff8700', 'ff875f', 'ff8787', 'ff87af', 'ff87d7', 'ff87ff', 'ffaf00', 'ffaf5f', 'ffaf87', 'ffafaf', 'ffafd7', 'ffafff', 'ffd700', 'ffd75f', 'ffd787', 'ffd7af', 'ffd7d7', 'ffd7ff', 'ffff00', 'ffff5f', 'ffff87', 'ffffaf', 'ffffd7', 'ffffff', '080808', '121212', '1c1c1c', '262626', '303030', '3a3a3a', '444444', '4e4e4e', '585858', '626262', '6c6c6c', '767676', '808080', '8a8a8a', '949494', '9e9e9e', 'a8a8a8', 'b2b2b2', 'bcbcbc', 'c6c6c6', 'd0d0d0', 'dadada', 'e4e4e4', 'eeeeee' ][0...Curses.colors] - COLOR_ID_LOOKUP = Hash.new - - def get_color_id(code) - if color_id = COLOR_ID_LOOKUP[code] - color_id - else - least_error = nil - least_error_id = nil - COLOR_CODE.each_index { |color_id| - error = ((COLOR_CODE[color_id][0..1].hex - code[0..1].hex)**2) + ((COLOR_CODE[color_id][2..3].hex - code[2..3].hex)**2) + ((COLOR_CODE[color_id][4..6].hex - code[4..6].hex)**2) - if least_error.nil? or (error < least_error) - least_error = error - least_error_id = color_id - end - } - COLOR_ID_LOOKUP[code] = least_error_id - least_error_id - end - end + COLOR_CODE = ['000000', '800000', '008000', '808000', '000080', '800080', '008080', 'c0c0c0', '808080', 'ff0000', '00ff00', 'ffff00', '0000ff', 'ff00ff', '00ffff', 'ffffff', '000000', '00005f', '000087', '0000af', '0000d7', '0000ff', '005f00', '005f5f', '005f87', '005faf', '005fd7', '005fff', '008700', '00875f', '008787', '0087af', '0087d7', '0087ff', '00af00', '00af5f', '00af87', '00afaf', '00afd7', '00afff', '00d700', '00d75f', '00d787', '00d7af', '00d7d7', '00d7ff', '00ff00', '00ff5f', '00ff87', '00ffaf', '00ffd7', '00ffff', '5f0000', '5f005f', '5f0087', '5f00af', '5f00d7', '5f00ff', '5f5f00', '5f5f5f', '5f5f87', '5f5faf', '5f5fd7', '5f5fff', '5f8700', '5f875f', '5f8787', '5f87af', '5f87d7', '5f87ff', '5faf00', '5faf5f', '5faf87', '5fafaf', '5fafd7', '5fafff', '5fd700', '5fd75f', '5fd787', '5fd7af', '5fd7d7', '5fd7ff', '5fff00', '5fff5f', '5fff87', '5fffaf', '5fffd7', '5fffff', '870000', '87005f', '870087', '8700af', '8700d7', '8700ff', '875f00', '875f5f', '875f87', '875faf', '875fd7', '875fff', '878700', '87875f', '878787', '8787af', '8787d7', '8787ff', '87af00', '87af5f', '87af87', '87afaf', '87afd7', '87afff', '87d700', '87d75f', '87d787', '87d7af', '87d7d7', '87d7ff', '87ff00', '87ff5f', '87ff87', '87ffaf', '87ffd7', '87ffff', 'af0000', 'af005f', 'af0087', 'af00af', 'af00d7', 'af00ff', 'af5f00', 'af5f5f', 'af5f87', 'af5faf', 'af5fd7', 'af5fff', 'af8700', 'af875f', 'af8787', 'af87af', 'af87d7', 'af87ff', 'afaf00', 'afaf5f', 'afaf87', 'afafaf', 'afafd7', 'afafff', 'afd700', 'afd75f', 'afd787', 'afd7af', 'afd7d7', 'afd7ff', 'afff00', 'afff5f', 'afff87', 'afffaf', 'afffd7', 'afffff', 'd70000', 'd7005f', 'd70087', 'd700af', 'd700d7', 'd700ff', 'd75f00', 'd75f5f', 'd75f87', 'd75faf', 'd75fd7', 'd75fff', 'd78700', 'd7875f', 'd78787', 'd787af', 'd787d7', 'd787ff', 'd7af00', 'd7af5f', 'd7af87', 'd7afaf', 'd7afd7', 'd7afff', 'd7d700', 'd7d75f', 'd7d787', 'd7d7af', 'd7d7d7', 'd7d7ff', 'd7ff00', 'd7ff5f', 'd7ff87', 'd7ffaf', 'd7ffd7', 'd7ffff', 'ff0000', 'ff005f', 'ff0087', 'ff00af', 'ff00d7', 'ff00ff', 'ff5f00', 'ff5f5f', 'ff5f87', 'ff5faf', 'ff5fd7', 'ff5fff', 'ff8700', 'ff875f', 'ff8787', 'ff87af', 'ff87d7', 'ff87ff', 'ffaf00', 'ffaf5f', 'ffaf87', 'ffafaf', 'ffafd7', 'ffafff', 'ffd700', 'ffd75f', 'ffd787', 'ffd7af', 'ffd7d7', 'ffd7ff', 'ffff00', 'ffff5f', 'ffff87', 'ffffaf', 'ffffd7', 'ffffff', '080808', '121212', '1c1c1c', '262626', '303030', '3a3a3a', '444444', '4e4e4e', '585858', '626262', '6c6c6c', '767676', '808080', '8a8a8a', '949494', '9e9e9e', 'a8a8a8', 'b2b2b2', 'bcbcbc', 'c6c6c6', 'd0d0d0', 'dadada', 'e4e4e4', 'eeeeee'][0...Curses.colors] + COLOR_ID_LOOKUP = Hash.new + + def get_color_id(code) + if (color_id = COLOR_ID_LOOKUP[code]) + color_id + else + least_error = nil + least_error_id = nil + COLOR_CODE.each_index { |color_id| + error = ((COLOR_CODE[color_id][0..1].hex - code[0..1].hex)**2) + ((COLOR_CODE[color_id][2..3].hex - code[2..3].hex)**2) + ((COLOR_CODE[color_id][4..6].hex - code[4..6].hex)**2) + if least_error.nil? or (error < least_error) + least_error = error + least_error_id = color_id + end + } + COLOR_ID_LOOKUP[code] = least_error_id + least_error_id + end + end end -#COLOR_PAIR_LIST = Array.new -#for num in 1...Curses::color_pairs -# COLOR_PAIR_LIST.push h={ :color_id => nil, :background_id => nil, :id => num } -#end +# COLOR_PAIR_LIST = Array.new +# for num in 1...Curses::color_pairs +# COLOR_PAIR_LIST.push h={ :color_id => nil, :background_id => nil, :id => num } +# end -#157+12+1 = 180 -#38+1+6 = 45 -#32767 +# 157+12+1 = 180 +# 38+1+6 = 45 +# 32767 COLOR_PAIR_ID_LOOKUP = Hash.new COLOR_PAIR_HISTORY = Array.new @@ -325,35 +355,40 @@ def get_color_id(code) # 500 = black and some underline for num in 1...Curses::color_pairs # fixme: things go to hell at about pair 256 -#for num in 1...([Curses::color_pairs, 256].min) - COLOR_PAIR_HISTORY.push(num) + # for num in 1...([Curses::color_pairs, 256].min) + COLOR_PAIR_HISTORY.push(num) end def get_color_pair_id(fg_code, bg_code) - if fg_code.nil? - fg_id = DEFAULT_COLOR_ID - else - fg_id = get_color_id(fg_code) - end - if bg_code.nil? - bg_id = DEFAULT_BACKGROUND_COLOR_ID - else - bg_id = get_color_id(bg_code) - end - if (COLOR_PAIR_ID_LOOKUP[fg_id]) and (color_pair_id = COLOR_PAIR_ID_LOOKUP[fg_id][bg_id]) - color_pair_id - else - color_pair_id = COLOR_PAIR_HISTORY.shift - COLOR_PAIR_ID_LOOKUP.each { |w,x| x.delete_if { |y,z| z == color_pair_id } } - sleep 0.01 - Curses.init_pair(color_pair_id, fg_id, bg_id) - COLOR_PAIR_ID_LOOKUP[fg_id] ||= Hash.new - COLOR_PAIR_ID_LOOKUP[fg_id][bg_id] = color_pair_id - COLOR_PAIR_HISTORY.push(color_pair_id) - color_pair_id - end + if fg_code.nil? + fg_id = DEFAULT_COLOR_ID + else + fg_id = get_color_id(fg_code) + end + if bg_code.nil? + bg_id = DEFAULT_BACKGROUND_COLOR_ID + else + bg_id = get_color_id(bg_code) + end + if (COLOR_PAIR_ID_LOOKUP[fg_id]) and (color_pair_id = COLOR_PAIR_ID_LOOKUP[fg_id][bg_id]) + color_pair_id + else + color_pair_id = COLOR_PAIR_HISTORY.shift + COLOR_PAIR_ID_LOOKUP.each { |_w, x| x.delete_if { |_y, z| z == color_pair_id } } + sleep 0.01 + Curses.init_pair(color_pair_id, fg_id, bg_id) + COLOR_PAIR_ID_LOOKUP[fg_id] ||= Hash.new + COLOR_PAIR_ID_LOOKUP[fg_id][bg_id] = color_pair_id + COLOR_PAIR_HISTORY.push(color_pair_id) + color_pair_id + end end +# Previously we weren't setting bkgd so it's no wonder it didn't seem to work +# Had to put this down here under the get_color_pair_id definition +Curses.bkgd(Curses.color_pair(get_color_pair_id(nil, nil))) +Curses.refresh + # Implement support for basic readline-style kill and yank (cut and paste) # commands. Successive calls to delete_word, backspace_word, kill_forward, and # kill_line will accumulate text into the kill_buffer as long as no other @@ -366,820 +401,835 @@ def get_color_pair_id(fg_code, bg_code) kill_last = '' kill_last_pos = 0 kill_before = proc { - if kill_last != command_buffer || kill_last_pos != command_buffer_pos - kill_buffer = '' - kill_original = command_buffer - end + if kill_last != command_buffer || kill_last_pos != command_buffer_pos + kill_buffer = '' + kill_original = command_buffer + end } kill_after = proc { - kill_last = command_buffer.dup - kill_last_pos = command_buffer_pos + kill_last = command_buffer.dup + kill_last_pos = command_buffer_pos } fix_layout_number = proc { |str| - str = str.gsub('lines', Curses.lines.to_s).gsub('cols', Curses.cols.to_s) - str.untaint - begin - proc { $SAFE = 1; eval(str) }.call.to_i - rescue - $stderr.puts $! - $stderr.puts $!.backtrace[0..1] - 0 - end + str = str.gsub('lines', Curses.lines.to_s).gsub('cols', Curses.cols.to_s) + begin + proc { eval(str) }.call.to_i + rescue + $stderr.puts $! + $stderr.puts $!.backtrace[0..1] + 0 + end } load_layout = proc { |layout_id| - if xml = LAYOUT[layout_id] - old_windows = IndicatorWindow.list | TextWindow.list | CountdownWindow.list | ProgressWindow.list - - previous_indicator_handler = indicator_handler - indicator_handler = Hash.new - - previous_stream_handler = stream_handler - stream_handler = Hash.new - - previous_progress_handler = progress_handler - progress_handler = Hash.new - - previous_countdown_handler = countdown_handler - progress_handler = Hash.new - - xml.elements.each { |e| - if e.name == 'window' - height, width, top, left = fix_layout_number.call( - e.attributes['height']), - fix_layout_number.call(e.attributes['width']), - fix_layout_number.call(e.attributes['top']), - fix_layout_number.call(e.attributes['left']) - - if (height > 0) and (width > 0) and (top >= 0) and (left >= 0) and (top < Curses.lines) and (left < Curses.cols) - if e.attributes['class'] == 'indicator' - if e.attributes['value'] and (window = previous_indicator_handler[e.attributes['value']]) - previous_indicator_handler[e.attributes['value']] = nil - old_windows.delete(window) - else - window = IndicatorWindow.new(height, width, top, left) - end - window.layout = [ e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left'] ] - window.scrollok(false) - window.label = e.attributes['label'] if e.attributes['label'] - window.fg = e.attributes['fg'].split(',') - .collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['fg'] - window.bg = e.attributes['bg'].split(',') - .collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['bg'] - if e.attributes['value'] - indicator_handler[e.attributes['value']] = window - end - window.redraw - elsif e.attributes['class'] == 'text' - if width > 1 - if e.attributes['value'] and (window = previous_stream_handler[previous_stream_handler.keys.find { |key| e.attributes['value'].split(',').include?(key) }]) - previous_stream_handler[e.attributes['value']] = nil - old_windows.delete(window) - else - window = TextWindow.new(height, width - 1, top, left) - window.scrollbar = Curses::Window.new(window.maxy, 1, window.begy, window.begx + window.maxx) - end - window.layout = [ e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left'] ] - window.scrollok(true) - window.max_buffer_size = e.attributes['buffer-size'] || 1000 - e.attributes['value'].split(',').each { |str| - stream_handler[str] = window - } - end - elsif e.attributes['class'] == 'countdown' - if e.attributes['value'] and (window = previous_countdown_handler[e.attributes['value']]) - previous_countdown_handler[e.attributes['value']] = nil - old_windows.delete(window) - else - window = CountdownWindow.new(height, width, top, left) - end - window.layout = [ e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left'] ] - window.scrollok(false) - window.label = e.attributes['label'] if e.attributes['label'] - window.fg = e.attributes['fg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['fg'] - window.bg = e.attributes['bg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['bg'] - if e.attributes['value'] - countdown_handler[e.attributes['value']] = window - end - window.update - elsif e.attributes['class'] == 'progress' - if e.attributes['value'] and (window = previous_progress_handler[e.attributes['value']]) - previous_progress_handler[e.attributes['value']] = nil - old_windows.delete(window) - else - window = ProgressWindow.new(height, width, top, left) - end - window.layout = [ e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left'] ] - window.scrollok(false) - window.label = e.attributes['label'] if e.attributes['label'] - window.fg = e.attributes['fg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['fg'] - window.bg = e.attributes['bg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['bg'] - if e.attributes['value'] - progress_handler[e.attributes['value']] = window - end - window.redraw - elsif e.attributes['class'] == 'command' - unless command_window - command_window = Curses::Window.new(height, width, top, left) - end - command_window_layout = [ e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left'] ] - command_window.scrollok(false) - command_window.keypad(true) - end - end - end - } - if current_scroll_window = TextWindow.list[0] - current_scroll_window.update_scrollbar - end - for window in old_windows - IndicatorWindow.list.delete(window) - TextWindow.list.delete(window) - CountdownWindow.list.delete(window) - ProgressWindow.list.delete(window) - if window.class == TextWindow - window.scrollbar.close - end - window.close - end - Curses.doupdate - end + if (xml = LAYOUT[layout_id]) + old_windows = IndicatorWindow.list | TextWindow.list | CountdownWindow.list | ProgressWindow.list + + previous_indicator_handler = indicator_handler + indicator_handler = Hash.new + + previous_stream_handler = stream_handler + stream_handler = Hash.new + + previous_progress_handler = progress_handler + progress_handler = Hash.new + + previous_countdown_handler = countdown_handler + progress_handler = Hash.new + + xml.elements.each { |e| + if e.name == 'window' + height, width, top, left = fix_layout_number.call( + e.attributes['height'] + ), + fix_layout_number.call(e.attributes['width']), + fix_layout_number.call(e.attributes['top']), + fix_layout_number.call(e.attributes['left']) + + if (height > 0) and (width > 0) and (top >= 0) and (left >= 0) and (top < Curses.lines) and (left < Curses.cols) + if e.attributes['class'] == 'indicator' + if e.attributes['value'] and (window = previous_indicator_handler[e.attributes['value']]) + previous_indicator_handler[e.attributes['value']] = nil + old_windows.delete(window) + else + window = IndicatorWindow.new(height, width, top, left) + window.bkgd(Curses.color_pair(get_color_pair_id(nil, nil))) + end + window.layout = [e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left']] + window.scrollok(false) + window.label = e.attributes['label'] if e.attributes['label'] + window.fg = e.attributes['fg'].split(',') + .collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['fg'] + window.bg = e.attributes['bg'].split(',') + .collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['bg'] + if e.attributes['value'] + indicator_handler[e.attributes['value']] = window + end + window.redraw + elsif e.attributes['class'] == 'text' + if width > 1 + if e.attributes['value'] and (window = previous_stream_handler[previous_stream_handler.keys.find { |key| e.attributes['value'].split(',').include?(key) }]) + previous_stream_handler[e.attributes['value']] = nil + old_windows.delete(window) + else + window = TextWindow.new(height, width - 1, top, left) + window.bkgd(Curses.color_pair(get_color_pair_id(nil, nil))) + window.scrollbar = Curses::Window.new(window.maxy, 1, window.begy, window.begx + window.maxx) + window.scrollbar.bkgd(Curses.color_pair(get_color_pair_id(nil, nil))) + end + window.layout = [e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left']] + window.scrollok(true) + window.max_buffer_size = e.attributes['buffer-size'] || 1000 + window.time_stamp = e.attributes['timestamp'] + e.attributes['value'].split(',').each { |str| + stream_handler[str] = window + } + end + elsif e.attributes['class'] == 'exp' + stream_handler['exp'] = ExpWindow.new(height, width - 1, top, left) + stream_handler['exp'].bkgd(Curses.color_pair(get_color_pair_id(nil, nil))) + elsif e.attributes['class'] == 'percWindow' + stream_handler['percWindow'] = PercWindow.new(height, width - 1, top, left) + stream_handler['percWindow'].bkgd(Curses.color_pair(get_color_pair_id(nil, nil))) + elsif e.attributes['class'] == 'countdown' + if e.attributes['value'] and (window = previous_countdown_handler[e.attributes['value']]) + previous_countdown_handler[e.attributes['value']] = nil + old_windows.delete(window) + else + window = CountdownWindow.new(height, width, top, left) + window.bkgd(Curses.color_pair(get_color_pair_id(nil, nil))) + end + window.layout = [e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left']] + window.scrollok(false) + window.label = e.attributes['label'] if e.attributes['label'] + window.fg = e.attributes['fg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['fg'] + window.bg = e.attributes['bg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['bg'] + if e.attributes['value'] + countdown_handler[e.attributes['value']] = window + end + window.update + elsif e.attributes['class'] == 'progress' + if e.attributes['value'] and (window = previous_progress_handler[e.attributes['value']]) + previous_progress_handler[e.attributes['value']] = nil + old_windows.delete(window) + else + window = ProgressWindow.new(height, width, top, left) + window.bkgd(Curses.color_pair(get_color_pair_id(nil, nil))) + end + window.layout = [e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left']] + window.scrollok(false) + window.label = e.attributes['label'] if e.attributes['label'] + window.fg = e.attributes['fg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['fg'] + window.bg = e.attributes['bg'].split(',').collect { |val| if val == 'nil'; nil; else; val; end } if e.attributes['bg'] + if e.attributes['value'] + progress_handler[e.attributes['value']] = window + end + window.redraw + elsif e.attributes['class'] == 'command' + unless command_window + command_window = Curses::Window.new(height, width, top, left) + command_window.bkgd(Curses.color_pair(get_color_pair_id(nil, nil))) + end + command_window_layout = [e.attributes['height'], e.attributes['width'], e.attributes['top'], e.attributes['left']] + command_window.scrollok(false) + command_window.keypad(true) + end + end + end + } + if (current_scroll_window = TextWindow.list[0]) + current_scroll_window.update_scrollbar + end + for window in old_windows + IndicatorWindow.list.delete(window) + TextWindow.list.delete(window) + CountdownWindow.list.delete(window) + ProgressWindow.list.delete(window) + if window.class == TextWindow + window.scrollbar.close + end + window.close + end + Curses.doupdate + end } do_macro = nil -setup_key = proc { |xml,binding| - if key = xml.attributes['id'] - if key =~ /^[0-9]+$/ - key = key.to_i - elsif (key.class) == String and (key.length == 1) - nil - else - key = key_name[key] - end - if key - if macro = xml.attributes['macro'] - binding[key] = proc { do_macro.call(macro) } - elsif xml.attributes['action'] and action = key_action[xml.attributes['action']] - binding[key] = action - else - binding[key] ||= Hash.new - xml.elements.each { |e| - setup_key.call(e, binding[key]) - } - end - end - end +setup_key = proc { |xml, binding| + if (key = xml.attributes['id']) + if key =~ /^[0-9]+$/ + key = key.to_i + elsif (key.class) == String and (key.length == 1) + nil + else + key = key_name[key] + end + if key + if (macro = xml.attributes['macro']) + binding[key] = proc { do_macro.call(macro) } + elsif xml.attributes['action'] and (action = key_action[xml.attributes['action']]) + binding[key] = action + else + binding[key] ||= Hash.new + xml.elements.each { |e| + setup_key.call(e, binding[key]) + } + end + end + end } load_settings_file = proc { |reload| - SETTINGS_LOCK.synchronize { - begin - xml = Hilite.load(file: SETTINGS_FILENAME, flush: reload) - unless reload - xml.elements.each { |e| - # These are things that we ignore if we're doing a reload of the settings file - if e.name == 'preset' - PRESET[e.attributes['id']] = [ e.attributes['fg'], e.attributes['bg'] ] - elsif (e.name == 'layout') and (layout_id = e.attributes['id']) - LAYOUT[layout_id] = e - elsif e.name == 'key' - setup_key.call(e, key_binding) - end - } - end - rescue - Profanity.log $! - Profanity.log $!.backtrace[0..1] - end - } + SETTINGS_LOCK.synchronize { + begin + xml = Hilite.load(file: SETTINGS_FILENAME, flush: reload) + unless reload + xml.elements.each { |e| + # These are things that we ignore if we're doing a reload of the settings file + if e.name == 'preset' + PRESET[e.attributes['id']] = [e.attributes['fg'], e.attributes['bg']] + elsif (e.name == 'layout') and (layout_id = e.attributes['id']) + LAYOUT[layout_id] = e + elsif e.name == 'key' + setup_key.call(e, key_binding) + end + } + end + rescue + Profanity.log $! + Profanity.log $!.backtrace[0..1] + end + } } command_window_put_ch = proc { |ch| - if (command_buffer_pos - command_buffer_offset + 1) >= command_window.maxx - command_window.setpos(0,0) - command_window.delch - command_buffer_offset += 1 - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - end - command_buffer.insert(command_buffer_pos, ch) - command_buffer_pos += 1 - command_window.insch(ch) - command_window.setpos(0, command_buffer_pos - command_buffer_offset) + if (command_buffer_pos - command_buffer_offset + 1) >= command_window.maxx + command_window.setpos(0, 0) + command_window.delch + command_buffer_offset += 1 + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + end + command_buffer.insert(command_buffer_pos, ch) + command_buffer_pos += 1 + command_window.insch(ch) + command_window.setpos(0, command_buffer_pos - command_buffer_offset) } do_macro = proc { |macro| - # fixme: gsub %whatever - backslash = false - at_pos = nil - backfill = nil - macro.split('').each_with_index { |ch, i| - if backslash - if ch == '\\' - command_window_put_ch.call('\\') - elsif ch == 'x' - command_buffer.clear - command_buffer_pos = 0 - command_buffer_offset = 0 - command_window.deleteln - command_window.setpos(0,0) - elsif ch == 'r' - at_pos = nil - key_action['send_command'].call - elsif ch == '@' - command_window_put_ch.call('@') - elsif ch == '?' - backfill = i - 3 - else - nil - end - backslash = false - else - if ch == '\\' - backslash = true - elsif ch == '@' - at_pos = command_buffer_pos - else - command_window_put_ch.call(ch) - end - end - } - if at_pos - while at_pos < command_buffer_pos - key_action['cursor_left'].call - end - while at_pos > command_buffer_pos - key_action['cursor_right'].call - end - end - command_window.noutrefresh - if backfill then - command_window.setpos(0,backfill) - command_buffer_pos = backfill - backfill = nil - end - Curses.doupdate + # fixme: gsub %whatever + backslash = false + at_pos = nil + backfill = nil + macro.split('').each_with_index { |ch, i| + if backslash + if ch == '\\' + command_window_put_ch.call('\\') + elsif ch == 'x' + command_buffer.clear + command_buffer_pos = 0 + command_buffer_offset = 0 + command_window.deleteln + command_window.setpos(0, 0) + elsif ch == 'r' + at_pos = nil + key_action['send_command'].call + elsif ch == '@' + command_window_put_ch.call('@') + elsif ch == '?' + backfill = i - 3 + else + nil + end + backslash = false + else + if ch == '\\' + backslash = true + elsif ch == '@' + at_pos = command_buffer_pos + else + command_window_put_ch.call(ch) + end + end + } + if at_pos + while at_pos < command_buffer_pos + key_action['cursor_left'].call + end + while at_pos > command_buffer_pos + key_action['cursor_right'].call + end + end + command_window.noutrefresh + if backfill then + command_window.setpos(0, backfill) + command_buffer_pos = backfill + backfill = nil + end + Curses.doupdate } key_action['resize'] = proc { - # fixme: re-word-wrap - window = Window.new(0,0,0,0) - window.refresh - window.close - first_text_window = true - for window in TextWindow.list.to_a - window.resize(fix_layout_number.call(window.layout[0]), fix_layout_number.call(window.layout[1]) - 1) - window.move(fix_layout_number.call(window.layout[2]), fix_layout_number.call(window.layout[3])) - window.scrollbar.resize(window.maxy, 1) - window.scrollbar.move(window.begy, window.begx + window.maxx) - window.scroll(-window.maxy) - window.scroll(window.maxy) - window.clear_scrollbar - if first_text_window - window.update_scrollbar - first_text_window = false - end - window.noutrefresh - end - for window in [ IndicatorWindow.list.to_a, ProgressWindow.list.to_a, CountdownWindow.list.to_a ].flatten - window.resize(fix_layout_number.call(window.layout[0]), fix_layout_number.call(window.layout[1])) - window.move(fix_layout_number.call(window.layout[2]), fix_layout_number.call(window.layout[3])) - window.noutrefresh - end - if command_window - command_window.resize(fix_layout_number.call(command_window_layout[0]), fix_layout_number.call(command_window_layout[1])) - command_window.move(fix_layout_number.call(command_window_layout[2]), fix_layout_number.call(command_window_layout[3])) - command_window.noutrefresh - end - Curses.doupdate + # fixme: re-word-wrap + Curses.clear + Curses.refresh + + first_text_window = true + for window in TextWindow.list.to_a + window.resize(fix_layout_number.call(window.layout[0]), fix_layout_number.call(window.layout[1]) - 1) + window.move(fix_layout_number.call(window.layout[2]), fix_layout_number.call(window.layout[3])) + window.scrollbar.resize(window.maxy, 1) + window.scrollbar.move(window.begy, window.begx + window.maxx) + window.scroll(-window.maxy) + window.scroll(window.maxy) + window.clear_scrollbar + if first_text_window + window.update_scrollbar + first_text_window = false + end + window.noutrefresh + end + for window in [IndicatorWindow.list.to_a, ProgressWindow.list.to_a, CountdownWindow.list.to_a].flatten + window.resize(fix_layout_number.call(window.layout[0]), fix_layout_number.call(window.layout[1])) + window.move(fix_layout_number.call(window.layout[2]), fix_layout_number.call(window.layout[3])) + window.noutrefresh + end + if command_window + command_window.resize(fix_layout_number.call(command_window_layout[0]), fix_layout_number.call(command_window_layout[1])) + command_window.move(fix_layout_number.call(command_window_layout[2]), fix_layout_number.call(command_window_layout[3])) + command_window.noutrefresh + end + Curses.doupdate } key_action['cursor_left'] = proc { - if (command_buffer_offset > 0) and (command_buffer_pos - command_buffer_offset == 0) - command_buffer_pos -= 1 - command_buffer_offset -= 1 - command_window.insch(command_buffer[command_buffer_pos]) - else - command_buffer_pos = [command_buffer_pos - 1, 0].max - end - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - command_window.noutrefresh - Curses.doupdate + if (command_buffer_offset > 0) and (command_buffer_pos - command_buffer_offset == 0) + command_buffer_pos -= 1 + command_buffer_offset -= 1 + command_window.insch(command_buffer[command_buffer_pos]) + else + command_buffer_pos = [command_buffer_pos - 1, 0].max + end + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + command_window.noutrefresh + Curses.doupdate } key_action['cursor_right'] = proc { - if ((command_buffer.length - command_buffer_offset) >= (command_window.maxx - 1)) and (command_buffer_pos - command_buffer_offset + 1) >= command_window.maxx - if command_buffer_pos < command_buffer.length - command_window.setpos(0,0) - command_window.delch - command_buffer_offset += 1 - command_buffer_pos += 1 - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - unless command_buffer_pos >= command_buffer.length - command_window.insch(command_buffer[command_buffer_pos]) - end - end - else - command_buffer_pos = [command_buffer_pos + 1, command_buffer.length].min - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - end - command_window.noutrefresh - Curses.doupdate + if ((command_buffer.length - command_buffer_offset) >= (command_window.maxx - 1)) and (command_buffer_pos - command_buffer_offset + 1) >= command_window.maxx + if command_buffer_pos < command_buffer.length + command_window.setpos(0, 0) + command_window.delch + command_buffer_offset += 1 + command_buffer_pos += 1 + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + unless command_buffer_pos >= command_buffer.length + command_window.insch(command_buffer[command_buffer_pos]) + end + end + else + command_buffer_pos = [command_buffer_pos + 1, command_buffer.length].min + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + end + command_window.noutrefresh + Curses.doupdate } key_action['cursor_word_left'] = proc { - if command_buffer_pos > 0 - if m = command_buffer[0...(command_buffer_pos-1)].match(/.*(\w[^\w\s]|\W\w|\s\S)/) - new_pos = m.begin(1) + 1 - else - new_pos = 0 - end - if (command_buffer_offset > new_pos) - command_window.setpos(0, 0) - command_buffer[new_pos, (command_buffer_offset - new_pos)].split('').reverse.each { |ch| command_window.insch(ch) } - command_buffer_pos = new_pos - command_buffer_offset = new_pos - else - command_buffer_pos = new_pos - end - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - command_window.noutrefresh - Curses.doupdate - end + if command_buffer_pos > 0 + if (m = command_buffer[0...(command_buffer_pos - 1)].match(/.*(\w[^\w\s]|\W\w|\s\S)/)) + new_pos = m.begin(1) + 1 + else + new_pos = 0 + end + if (command_buffer_offset > new_pos) + command_window.setpos(0, 0) + command_buffer[new_pos, (command_buffer_offset - new_pos)].split('').reverse.each { |ch| command_window.insch(ch) } + command_buffer_pos = new_pos + command_buffer_offset = new_pos + else + command_buffer_pos = new_pos + end + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + command_window.noutrefresh + Curses.doupdate + end } key_action['cursor_word_right'] = proc { - if command_buffer_pos < command_buffer.length - if m = command_buffer[command_buffer_pos..-1].match(/\w[^\w\s]|\W\w|\s\S/) - new_pos = command_buffer_pos + m.begin(0) + 1 - else - new_pos = command_buffer.length - end - overflow = new_pos - command_window.maxx - command_buffer_offset + 1 - if overflow > 0 - command_window.setpos(0,0) - overflow.times { - command_window.delch - command_buffer_offset += 1 - } - command_window.setpos(0, command_window.maxx - overflow) - command_window.addstr command_buffer[(command_window.maxx - overflow + command_buffer_offset),overflow] - end - command_buffer_pos = new_pos - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - command_window.noutrefresh - Curses.doupdate - end + if command_buffer_pos < command_buffer.length + if (m = command_buffer[command_buffer_pos..-1].match(/\w[^\w\s]|\W\w|\s\S/)) + new_pos = command_buffer_pos + m.begin(0) + 1 + else + new_pos = command_buffer.length + end + overflow = new_pos - command_window.maxx - command_buffer_offset + 1 + if overflow > 0 + command_window.setpos(0, 0) + overflow.times { + command_window.delch + command_buffer_offset += 1 + } + command_window.setpos(0, command_window.maxx - overflow) + command_window.addstr command_buffer[(command_window.maxx - overflow + command_buffer_offset), overflow] + end + command_buffer_pos = new_pos + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + command_window.noutrefresh + Curses.doupdate + end } key_action['cursor_home'] = proc { - command_buffer_pos = 0 - command_window.setpos(0, 0) - for num in 1..command_buffer_offset - begin - command_window.insch(command_buffer[command_buffer_offset-num]) - rescue - Profanity.log_file { |f| - f.puts "command_buffer: #{command_buffer.inspect}"; - f.puts "command_buffer_offset: #{command_buffer_offset.inspect}"; - f.puts "num: #{num.inspect}"; - f.puts $!; - f.puts $!.backtrace[0...4] - } - exit - end - end - command_buffer_offset = 0 - command_window.noutrefresh - Curses.doupdate + command_buffer_pos = 0 + command_window.setpos(0, 0) + for num in 1..command_buffer_offset + begin + command_window.insch(command_buffer[command_buffer_offset - num]) + rescue + Profanity.log_file { |f| + f.puts "command_buffer: #{command_buffer.inspect}"; + f.puts "command_buffer_offset: #{command_buffer_offset.inspect}"; + f.puts "num: #{num.inspect}"; + f.puts $!; + f.puts $!.backtrace[0...4] + } + exit + end + end + command_buffer_offset = 0 + command_window.noutrefresh + Curses.doupdate } key_action['cursor_end'] = proc { - if command_buffer.length < (command_window.maxx - 1) - command_buffer_pos = command_buffer.length - command_window.setpos(0, command_buffer_pos) - else - scroll_left_num = command_buffer.length - command_window.maxx + 1 - command_buffer_offset - command_window.setpos(0, 0) - scroll_left_num.times { - command_window.delch - command_buffer_offset += 1 - } - command_buffer_pos = command_buffer_offset + command_window.maxx - 1 - scroll_left_num - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - scroll_left_num.times { - command_window.addch(command_buffer[command_buffer_pos]) - command_buffer_pos += 1 - } - end - command_window.noutrefresh - Curses.doupdate + if command_buffer.length < (command_window.maxx - 1) + command_buffer_pos = command_buffer.length + command_window.setpos(0, command_buffer_pos) + else + scroll_left_num = command_buffer.length - command_window.maxx + 1 - command_buffer_offset + command_window.setpos(0, 0) + scroll_left_num.times { + command_window.delch + command_buffer_offset += 1 + } + command_buffer_pos = command_buffer_offset + command_window.maxx - 1 - scroll_left_num + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + scroll_left_num.times { + command_window.addch(command_buffer[command_buffer_pos]) + command_buffer_pos += 1 + } + end + command_window.noutrefresh + Curses.doupdate } key_action['cursor_backspace'] = proc { - if command_buffer_pos > 0 - command_buffer_pos -= 1 - if command_buffer_pos == 0 - command_buffer = command_buffer[(command_buffer_pos+1)..-1] - else - command_buffer = command_buffer[0..(command_buffer_pos-1)] + command_buffer[(command_buffer_pos+1)..-1] - end - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - command_window.delch - if (command_buffer.length - command_buffer_offset + 1) > command_window.maxx - command_window.setpos(0, command_window.maxx - 1) - command_window.addch command_buffer[command_window.maxx - command_buffer_offset - 1] - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - end - command_window.noutrefresh - Curses.doupdate - end + if command_buffer_pos > 0 + command_buffer_pos -= 1 + if command_buffer_pos == 0 + command_buffer = command_buffer[(command_buffer_pos + 1)..-1] + else + command_buffer = command_buffer[0..(command_buffer_pos - 1)] + command_buffer[(command_buffer_pos + 1)..-1] + end + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + command_window.delch + if (command_buffer.length - command_buffer_offset + 1) > command_window.maxx + command_window.setpos(0, command_window.maxx - 1) + command_window.addch command_buffer[command_window.maxx - command_buffer_offset - 1] + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + end + command_window.noutrefresh + Curses.doupdate + end } key_action['cursor_delete'] = proc { - if (command_buffer.length > 0) and (command_buffer_pos < command_buffer.length) - if command_buffer_pos == 0 - command_buffer = command_buffer[(command_buffer_pos+1)..-1] - elsif command_buffer_pos < command_buffer.length - command_buffer = command_buffer[0..(command_buffer_pos-1)] + command_buffer[(command_buffer_pos+1)..-1] - end - command_window.delch - if (command_buffer.length - command_buffer_offset + 1) > command_window.maxx - command_window.setpos(0, command_window.maxx - 1) - command_window.addch command_buffer[command_window.maxx - command_buffer_offset - 1] - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - end - command_window.noutrefresh - Curses.doupdate - end + if (command_buffer.length > 0) and (command_buffer_pos < command_buffer.length) + if command_buffer_pos == 0 + command_buffer = command_buffer[(command_buffer_pos + 1)..-1] + elsif command_buffer_pos < command_buffer.length + command_buffer = command_buffer[0..(command_buffer_pos - 1)] + command_buffer[(command_buffer_pos + 1)..-1] + end + command_window.delch + if (command_buffer.length - command_buffer_offset + 1) > command_window.maxx + command_window.setpos(0, command_window.maxx - 1) + command_window.addch command_buffer[command_window.maxx - command_buffer_offset - 1] + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + end + command_window.noutrefresh + Curses.doupdate + end } key_action['cursor_backspace_word'] = proc { - num_deleted = 0 - deleted_alnum = false - deleted_nonspace = false - while command_buffer_pos > 0 do - next_char = command_buffer[command_buffer_pos - 1] - if num_deleted == 0 || (!deleted_alnum && next_char.punct?) || (!deleted_nonspace && next_char.space?) || next_char.alnum? - deleted_alnum = deleted_alnum || next_char.alnum? - deleted_nonspace = !next_char.space? - num_deleted += 1 - kill_before.call - kill_buffer = next_char + kill_buffer - key_action['cursor_backspace'].call - kill_after.call - else - break - end - end + num_deleted = 0 + deleted_alnum = false + deleted_nonspace = false + while command_buffer_pos > 0 do + next_char = command_buffer[command_buffer_pos - 1] + if num_deleted == 0 || (!deleted_alnum && next_char.punct?) || (!deleted_nonspace && next_char.space?) || next_char.alnum? + deleted_alnum = deleted_alnum || next_char.alnum? + deleted_nonspace = !next_char.space? + num_deleted += 1 + kill_before.call + kill_buffer = next_char + kill_buffer + key_action['cursor_backspace'].call + kill_after.call + else + break + end + end } key_action['cursor_delete_word'] = proc { - num_deleted = 0 - deleted_alnum = false - deleted_nonspace = false - while command_buffer_pos < command_buffer.length do - next_char = command_buffer[command_buffer_pos] - if num_deleted == 0 || (!deleted_alnum && next_char.punct?) || (!deleted_nonspace && next_char.space?) || next_char.alnum? - deleted_alnum = deleted_alnum || next_char.alnum? - deleted_nonspace = !next_char.space? - num_deleted += 1 - kill_before.call - kill_buffer = kill_buffer + next_char - key_action['cursor_delete'].call - kill_after.call - else - break - end - end + num_deleted = 0 + deleted_alnum = false + deleted_nonspace = false + while command_buffer_pos < command_buffer.length do + next_char = command_buffer[command_buffer_pos] + if num_deleted == 0 || (!deleted_alnum && next_char.punct?) || (!deleted_nonspace && next_char.space?) || next_char.alnum? + deleted_alnum = deleted_alnum || next_char.alnum? + deleted_nonspace = !next_char.space? + num_deleted += 1 + kill_before.call + kill_buffer = kill_buffer + next_char + key_action['cursor_delete'].call + kill_after.call + else + break + end + end } key_action['cursor_kill_forward'] = proc { - if command_buffer_pos < command_buffer.length - kill_before.call - if command_buffer_pos == 0 - kill_buffer = kill_buffer + command_buffer - command_buffer = '' - else - kill_buffer = kill_buffer + command_buffer[command_buffer_pos..-1] - command_buffer = command_buffer[0..(command_buffer_pos-1)] - end - kill_after.call - command_window.clrtoeol - command_window.noutrefresh - Curses.doupdate - end + if command_buffer_pos < command_buffer.length + kill_before.call + if command_buffer_pos == 0 + kill_buffer = kill_buffer + command_buffer + command_buffer = '' + else + kill_buffer = kill_buffer + command_buffer[command_buffer_pos..-1] + command_buffer = command_buffer[0..(command_buffer_pos - 1)] + end + kill_after.call + command_window.clrtoeol + command_window.noutrefresh + Curses.doupdate + end } key_action['cursor_kill_line'] = proc { - if command_buffer.length != 0 - kill_before.call - kill_buffer = kill_original - command_buffer = '' - command_buffer_pos = 0 - command_buffer_offset = 0 - kill_after.call - command_window.setpos(0, 0) - command_window.clrtoeol - command_window.noutrefresh - Curses.doupdate - end + if command_buffer.length != 0 + kill_before.call + kill_buffer = kill_original + command_buffer = '' + command_buffer_pos = 0 + command_buffer_offset = 0 + kill_after.call + command_window.setpos(0, 0) + command_window.clrtoeol + command_window.noutrefresh + Curses.doupdate + end } key_action['cursor_yank'] = proc { - kill_buffer.each_char { |c| command_window_put_ch.call(c) } + kill_buffer.each_char { |c| command_window_put_ch.call(c) } } key_action['switch_current_window'] = proc { - if current_scroll_window = TextWindow.list[0] - current_scroll_window.clear_scrollbar - end - TextWindow.list.push(TextWindow.list.shift) - if current_scroll_window = TextWindow.list[0] - current_scroll_window.update_scrollbar - end - command_window.noutrefresh - Curses.doupdate + if (current_scroll_window = TextWindow.list[0]) + current_scroll_window.clear_scrollbar + end + TextWindow.list.push(TextWindow.list.shift) + if (current_scroll_window = TextWindow.list[0]) + current_scroll_window.update_scrollbar + end + command_window.noutrefresh + Curses.doupdate } key_action['scroll_current_window_up_one'] = proc { - if current_scroll_window = TextWindow.list[0] - current_scroll_window.scroll(-1) - end - command_window.noutrefresh - Curses.doupdate + if (current_scroll_window = TextWindow.list[0]) + current_scroll_window.scroll(-1) + end + command_window.noutrefresh + Curses.doupdate } key_action['scroll_current_window_down_one'] = proc { - if current_scroll_window = TextWindow.list[0] - current_scroll_window.scroll(1) - end - command_window.noutrefresh - Curses.doupdate + if (current_scroll_window = TextWindow.list[0]) + current_scroll_window.scroll(1) + end + command_window.noutrefresh + Curses.doupdate } key_action['scroll_current_window_up_page'] = proc { - if current_scroll_window = TextWindow.list[0] - current_scroll_window.scroll(0 - current_scroll_window.maxy + 1) - end - command_window.noutrefresh - Curses.doupdate + if (current_scroll_window = TextWindow.list[0]) + current_scroll_window.scroll(0 - current_scroll_window.maxy + 1) + end + command_window.noutrefresh + Curses.doupdate } key_action['scroll_current_window_down_page'] = proc { - if current_scroll_window = TextWindow.list[0] - current_scroll_window.scroll(current_scroll_window.maxy - 1) - end - command_window.noutrefresh - Curses.doupdate + if (current_scroll_window = TextWindow.list[0]) + current_scroll_window.scroll(current_scroll_window.maxy - 1) + end + command_window.noutrefresh + Curses.doupdate } key_action['scroll_current_window_bottom'] = proc { - if current_scroll_window = TextWindow.list[0] - current_scroll_window.scroll(current_scroll_window.max_buffer_size) - end - command_window.noutrefresh - Curses.doupdate + if (current_scroll_window = TextWindow.list[0]) + current_scroll_window.scroll(current_scroll_window.max_buffer_size) + end + command_window.noutrefresh + Curses.doupdate } write_to_client = proc { |str, color| - stream_handler["main"].add_string str, [{:fg => color, :start => 0, :end => str.size}] - command_window.noutrefresh - Curses.doupdate + stream_handler["main"].add_string str, [{ :fg => color, :start => 0, :end => str.size }] + command_window.noutrefresh + Curses.doupdate } key_action['autocomplete'] = proc { |idx| - Autocomplete.wrap do - current = command_buffer.dup - history = command_history.map(&:strip).reject(&:empty?).compact.uniq - - # collection of possibilities - possibilities = [] - - unless current.strip.empty? - history.each do |historical| - possibilities.push(historical) if Autocomplete.compare(current, historical) - end - end - - if possibilities.size == 0 - write_to_client.call "[autocomplete] no suggestions", Autocomplete::HIGHLIGHT - end - - if possibilities.size > 1 - # we should autoprogress the command input until there - # is a divergence in the possible commands - divergence = Autocomplete.find_branch(possibilities) - - command_buffer = divergence - command_buffer_offset = [ (command_buffer.length - command_window.maxx + 1), 0 ].max - command_buffer_pos = command_buffer.length - command_window.addstr divergence[current.size..-1] - command_window.setpos(0, divergence.size) - - write_to_client.call("[autocomplete:#{possibilities.size}]", Autocomplete::HIGHLIGHT) - possibilities.each_with_index do |command, i| - write_to_client.call("[#{i}] #{command}", Autocomplete::HIGHLIGHT) end - end - - idx = 0 if possibilities.size == 1 - - if idx && possibilities[idx] - command_buffer = possibilities[idx] - command_buffer_offset = [ (command_buffer.length - command_window.maxx + 1), 0 ].max - command_buffer_pos = command_buffer.length - command_window.addstr possibilities.first[current.size..-1] - command_window.setpos(0, possibilities.first.size) - Curses.doupdate - end - end + Autocomplete.wrap do + current = command_buffer.dup + history = command_history.map(&:strip).reject(&:empty?).compact.uniq + + # collection of possibilities + possibilities = [] + + unless current.strip.empty? + history.each do |historical| + possibilities.push(historical) if Autocomplete.compare(current, historical) + end + end + + if possibilities.size == 0 + write_to_client.call "[autocomplete] no suggestions", Autocomplete::HIGHLIGHT + end + + if possibilities.size > 1 + # we should autoprogress the command input until there + # is a divergence in the possible commands + divergence = Autocomplete.find_branch(possibilities) + + command_buffer = divergence + command_buffer_offset = [(command_buffer.length - command_window.maxx + 1), 0].max + command_buffer_pos = command_buffer.length + command_window.addstr divergence[current.size..-1] + command_window.setpos(0, divergence.size) + + write_to_client.call("[autocomplete:#{possibilities.size}]", Autocomplete::HIGHLIGHT) + possibilities.each_with_index do |command, i| + write_to_client.call("[#{i}] #{command}", Autocomplete::HIGHLIGHT) + end + end + + idx = 0 if possibilities.size == 1 + + if idx && possibilities[idx] + command_buffer = possibilities[idx] + command_buffer_offset = [(command_buffer.length - command_window.maxx + 1), 0].max + command_buffer_pos = command_buffer.length + command_window.addstr possibilities.first[current.size..-1] + command_window.setpos(0, possibilities.first.size) + Curses.doupdate + end + end } key_action['previous_command'] = proc { - if command_history_pos < (command_history.length - 1) - command_history[command_history_pos] = command_buffer.dup - command_history_pos += 1 - command_buffer = command_history[command_history_pos].dup - command_buffer_offset = [ (command_buffer.length - command_window.maxx + 1), 0 ].max - command_buffer_pos = command_buffer.length - command_window.setpos(0, 0) - command_window.deleteln - command_window.addstr command_buffer[command_buffer_offset,(command_buffer.length - command_buffer_offset)] - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - command_window.noutrefresh - Curses.doupdate - end + if command_history_pos < (command_history.length - 1) + command_history[command_history_pos] = command_buffer.dup + command_history_pos += 1 + command_buffer = command_history[command_history_pos].dup + command_buffer_offset = [(command_buffer.length - command_window.maxx + 1), 0].max + command_buffer_pos = command_buffer.length + command_window.setpos(0, 0) + command_window.deleteln + command_window.addstr command_buffer[command_buffer_offset, (command_buffer.length - command_buffer_offset)] + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + command_window.noutrefresh + Curses.doupdate + end } key_action['next_command'] = proc { - if command_history_pos == 0 - unless command_buffer.empty? - command_history[command_history_pos] = command_buffer.dup - command_history.unshift String.new - command_buffer.clear - command_window.deleteln - command_buffer_pos = 0 - command_buffer_offset = 0 - command_window.setpos(0,0) - command_window.noutrefresh - Curses.doupdate - end - else - command_history[command_history_pos] = command_buffer.dup - command_history_pos -= 1 - command_buffer = command_history[command_history_pos].dup - command_buffer_offset = [ (command_buffer.length - command_window.maxx + 1), 0 ].max - command_buffer_pos = command_buffer.length - command_window.setpos(0, 0) - command_window.deleteln - command_window.addstr command_buffer[command_buffer_offset,(command_buffer.length - command_buffer_offset)] - command_window.setpos(0, command_buffer_pos - command_buffer_offset) - command_window.noutrefresh - Curses.doupdate - end + if command_history_pos == 0 + unless command_buffer.empty? + command_history[command_history_pos] = command_buffer.dup + command_history.unshift String.new + command_buffer.clear + command_window.deleteln + command_buffer_pos = 0 + command_buffer_offset = 0 + command_window.setpos(0, 0) + command_window.noutrefresh + Curses.doupdate + end + else + command_history[command_history_pos] = command_buffer.dup + command_history_pos -= 1 + command_buffer = command_history[command_history_pos].dup + command_buffer_offset = [(command_buffer.length - command_window.maxx + 1), 0].max + command_buffer_pos = command_buffer.length + command_window.setpos(0, 0) + command_window.deleteln + command_window.addstr command_buffer[command_buffer_offset, (command_buffer.length - command_buffer_offset)] + command_window.setpos(0, command_buffer_pos - command_buffer_offset) + command_window.noutrefresh + Curses.doupdate + end } key_action['switch_arrow_mode'] = proc { - if key_binding[Curses::KEY_UP] == key_action['previous_command'] - key_binding[Curses::KEY_UP] = key_action['scroll_current_window_up_page'] - key_binding[Curses::KEY_DOWN] = key_action['scroll_current_window_down_page'] - else - key_binding[Curses::KEY_UP] = key_action['previous_command'] - key_binding[Curses::KEY_DOWN] = key_action['next_command'] - end + if key_binding[Curses::KEY_UP] == key_action['previous_command'] + key_binding[Curses::KEY_UP] = key_action['scroll_current_window_up_page'] + key_binding[Curses::KEY_DOWN] = key_action['scroll_current_window_down_page'] + else + key_binding[Curses::KEY_UP] = key_action['previous_command'] + key_binding[Curses::KEY_DOWN] = key_action['next_command'] + end } key_action['send_command'] = proc { - cmd = command_buffer.dup - command_buffer.clear - command_buffer_pos = 0 - command_buffer_offset = 0 - need_prompt = false - if window = stream_handler['main'] - add_prompt(window, prompt_text, cmd) - end - command_window.deleteln - command_window.setpos(0,0) - command_window.noutrefresh - Curses.doupdate - command_history_pos = 0 - # Remember all digit commands because they are likely spells for voodoo.lic - if (cmd.length >= min_cmd_length_for_history || cmd.digits?) and (cmd != command_history[1]) - if command_history[0].nil? or command_history[0].empty? - command_history[0] = cmd - else - command_history.unshift cmd - end - command_history.unshift String.new - end - if cmd =~ /^\.quit/ - exit - elsif cmd =~ /^\.key/i - window = stream_handler['main'] - window.add_string("* ") - window.add_string("* Waiting for key press...") - command_window.noutrefresh - Curses.doupdate - window.add_string("* Detected keycode: #{command_window.getch.to_s}") - window.add_string("* ") - Curses.doupdate - elsif cmd =~ /^\.copy/ - # fixme - elsif cmd =~ /^\.fixcolor/i - if CUSTOM_COLORS - COLOR_ID_LOOKUP.each { |code,id| - Curses.init_color(id, ((code[0..1].to_s.hex/255.0)*1000).round, ((code[2..3].to_s.hex/255.0)*1000).round, ((code[4..5].to_s.hex/255.0)*1000).round) - } - end - elsif cmd =~ /^\.resync/i - skip_server_time_offset = false - elsif cmd =~ /^\.reload/i - load_settings_file.call(true) - elsif cmd =~ /^\.layout\s+(.+)/ - load_layout.call($1) - key_action['resize'].call - elsif cmd =~ /^\.arrow/i - key_action['switch_arrow_mode'].call - elsif cmd =~ /^\.e (.*)/ - eval(cmd.sub(/^\.e /, '')) - else - server.puts cmd.sub(/^\./, ';') - end + cmd = command_buffer.dup + command_buffer.clear + command_buffer_pos = 0 + command_buffer_offset = 0 + need_prompt = false + if (window = stream_handler['main']) + add_prompt(window, prompt_text, cmd) + end + command_window.deleteln + command_window.setpos(0, 0) + command_window.noutrefresh + Curses.doupdate + command_history_pos = 0 + # Remember all digit commands because they are likely spells for voodoo.lic + if (cmd.length >= min_cmd_length_for_history || cmd.digits?) and (cmd != command_history[1]) + if command_history[0].nil? or command_history[0].empty? + command_history[0] = cmd + else + command_history.unshift cmd + end + command_history.unshift String.new + end + if cmd =~ /^\.quit/ + exit + elsif cmd =~ /^\.key/i + window = stream_handler['main'] + window.add_string("* ") + window.add_string("* Waiting for key press...") + command_window.noutrefresh + Curses.doupdate + window.add_string("* Detected keycode: #{command_window.getch}") + window.add_string("* ") + Curses.doupdate + elsif cmd =~ /^\.copy/ + # fixme + elsif cmd =~ /^\.fixcolor/i + if CUSTOM_COLORS + COLOR_ID_LOOKUP.each { |code, id| + Curses.init_color(id, ((code[0..1].to_s.hex / 255.0) * 1000).round, ((code[2..3].to_s.hex / 255.0) * 1000).round, ((code[4..5].to_s.hex / 255.0) * 1000).round) + } + end + elsif cmd =~ /^\.resync/i + skip_server_time_offset = false + elsif cmd =~ /^\.reload/i + load_settings_file.call(true) + elsif cmd =~ /^\.layout\s+(.+)/ + load_layout.call($1) + key_action['resize'].call + elsif cmd =~ /^\.arrow/i + key_action['switch_arrow_mode'].call + elsif cmd =~ /^\.e (.*)/ + eval(cmd.sub(/^\.e /, '')) + elsif cmd =~ /^\.links/i + blue_links = !blue_links + else + server.puts cmd.sub(/^\./, ';') + end } key_action['send_last_command'] = proc { - if cmd = command_history[1] - if window = stream_handler['main'] - add_prompt(window, prompt_text, cmd) - #window.add_string(">#{cmd}", [ h={ :start => 0, :end => (cmd.length + 1), :fg => '555555' } ]) - command_window.noutrefresh - Curses.doupdate - end - if cmd =~ /^\.quit/i - exit - elsif cmd =~ /^\.fixcolor/i - if CUSTOM_COLORS - COLOR_ID_LOOKUP.each { |code,id| - Curses.init_color(id, ((code[0..1].to_s.hex/255.0)*1000).round, ((code[2..3].to_s.hex/255.0)*1000).round, ((code[4..5].to_s.hex/255.0)*1000).round) - } - end - elsif cmd =~ /^\.resync/i - skip_server_time_offset = false - elsif cmd =~ /^\.arrow/i - key_action['switch_arrow_mode'].call - elsif cmd =~ /^\.e (.*)/ - eval(cmd.sub(/^\.e /, '')) - else - server.puts cmd.sub(/^\./, ';') - end - end + if (cmd = command_history[1]) + if (window = stream_handler['main']) + add_prompt(window, prompt_text, cmd) + # window.add_string(">#{cmd}", [ h={ :start => 0, :end => (cmd.length + 1), :fg => '555555' } ]) + command_window.noutrefresh + Curses.doupdate + end + if cmd =~ /^\.quit/i + exit + elsif cmd =~ /^\.fixcolor/i + if CUSTOM_COLORS + COLOR_ID_LOOKUP.each { |code, id| + Curses.init_color(id, ((code[0..1].to_s.hex / 255.0) * 1000).round, ((code[2..3].to_s.hex / 255.0) * 1000).round, ((code[4..5].to_s.hex / 255.0) * 1000).round) + } + end + elsif cmd =~ /^\.resync/i + skip_server_time_offset = false + elsif cmd =~ /^\.arrow/i + key_action['switch_arrow_mode'].call + elsif cmd =~ /^\.e (.*)/ + eval(cmd.sub(/^\.e /, '')) + else + server.puts cmd.sub(/^\./, ';') + end + end } key_action['send_second_last_command'] = proc { - if cmd = command_history[2] - if window = stream_handler['main'] - add_prompt(window, prompt_text, cmd) - #window.add_string(">#{cmd}", [ h={ :start => 0, :end => (cmd.length + 1), :fg => '555555' } ]) - command_window.noutrefresh - Curses.doupdate - end - if cmd =~ /^\.quit/i - exit - elsif cmd =~ /^\.fixcolor/i - if CUSTOM_COLORS - COLOR_ID_LOOKUP.each { |code,id| - Curses.init_color(id, ((code[0..1].to_s.hex/255.0)*1000).round, ((code[2..3].to_s.hex/255.0)*1000).round, ((code[4..5].to_s.hex/255.0)*1000).round) - } - end - elsif cmd =~ /^\.resync/i - skip_server_time_offset = false - elsif cmd =~ /^\.arrow/i - key_action['switch_arrow_mode'].call - elsif cmd =~ /^\.e (.*)/ - eval(cmd.sub(/^\.e /, '')) - else - server.puts cmd.sub(/^\./, ';') - end - end + if (cmd = command_history[2]) + if (window = stream_handler['main']) + add_prompt(window, prompt_text, cmd) + # window.add_string(">#{cmd}", [ h={ :start => 0, :end => (cmd.length + 1), :fg => '555555' } ]) + command_window.noutrefresh + Curses.doupdate + end + if cmd =~ /^\.quit/i + exit + elsif cmd =~ /^\.fixcolor/i + if CUSTOM_COLORS + COLOR_ID_LOOKUP.each { |code, id| + Curses.init_color(id, ((code[0..1].to_s.hex / 255.0) * 1000).round, ((code[2..3].to_s.hex / 255.0) * 1000).round, ((code[4..5].to_s.hex / 255.0) * 1000).round) + } + end + elsif cmd =~ /^\.resync/i + skip_server_time_offset = false + elsif cmd =~ /^\.arrow/i + key_action['switch_arrow_mode'].call + elsif cmd =~ /^\.e (.*)/ + eval(cmd.sub(/^\.e /, '')) + else + server.puts cmd.sub(/^\./, ';') + end + end } new_stun = proc { |seconds| - if window = countdown_handler['stunned'] - temp_stun_end = Time.now.to_f - $server_time_offset.to_f + seconds.to_f - window.end_time = temp_stun_end - window.update - need_update = true - Thread.new { - while (countdown_handler['stunned'].end_time == temp_stun_end) and (countdown_handler['stunned'].value > 0) - sleep 0.15 - if countdown_handler['stunned'].update - command_window.noutrefresh - Curses.doupdate - end - end - } - end + if (window = countdown_handler['stunned']) + temp_stun_end = Time.now.to_f - $server_time_offset.to_f + seconds.to_f + window.end_time = temp_stun_end + window.update + Thread.new { + while (countdown_handler['stunned'].end_time == temp_stun_end) and (countdown_handler['stunned'].value > 0) + sleep 0.15 + if countdown_handler['stunned'].update + command_window.noutrefresh + Curses.doupdate + end + end + } + end } load_settings_file.call(false) @@ -1192,492 +1242,1042 @@ def get_color_pair_id(fg_code, bg_code) Thread.new { sleep 15; skip_server_time_offset = false } Thread.new { - begin - line = nil - need_update = false - line_colors = Array.new - open_monsterbold = Array.new - open_preset = Array.new - open_style = nil - open_color = Array.new - current_stream = nil - - handle_game_text = proc { |text| - - for escapable in xml_escape_list.keys - search_pos = 0 - while (pos = text.index(escapable, search_pos)) - text = text.sub(escapable, xml_escape_list[escapable]) - line_colors.each { |h| - h[:start] -= (escapable.length - 1) if h[:start] > pos - h[:end] -= (escapable.length - 1) if h[:end] > pos - } - if open_style and (open_style[:start] > pos) - open_style[:start] -= (escapable.length - 1) - end - end - end - - if text =~ /^\[.*?\]>/ - need_prompt = false - elsif text =~ /^\s*You are stunned for ([0-9]+) rounds?/ - new_stun.call($1.to_i * 5) - elsif text =~ /^Deep and resonating, you feel the chant that falls from your lips instill within you with the strength of your faith\. You crouch beside [A-Z][a-z]+ and gently lift (?:he|she|him|her) into your arms, your muscles swelling with the power of your deity, and cradle (?:him|her) close to your chest\. Strength and life momentarily seep from your limbs, causing them to feel laden and heavy, and you are overcome with a sudden weakness\. With a sigh, you are able to lay [A-Z][a-z]+ back down\.$|^Moisture beads upon your skin and you feel your eyes cloud over with the darkness of a rising storm\. Power builds upon the air and when you utter the last syllable of your spell thunder rumbles from your lips\. The sound ripples upon the air, and colling with [A-Z][a-z']+ prone form and a brilliant flash transfers the spiritual energy between you\.$|^Lifting your finger, you begin to chant and draw a series of conjoined circles in the air\. Each circle turns to mist and takes on a different hue - white, blue, black, red, and green\. As the last ring is completed, you spread your fingers and gently allow your tips to touch each color before pushing the misty creation towards [A-Z][a-z]+\. A shock of energy courses through your body as the mist seeps into [A-Z][a-z']+ chest and life is slowly returned to (?:his|her) body\.$|^Crouching beside the prone form of [A-Z][a-z]+, you softly issue the last syllable of your chant\. Breathing deeply, you take in the scents around you and let the feel of your surroundings infuse you\. With only your gaze, you track the area and recreate the circumstances of [A-Z][a-z']+ within your mind\. Touching [A-Z][a-z]+, you follow the lines of the web that holds (?:his|her) soul in place and force it back into (?:his|her) body\. Raw energy courses through you and you feel your sense of justice and vengeance filling [A-Z][a-z]+ with life\.$|^Murmuring softly, you call upon your connection with the Destroyer,? and feel your words twist into an alien, spidery chant\. Dark shadows laced with crimson swirl before your eyes and at your forceful command sink into the chest of [A-Z][a-z]+\. The transference of energy is swift and immediate as you bind [A-Z][a-z]+ back into (?:his|her) body\.$|^Rich and lively, the scent of wild flowers suddenly fills the air as you finish your chant, and you feel alive with the energy of spring\. With renewal at your fingertips, you gently touch [A-Z][a-z]+ on the brow and revel in the sweet rush of energy that passes through you into (?:him|her|his)\.$|^Breathing slowly, you extend your senses towards the world around you and draw into you the very essence of nature\. You shift your gaze towards [A-z][a-z]+ and carefully release the energy you've drawn into yourself towards (?:him|her)\. A rush of energy briefly flows between the two of you as you feel life slowly return to (?:him|her)\.$|^Your surroundings grow dim\.\.\.you lapse into a state of awareness only, unable to do anything\.\.\.$|^Murmuring softly, a mournful chant slips from your lips and you feel welts appear upon your wrists\. Dipping them briefly, you smear the crimson liquid the leaks from these sudden wounds in a thin line down [A-Z][a-z']+ face\. Tingling with each second that your skin touches (?:his|hers), you feel the transference of your raw energy pass into [A-Z][a-z]+ and momentarily reel with the pain of its release\. Slowly, the wounds on your wrists heal, though a lingering throb remains\.$|^Emptying all breathe from your body, you slowly still yourself and close your eyes\. You reach out with all of your senses and feel a film shift across your vision\. Opening your eyes, you gaze through a white haze and find images of [A-Z][a-z]+ floating above his prone form\. Acts of [A-Z][a-z]'s? past, present, and future play out before your clouded vision\. With conviction and faith, you pluck a future image of [A-Z][a-z]+ from the air and coax (?:he|she|his|her) back into (?:he|she|his|her) body\. Slowly, the film slips from your eyes and images fade away\.$|^Thin at first, a fine layer of rime tickles your hands and fingertips\. The hoarfrost smoothly glides between you and [A-Z][a-z]+, turning to a light powder as it traverses the space\. The white substance clings to [A-Z][a-z]+'s? eyelashes and cheeks for a moment before it becomes charged with spiritual power, then it slowly melts away\.$|^As you begin to chant,? you notice the scent of dry, dusty parchment and feel a cool mist cling to your skin somewhere near your feet\. You sense the ethereal tendrils of the mist as they coil about your body and notice that the world turns to a yellowish hue as the mist settles about your head\. Focusing on [A-Z][a-z]+, you feel the transfer of energy pass between you as you return (?:him|her) to life\.$|^Wrapped in an aura of chill, you close your eyes and softly begin to chant\. As the cold air that surrounds you condenses you feel it slowly ripple outward in waves that turn the breath of those nearby into a fine mist\. This mist swiftly moves to encompass you and you feel a pair of wings arc over your back\. With the last words of your chant, you open your eyes and watch as foggy wings rise above you and gently brush against [A-Z][a-z]+\. As they dissipate in a cold rush against [A-Z][a-z]+, you feel a surge of power spill forth from you and into (?:him|her)\.$|^As .*? begins to chant, your spirit is drawn closer to your body by the scent of dusty, dry parchment\. Topaz tendrils coil about .*?, and you feel an ancient presence demand that you return to your body\. All at once .*? focuses upon you and you feel a surge of energy bind you back into your now-living body\.$/ - # raise dead stun - new_stun.call(30.6) - elsif text =~ /^Just as you think the falling will never end, you crash through an ethereal barrier which bursts into a dazzling kaleidoscope of color! Your sensation of falling turns to dizziness and you feel unusually heavy for a moment\. Everything seems to stop for a prolonged second and then WHUMP!!!/ - # Shadow Valley exit stun - new_stun.call(16.2) - elsif text =~ /^You have.*?(?:case of uncontrollable convulsions|case of sporadic convulsions|strange case of muscle twitching)/ - # nsys wound will be correctly set by xml, dont set the scar using health verb output - skip_nsys = true - else - if skip_nsys - skip_nsys = false - elsif window = indicator_handler['nsys'] - if text =~ /^You have.*? very difficult time with muscle control/ - if window.update(3) - need_update = true - end - elsif text =~ /^You have.*? constant muscle spasms/ - if window.update(2) - need_update = true - end - elsif text =~ /^You have.*? developed slurred speech/ - if window.update(1) - need_update = true - end - end - end - end - - if open_style - h = open_style.dup - h[:end] = text.length - line_colors.push(h) - open_style[:start] = 0 - end - for oc in open_color - ocd = oc.dup - ocd[:end] = text.length - line_colors.push(ocd) - oc[:start] = 0 - end - - if current_stream.nil? or stream_handler[current_stream] or (current_stream =~ /^(?:death|logons|thoughts|voln|familiar)$/) - SETTINGS_LOCK.synchronize { - HIGHLIGHT.each_pair { |regex,colors| - pos = 0 - while (match_data = text.match(regex, pos)) - h = { - :start => match_data.begin(0), - :end => match_data.end(0), - :fg => colors[0], - :bg => colors[1], - :ul => colors[2] - } - line_colors.push(h) - pos = match_data.end(0) - end - } - } - end - - unless text.empty? - if current_stream - if current_stream == 'thoughts' - if text =~ /^\[.+?\]\-[A-z]+\:[A-Z][a-z]+\: "|^\[server\]\: / - current_stream = 'lnet' - end - end - if window = stream_handler[current_stream] - if current_stream == 'death' - # fixme: has been vaporized! - # fixme: ~ off to a rough start - if text =~ /^\s\*\s(The death cry of )?([A-Z][a-z]+) (?:just bit the dust!|echoes in your mind!)/ - front_count = 3 - front_count += 17 if $1 - name = $2 - text = "#{name} #{Time.now.strftime('%l:%M%P').sub(/^0/, '')}" - line_colors.each { |h| - h[:start] -= front_count - h[:end] = [ h[:end], name.length ].min - } - line_colors.delete_if { |h| h[:start] >= h[:end] } - h = { - :start => (name.length+1), - :end => text.length, - :fg => 'ff0000', - } - line_colors.push(h) - end - elsif current_stream == 'logons' - foo = { 'joins the adventure.' => '007700', 'returns home from a hard day of adventuring.' => '777700', 'has disconnected.' => 'aa7733' } - if text =~ /^\s\*\s([A-Z][a-z]+) (#{foo.keys.join('|')})/ - name = $1 - logon_type = $2 - text = "#{name} #{Time.now.strftime('%l:%M%P').sub(/^0/, '')}" - line_colors.each { |h| - h[:start] -= 3 - h[:end] = [ h[:end], name.length ].min - } - line_colors.delete_if { |h| h[:start] >= h[:end] } - h = { - :start => (name.length+1), - :end => text.length, - :fg => foo[logon_type], - } - line_colors.push(h) - end - end - unless text =~ /^\[server\]: "(?:kill|connect)/ - window.add_string(text, line_colors) - need_update = true - end - elsif current_stream =~ /^(?:death|logons|thoughts|voln|familiar)$/ - if window = stream_handler['main'] - if PRESET[current_stream] - line_colors.push(:start => 0, :fg => PRESET[current_stream][0], :bg => PRESET[current_stream][1], :end => text.length) - end - unless text.empty? - if need_prompt - need_prompt = false - add_prompt(window, prompt_text) - end - window.add_string(text, line_colors) - need_update = true - end - end - else - # stream_handler['main'].add_string "#{current_stream}: #{text.inspect}" - end - else - if window = stream_handler['main'] - if need_prompt - need_prompt = false - add_prompt(window, prompt_text) - end - window.add_string(text, line_colors) - need_update = true - end - end - end - line_colors = Array.new - open_monsterbold.clear - open_preset.clear - # open_color.clear - } - - while (line = server.gets) - line.chomp! - if line.empty? - if current_stream.nil? - if need_prompt - need_prompt = false - add_prompt(stream_handler['main'], prompt_text) - end - stream_handler['main'].add_string String.new - need_update = true - end - else - while (start_pos = (line =~ /(<(prompt|spell|right|left|inv|style|compass).*?\2>|<.*?>)/)) - xml = $1 - line.slice!(start_pos, xml.length) - if xml =~ /^(.*?)><\/prompt>$/ - Profanity.put(prompt: "#{$3.clone}".strip) - Profanity.update_process_title() - unless skip_server_time_offset - $server_time_offset = Time.now.to_f - $2.to_f - skip_server_time_offset = true - end - new_prompt_text = "#{$3}>" - if prompt_text != new_prompt_text - need_prompt = false - prompt_text = new_prompt_text - add_prompt(stream_handler['main'], new_prompt_text) - if prompt_window = indicator_handler["prompt"] - init_prompt_height, init_prompt_width = fix_layout_number.call(prompt_window.layout[0]), fix_layout_number.call(prompt_window.layout[1]) - new_prompt_width = new_prompt_text.length - prompt_window.resize(init_prompt_height, new_prompt_width) - prompt_width_diff = new_prompt_width - init_prompt_width - command_window.resize(fix_layout_number.call(command_window_layout[0]), fix_layout_number.call(command_window_layout[1]) - prompt_width_diff) - ctop, cleft = fix_layout_number.call(command_window_layout[2]), fix_layout_number.call(command_window_layout[3]) + prompt_width_diff - command_window.move(ctop, cleft) - prompt_window.label = new_prompt_text - end - else - need_prompt = true - end - elsif xml =~ /^|\s.*?>)(.*?)<\/spell>$/ - if window = indicator_handler['spell'] - window.clear - window.label = $1 - window.update($1 == 'None' ? 0 : 1) - need_update = true - end - elsif xml =~ /^|\s.*?>)(.*?)<\/\1>/ - if window = indicator_handler[$1] - window.clear - window.label = $2 - window.update($2 == 'Empty' ? 0 : 1) - need_update = true - end - elsif xml =~ /^ 0) - sleep 0.15 - if countdown_handler['roundtime'].update - command_window.noutrefresh - Curses.doupdate - end - end - } - end - elsif xml =~ /^ 0) - sleep 0.15 - if countdown_handler['roundtime'].update - command_window.noutrefresh - Curses.doupdate - end - end - } - end - elsif xml =~ /^' or xml == '' - h = { :start => start_pos } - if PRESET['monsterbold'] - h[:fg] = PRESET['monsterbold'][0] - h[:bg] = PRESET['monsterbold'][1] - end - open_monsterbold.push(h) - elsif xml == '' or xml == '' - if h = open_monsterbold.pop - h[:end] = start_pos - line_colors.push(h) if h[:fg] or h[:bg] - end - elsif xml =~ /^$/ - h = { :start => start_pos } - if PRESET[$2] - h[:fg] = PRESET[$2][0] - h[:bg] = PRESET[$2][1] - end - open_preset.push(h) - elsif xml == '' - if h = open_preset.pop - h[:end] = start_pos - line_colors.push(h) if h[:fg] or h[:bg] - end - elsif xml =~ /^ start_pos } - if xml =~ /\sfg=('|")(.*?)\1[\s>]/ - h[:fg] = $2.downcase - end - if xml =~ /\sbg=('|")(.*?)\1[\s>]/ - h[:bg] = $2.downcase - end - if xml =~ /\sul=('|")(.*?)\1[\s>]/ - h[:ul] = $2.downcase - end - open_color.push(h) - elsif xml == '' - if h = open_color.pop - h[:end] = start_pos - line_colors.push(h) - end - elsif xml =~ /^