// // ruby_mode // // v 1.1.1 // 2006-Oct-05 // // Timothy Byrd // timbyrd@pobox.com // // (Please contact me with comments/corrections.) // // This is a mode for Ruby files // // History: // Note: This file is set up to work with Ruby 1.8.2. // Don't know what will change in 2.0... // // 2006-10-05 v1.1.1 - expand # in backtick strings. // // 2006-09-25 Jim added more stuff :) // - ALT-SHIFT-F11 will run the specific test case the cursor is in // - Added a subroutine that executes a ruby script passed as a string // - changed the show_table function to have the ruby script in-line // - use args to determine if the output buffer for ALT-F11 (run // ruby buffer) will be switch to, and also prompt for paramters // to pass to the script args of 1 will switch to buffer, args of // 2 will prompt for parameters args of 3 will do both // - Added environment variables to any call to a ruby script // - added ruby_pound_sign command on #, which will expand #{} when // in a string or regex can be disabled by setting // ruby_expand_pound to 0 // // 2006-09-22 - v1.1.0 - Jim Morris added: // - ri popup of currently selected word, or prompt for ri search // on ALT-F1 // - ctrl-alt-v toggles between rails view and controller // - alt-F11 runs the selected buffer through ruby and displays // results in pop-up // - Made it so end reindents when the d is typed rather than // when return is typed // - Added a show_table function for rails // // 2005-Apr-13 - v1.0.1 - Give backquoted strings own color classes. // Add color class for built-in classes (e.g. Array) // // 2005-Apr-06 - Added ruby_delete_trailing_spaces variable // // 2005-Apr-05 - Initial version: Syntax coloring and smart // indenting (shift-tab to unindent) // // // See the user variables below for info on how the behavior of // ruby-mode can be customized. // #if 0 // Add these lines to colclass.txt to get descriptions in set-color ruby-brace Square and curly braces in Ruby, i.e., [ ] { }. ruby-class A pre-defined class or module in Ruby, e.g., Array. ruby-class-var A class variable in Ruby, e.g., @length ruby-comment A comment in Ruby. ruby-global A standard global constant in Ruby, e.g., ARGV. ruby-global-var A global variable in Ruby, e.g., $myGlobal ruby-instance-var A class instance variable in Ruby, e.g., @length ruby-keyword A keyword in Ruby, e.g., def, class or module. ruby-number A number or :symbol in Ruby. ruby-perl-var A Perl-style variable in Ruby, e.g., $&. ruby-punctuation Punctuation in Ruby. ruby-regexp A regular expression in Ruby. ruby-shell-cmd A shell command string in Ruby, e.g. `cd mySubdir`. ruby-shell-subst A substitution in a Ruby shell command, e.g. `cd #{subDir}`. ruby-str-subst A string substitution in Ruby, e.g., "i = #{i}". ruby-string A string in Ruby. #endif // // Here are the colors I (Timothy) am using at the moment: // // color_class ruby_brace 0x725ceb on black; // color_class ruby_class 0xd9d240 on black; // color_class ruby_comment grey on black; // color_class ruby_global 0xffb737 on black; // color_class ruby_keyword 0xff8000 on black; // color_class ruby_number 0xff9090 on black; // color_class ruby_punctuation yellow on black; // color_class ruby_regexp 0x007fff on black; // color_class ruby_shell_cmd = 0x8fff2f on black; // color_class ruby_shell_subst = 0x8fff8f on black; // color_class ruby_str_subst 0xffc0c8 on black; // color_class ruby_string cyan on black; // color_class ruby_perl_var red on black; // #include "eel.h" #include "proc.h" #include "colcode.h" #include "perl.h" #include "c.h" user buffer short ruby_indent = 4; /* indent by this amt; 0 means use tab size */ user char ruby_indent_with_tabs = 0; user char ruby_reindent_previous_line = 1; user int reindent_after_ruby_yank = 20000; user int ruby_expand_pound = 1; // set to 0 to disable the # expansion in strings // Should a lone } or ] be unindented? // // Closeback = 1: // def foo // a = [ // 1, // 2, // 3 // ] // a.each {|x| // puts x // } // end // // Closeback = 0: // def foo // a = [ // 1, // 2, // 3 // ] // a.each {|x| // puts x // } // end // user char ruby_closeback = 1; user char compile_ruby_cmd[128] = "ruby \"%r\""; // run Ruby on this file. user char auto_show_ruby_delimiters = 1; user char ruby_auto_show_delim_chars[20] = "{[()]}"; user char ruby_delete_trailing_spaces = 1; /* Use the c-mode variables for now */ user char Matchdelim = 1; buffer char in_shell_buffer; ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// char _ruby_mode_name[] = "Ruby"; keytable ruby_tab; /* key table for ruby mode */ color_class ruby_brace = color_class perl_constant; color_class ruby_class = color_class perl_variable; color_class ruby_class_var = color_class perl_variable; color_class ruby_comment = color_class perl_comment; color_class ruby_global = color_class perl_variable; color_class ruby_global_var = color_class perl_variable; color_class ruby_instance_var = color_class perl_variable; color_class ruby_keyword = color_class perl_keyword; color_class ruby_number = color_class perl_constant; color_class ruby_perl_var = color_class perl_constant; color_class ruby_punctuation = color_class perl_constant; color_class ruby_regexp = color_class perl_function; color_class ruby_shell_cmd = color_class perl_constant; color_class ruby_shell_subst = color_class perl_constant; color_class ruby_str_subst = color_class perl_string; color_class ruby_string = color_class perl_string; #define RUBY_IDENT "(@?@?|?)[a-zA-Z_][a-zA-Z0-9_]*" #define RUBY_NAME RUBY_IDENT "[!?=]?" // This is now global so it can be shared between // ruby_func_name_finder() and ruby_toggle_view_controller() // char defname[FNAMELEN]; // // Numbers: // // 123 1_234 123.45 1.2e-3 0xffff (hex) 0b01011 (binary) 0377 (octal) // ?a ASCII character // ?\\ ASCII value of backslash // ?\C-a Control-a // ?\M-a Meta-a // ?\M-\C-a Meta-Control-a // :symbol Integer corresponding to identifiers, variables, and operators. // #define R_INT "[1-9](_?[0-9])*" #define R_INT0 "[0-9](_?[0-9])*" #define R_DEC "-?(" R_INT "|0)(%." R_INT0 ")?" "([eE]-?" R_INT0 ")?" #define R_OCT "0(_?[0-7])*" #define R_HEX "0x[0-9a-fA-F](_?[0-9a-fA-F])*" #define R_CHA "%?(\[MC]-)*([^ \\]|\\\\|\\s)" #define R_SYM ":[a-zA-Z_][a-zA-Z0-9_]*" #define R_NUM "(" R_DEC "|" R_OCT "|" R_HEX "|" R_CHA "|" R_SYM ")" // // Perl-style variables for convenience and obfuscation... // // $! The exception information message set by 'raise'. // $@ Array of backtrace of the last exception thrown. // $& The string matched by the last successful pattern match in this scope. // $` The string to the left of the last successful match. // $' The string to the right of the last successful match. // $+ The last bracket matched by the last successful match. // $1 The Nth group of the last successful match. May be > 1. // $~ The information about the last match in the current scope. // $= The flag for case insensitive, nil by default. // $/ The input record separator, newline by default. // $\ The output record separator for the print and IO#write. Default is nil. // $, The output field separator for the print and Array#join. // $; The default separator for String#split. // $. The current input line number of the last file that was read. // $< The virtual concatenation file of the files given on command line. // $> The default output for print, printf. $stdout by default. // $_ The last input line of string by gets or readline. // $0 Contains the name of the script being executed. May be assignable. // $* Command line arguments given for the script sans args. // $$ The process number of the Ruby running this script. // $? The status of the last executed child process. // $: Load path for scripts and binary modules by load or require. // $" The array contains the module names loaded by require. // $-0 The alias to $/. // $-a True if option -a is set. Read-only variable. // $-d The alias to $DEBUG. // $-F The alias to $;. // $-i In in-place-edit mode, this variable holds the extention, otherwise nil. // $-I The alias to $:. // $-l True if option -l is set. Read-only variable. // $-p True if option -p is set. Read-only variable. // $-v The alias to $VERBOSE. // // These will count as pre-defined globals: // // $DEBUG The status of the -d switch. // $FILENAME Current input file from $<. Same as $<.filename. // $LOAD_PATH The alias to $:. // $stderr The current standard error output. // $stdin The current standard input. // $stdout The current standard output. // $VERBOSE The verbose flag, which is set by the -v switch. // #define R_PERL_VAR "%$[!@&`'+~=/\\,;.<>_*$?:\"]|%$[0-9]+" \ "|%$-[0adFiIlpv]" // "|%$(DEBUG|FILENAME|LOAD_PATH|stderr|stdin|stdout|VERBOSE)" // // Sample Ruby strings: // // 'string' // // 'no #{subst}' // %q!no #{subst}! // // "#{subst}, #{$subst}, #{@subst}, and backslashes\n" // %Q(#{subst} and backslashes) // // %!#{subst} and backslashes! // %[paired [delimiters] can nest] // // `echo command interpretation with #{subst} and backslashes` // %x/echo command interpretation with #{subst} and backslashes/ // ruby_parse_string(int t) { int level = 0; char delim = character(t); char can_nest = 0; char can_replace = 1; char shell_string = '`' == delim; if (strchr("\"'`", delim)) { can_replace = delim != '\''; point = t + 1; } else if (delim != '%') { // say("ruby_parse_string() - unknown string start '%c'", delim); return; } else if (strchr("qQx", character(t+1))) { can_replace = character(t+1) != 'q'; shell_string = character(t+1) == 'x'; delim = character(t+2); point = t + 3; } else { delim = character(t+1); point = t + 2; } char search_str[30]; strcpy(search_str, "["); if (delim == '(') { can_nest = delim; delim = ')'; strcat(search_str, "()"); } else if (delim == '<') { can_nest = delim; delim = '>'; strcat(search_str, "<>"); } else if (delim == '{') { can_nest = delim; delim = '}'; strcat(search_str, "{}"); } else if (delim == '[') { can_nest = delim; delim = ']'; // close bracket must come first in char group strcat(search_str, "]["); } else if (!strchr("~!@#$%^&*_+-=|\\:;,./?)]}>'\"`", delim)) { // say("ruby_parse_string() - unknown delim '%c'", delim); return; } else { int len = strlen(search_str); search_str[len] = delim; search_str[len+1] = '\0'; } if (can_replace && delim != '#') { strcat(search_str, "#"); } strcat(search_str, "\\]"); // set the color classes to use // int cc_str, cc_subst; if (shell_string) { cc_str = color_class ruby_shell_cmd; cc_subst = color_class ruby_shell_subst; } else { cc_str = color_class ruby_string; cc_subst = color_class ruby_str_subst; } // say("ruby_parse_string() search_str = '%s'", search_str); while (re_search(1, search_str)) { char ch = character(point - 1); if (ch == '\\') { ++point; } else if (ch == can_nest) { ++level; } else if (can_replace && ch == '#' && character(point) == '{') { set_character_color(t, point, cc_str); t = point - 1; search(1, "}"); set_character_color(t, point, cc_subst); // say("ruby_parse_string() replace code - %d to %d", t, point); t = point; } else if (ch == delim) { --level; if (level < 0) { break; } } else if (can_replace && ch == '#') { // do nothing - just to avoid the message below } else { // This implies the pattern was malformed say("ruby_parse_string() unknown '%c' - %d, %d", ch, t, point); } } set_character_color(t, point, cc_str); } // `/' any_char* `/'[`e'|`i'|`m'|`n'|`o'|`s'|`u'|`x'] // `%'`r' char any_char* char // // /normal regex/i // %r|alternate form| // // /i case insensitive // /m multiline mode - '.' will match newline // /x extended mode - whitespace is ignored // /o perform #{...} substitutions just once // /[neus] encoding: none, EUC, UTF-8, SJIS, respectively // int ruby_parse_regexp(int t) { int level = 0; char delim = character(t); char can_nest = 0; if ('/' == delim) { point = t + 1; } else if (delim != '%' || character(t+1) != 'r') { // say("ruby_parse_regexp() - unknown Regexp start '%c'", // delim); return 0; } else { delim = character(t+2); point = t + 3; } char search_str[30]; strcpy(search_str, "[]-[\\\n"); if (delim == '(') { can_nest = delim; delim = ')'; strcat(search_str, "()"); } else if (delim == '<') { can_nest = delim; delim = '>'; strcat(search_str, "<>"); } else if (delim == '{') { can_nest = delim; delim = '}'; strcat(search_str, "{}"); } else if (delim == '[') { can_nest = delim; delim = ']'; // square brackets already in search string } else if (!strchr("~!@#$%^&*_+-=|\\:;,./?)]}>'\"`", delim)) { // say("ruby_parse_regexp() - unknown delim '%c'", delim); return 0; } else if (!strchr(search_str, delim)) { int len = strlen(search_str); search_str[len] = delim; search_str[len+1] = '\0'; } strcat(search_str, "]"); // say("ruby_parse_regexp() search_str = '%s'", search_str); while (re_search(1, search_str)) { char ch = character(point - 1); if (ch == '\\') { ++point; } else if (ch == '\n') { return 0; } else if (ch == '[') { int ps = point; if (re_search(1, "[^\\][]]")) { } else { point = ps; to_end_line(); break; } } else if (ch == can_nest) { ++level; } else if (ch == delim) { --level; if (level < 0) { if (matches_at(point, 1, "[eimnosux]+")) { point = matchend; } break; } } else if (strchr("]-", ch)) { // do nothing - just to avoid the message below } else { // This implies the pattern was malformed say("ruby_parse_regexp() unknown '%c' - %d, %d (%d:%d)", ch, t, point, lines_between(0, point, 0)+1, get_column(point)+1); } } set_character_color(t, point, color_class ruby_regexp); return 1; } // // Pre-defined global constants // // ttp://www.ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/variable.html // // TRUE The typical true value. // FALSE The false itself. // NIL The nil itself. // STDIN The standard input. The default value for $stdin. // STDOUT The standard output. The default value for $stdout. // STDERR The standard error output. The default value for $stderr. // ENV The hash contains current environment variables. // ARGF The alias to the $<. // ARGV The alias to the $*. // DATA The file object of the script, pointing just after __END__. // RUBY_VERSION The ruby version string (VERSION was depricated). // RUBY_RELEASE_DATE The relase date string. // RUBY_PLATFORM The platform identifier. // // Also including these as pre-defined globals: // // $DEBUG The status of the -d switch. // $FILENAME Current input file from $<. Same as $<.filename. // $LOAD_PATH The alias to $:. // $stderr The current standard error output. // $stdin The current standard input. // $stdout The current standard output. // $VERBOSE The verbose flag, which is set by the -v switch. // // // Built-in functions // // http://www.ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/function.html // // Some methods defined in the Kernel module can be called from // everywhere, and are to be called like functions. You'd better think // twice before redefining these methods. // // Array(arg) Convert argument to array using to_a. // Float(arg) Convert argument to float value. // Integer(arg) Convert argument to integer value. // String(arg) Convert argument to string using Kernel#to_s. // at_exit Register block for clean-up. // autoload(module, file) Specifies file to autoload to access module. // binding Returns variable/method binding. // caller([level]) Returns context information (the backtrace) // catch(tag){...} Executes block, catching given throws. // chop Returns $_ with last character removed. // chop! Self-modifying chop. // chomp([rs]) Returns $_ with line ending removed. // chomp! Self-modifying chomp. // eval(expr) Evaluate expr as a Ruby program. // exec(command...) Executes command as subprocess, never returns. // exit([status]) Exits immediately with status. // exit! Exits, ignoring exception handling. // fork Does a fork(2) system call. // gets([rs]) Read string from input into $_. // readline([rs]) Read string from input into $_. // global_variables Returns list of global variable names. // gsub(pattern[, replace]) RegExp substitution on $_. // gsub! Self-modifying gsub. // iterator? Returns true, if called inside a block. // load(file[, priv]) Loads and evaluates the Ruby program in file. // local_variables Returns list of local variable names. // loop Loops forever (until terminated explicitly). // open(file[, mode]) Opens the file, and returns a File object. // p(obj) Prints human-readable form of obj to stdout. // print(arg1...) Prints arguments (or $_). // printf(...) Formatted printing. // proc Makes a block into a procedure object. // lambda Makes a block into a procedure object. // putc(c) Writes the char c to the default output ($>). // raise Raises an exception. // fail Raises an exception. // rand(max) Random integer in [1..max-1] // readlines([rs]) Reads input into an array of lines. // require(feature) Demands library file specified by feature. // select() Calls select(2) system call. // sleep([sec]) Sleep for sec seconds. // split([sep[, limit]]) Splits a string into an array. // format(format...) Returns string formatted like sprintf. // sprintf(format...) Returns formatted string. // srand([seed]) Sets the random number seed for the rand. // sub(pattern[, replace]) Returns $_ with a single RegExp sub performed. // sub! Self-modifying form of sub. // syscall(num, arg...) Makes a system call as specified. // system(command...) Perform command in a sub-process. // test(cmd, file [, file]) Does a file test. // throw(tag[, value]) Casts an non-local exit to enclosing catch. // trace_var() Set hook to execute on var change. // trap(signal, command) Specifies the signal handler for the signal. // untrace_var() Deletes trace_var hook. // // // Pre-defined classes and modules // // http://www.ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/classes.html // // Object // Array // Data // Dir // Exception // Interrupt // NotImplementError // SignalException // StandardError // ArgumentError // FloatDomainError // IOError // EOFError // IndexError // LoadError // LocalJumpError // NameError // RuntimeError // SecurityError // SyntaxError // SystemCallError // Errno::E* // SystemStackError // ThreadError // TypeError // ZeroDivisionError // SystemExit // fatal // Hash // IO // File // MatchingData // Module // Class // Numeric // Integer // Bignum // Fixnum // Float // Proc // Range // Regexp // String // Struct // Time // NilClass // Comparable // Enumerable // Errno // FileTest // GC // Kernel // Marshal // Math // ObjectSpace // Precision // Process // // Return code for word from here to point (something with alpha or // digits). // // 0 = unknown (identifier?) // 1 = Keyword // 2 = Regexp // 3 = Pre-defined global constants, e.g., ARGV. // 4 - Pre-defined classes and modules // 5 - Built-in functions // 6 - Common methods // 7 - Global Variable, e.g., $myGlobal // 8 - Class instance variable, e.g., @length // 9 - Class variable, e.g., @@count // is_ruby_keyword(int from) { char buf[500], *s; save_var case_fold = 0; if (point - from > sizeof(buf) / sizeof(char) - 10) save_var point = from + sizeof(buf) / sizeof(char) - 10; // put identifier into string with '|' chars on either side // buf[0] = '|'; grab(from, point, buf + 1); strcat(buf, "|"); // If it follows a period, don't count it as a keyword. // char follows_period = 0; // matches_at(from, -1, "%.[ \t]*"); // Keywords (mostly reserved) // if (!follows_period && strstr( "|__FILE__|__LINE__|alias|and|BEGIN|begin|break|case|catch" "|class|def|defined?|do|else|elsif|END|end|ensure|false|for" "|if|in|include|initialize|loop|module|new|next|nil|not|or" "|private|protected|public|raise|redo|require|rescue|retry" "|return|self|super|then|throw|true|undef|unless|until|when" "|while|yield" "|", buf)) { return 1; } // Special case: highlight this class name as if it were a Regexp // itself. // if (!strcmp("|Regexp|", buf)) { return 2; } // Pre-defined global constants // if (strstr( "|TRUE|FALSE|NIL|STDIN|STDOUT|STDERR|ENV|ARGF|ARGV|DATA" "|RUBY_VERSION|RUBY_RELEASE_DATE|RUBY_PLATFORM" "$DEBUG|$FILENAME|$LOAD_PATH|$stderr|$stdin|$stdout|$VERBOSE" "|", buf)) { return 3; } // Pre-defined classes and modules // if (strstr( "|ArgumentError|Array|Bignum|Class|Comparable|Data|Dir" "|Enumerable|EOFError|Errno|Exception|fatal|File|FileTest" "|Fixnum|Float|FloatDomainError|GC|Hash|IndexError|Integer" "|Interrupt|IO|IOError|Kernel|LoadError|LocalJumpError|Marshal" "|MatchingData|Math|Module|NameError|NilClass" "|NotImplementError|Numeric|Object|ObjectSpace|Precision|Proc" "|Process|Range|Regexp|RuntimeError|SecurityError" "|SignalException|StandardError|String|Struct|SyntaxError" "|SystemCallError|SystemExit|SystemStackError|ThreadError|Time" "|TypeError|ZeroDivisionError" "|", buf)) { return 4; } // Built-in functions // (Some items, e.g. Array, overlap previous sections) // if (strstr( "|Array|at_exit|autoload|binding|caller|chomp|chomp!|chop" "|chop!|eval|exec|exit|exit!|fail|Float|fork|format|gets" "|global_variables|gsub|gsub!|Integer|iterator?|lambda|load" "|local_variables|open|open|p|print|printf|proc|putc|putc|rand" "|readline|readlines|select|sleep|split|sprintf|srand|String" "|sub|sub!|syscall|system|test|trace_var|trap|untrace_var" "|", buf)) { return 5; } // Common methods // if (strstr( "|each|each_with_index|load|to_f|to_i|to_s" "|", buf)) { return 6; } // Global variables // if ('$' == buf[1]) { return 7; } // Global variables // if ('@' == buf[1]) { return ('@' == buf[2]) ? 8 : 9; } return 0; } ruby_parse_command(int t) { point = t; point += parse_string(1, "[ \t]*"); set_character_color(t, point, -1); t = point; if (parse_string(1, "#")) { to_end_line(); set_character_color(t, point, color_class ruby_comment); } else if (parse_string(1, "=begin")) { re_search(1, "^[ \t]*=end"); to_end_line(); set_character_color(t, point, color_class ruby_comment); } else if (parse_string(1, "=end")) { to_end_line(); t = point; re_search(-1, "^[ \t]*=begin"); to_begin_line(); set_character_color(point, t, color_class ruby_comment); point = t; } // Take out %=string= and %$string$ for now... // else if (matches_at(point, 1, "[\"'`]|(%%[]-qQx{<[()>}~!@#%^&*_+%|\\:;,./?])")) { ruby_parse_string(point); } else if (matches_at(point, 1, R_NUM)) { point = matchend; set_character_color(t, point, color_class ruby_number); } else if (matches_at(point, 1, R_PERL_VAR)) { point = matchend; set_character_color(t, point, color_class ruby_perl_var); } else if (matches_at(point, 1, "/|(%%r)") && !matches_at(point, RE_REVERSE, "(<^c:*keyword>" RUBY_IDENT "|" R_NUM "|[])])[ \t]*" )) { ruby_parse_regexp(point); } else if (parse_string(1, RUBY_IDENT "[!?]?")) { point = matchend; switch (is_ruby_keyword(t)) { case 1: set_character_color(t, point, color_class ruby_keyword); break; case 2: set_character_color(t, point, color_class ruby_regexp); break; case 3: set_character_color(t, point, color_class ruby_global); break; case 4: set_character_color(t, point, color_class ruby_class); break; case 7: set_character_color(t, point, color_class ruby_global_var); break; case 8: set_character_color(t, point, color_class ruby_instance_var); break; case 9: set_character_color(t, point, color_class ruby_class_var); break; // Ignore 5 & 6 for now, until I decide what I want to do. // default: set_character_color(t, point, -1); break; } } else if (strchr("{}[]", character(point))) { ++point; set_character_color(t, point, color_class ruby_brace); } else if (matches_at(point, 1, "::|[]-`~!@#$%^&*()_=+[\\|;':\"<>?,./]")) { point = matchend; set_character_color(t, point, color_class ruby_punctuation); } else { say("ruby_parse_command() unknown '%c' - %d (%d:%d)", character(point), point, lines_between(0, point, 0)+1, get_column(point)+1); ++point; } } color_ruby_range(from, to) { if (from >= to) return to; save_var point, matchstart, matchend; point = to; // Color entire lines. nl_forward(); to = point; set_character_color(from, to, -1); point = from; ruby_before_continuation(); int t = point; while (point < to) { if (!re_search(1, "[^ \t\n]")) { t = size(); break; } t = matchstart; ruby_parse_command(t); } if (to < t) set_character_color(to, t, -1); return point; } // Indent the following line more? // // Typically true for lines following one of: // // def | class | module | if | elsif | else | unless | while | // until | case | when | begin | for | rescue // int ruby_indent_more() { if (parse_string(1, "(^|.*;|.*=)[ \t]*" "(def|class|module|if|elsif|else|unless|while|until|case|when|begin|for|rescue|ensure)")) { if (!parse_string(1, ".*end")) // Multiple statements: ignore block. return 1; else return 0; } // Check for braces/brackets // char* brace_pat = ".*([][}{]|[)(]|(do))"; if (parse_string(1, brace_pat)) { int count = 0; do { // opening brace or 'o' in 'do' // if (strchr("[{(o", character(matchend - 1))) { ++count; } else { --count; } } while (matches_at(matchend, 1, brace_pat)); if (ruby_closeback && count < 0) ++count; return count; } // Odd case of an 'end' embedded in the line. // if (parse_string(1, ".*;[ \t]*end")) { return -1; } return 0; } // Indent *this* line less? // // True for lines starting with one of: // // end | when | else | elsif | rescue // // If ruby_closeback is set then also true for lines starting with a // closing brace/bracket. // int ruby_indent_less() { char* brace_pat = "[ \t]*([]}]|[)])"; if (ruby_closeback && parse_string(1, brace_pat)) { return 1; } return parse_string(1, // "(.*;)?" "[ \t]*(" "end|when|else|elsif|rescue|ensure" ")") != 0 ? 1 : 0; } // Is this a continuation line? // ruby_is_continuation() { save_var point; to_begin_line(); // leave out '|' since 'array.each { |pair|' is so common. // return parse_string(RE_REVERSE, "(\\|([-%&*+@/<=>^~])[ \t]*)\n"); } // If this is a continuation line, move back to the actual start. // ruby_before_continuation() { while (ruby_is_continuation()) nl_reverse(); to_begin_line(); } ruby_step() { return (ruby_indent > 0) ? ruby_indent : tab_size; } // Indent line at orig based on this one. // ruby_indent_continuation(orig) { int ind; ruby_before_continuation(); ind = get_indentation(point); point = orig; indent_to_column(ind + 2 * ruby_step()); } do_ruby_indent() on ruby_tab['\t'] { if (maybe_indent_rigidly(0)) return; if (this_cmd != CMD_INDENT_REG) this_cmd = C_INDENT; if (current_column() > get_indentation(point) || prev_cmd == C_INDENT) { indent_like_tab(); return; } ruby_indenter(); } ruby_indenter() { int orig = point, ind; recolor_buffer_range(0, size()); if (matches_at(give_begin_line(), 1, "=begin|=end") || point <= narrow_start) { indent_to_column(0); return; } if (ruby_is_continuation()) { ruby_indent_continuation(orig); return; } do { to_begin_line(); if (!re_search(-1, "[^ \t\n]")) /* Find previous non-blank line */ break; to_indentation(); } while (parse_string(1, "#")); // that's not a comment. ruby_before_continuation(); ind = get_indentation(point); ind += ruby_step() * ruby_indent_more(); point = orig; if (ruby_indent_less()) ind -= ruby_step(); to_indentation(); /* go to current line's indent */ indent_to_column(ind); } // move back to nearest tab stop // unindent if in the initial whitespace of the line // command ruby_unindent() on ruby_tab[NUMSHIFT(GREYTAB)] { int tab = get_soft_tab_size(), old, old_point; if (maybe_indent_rigidly(1)) return; old = current_column(); old_point = point; to_indentation(); if (point >= old_point) { old_point = point; move_to_column(((current_column() - 1) / tab) * tab); delete(old_point, point); return; } else { point = old_point; } move_to_column(((current_column() - 1) / tab) * tab); if (old && old == current_column()) point--; } // fix indentation if necessary when } or ) is typed // command ruby_close() on ruby_tab['}'], ruby_tab[')'], ruby_tab[']'] { // only if typed inside indentation, & it might need fixup // normal_character(); if (current_column() - 1 <= get_indentation(point)) { fix_ruby_indentation(); } save_var inside_show_matching_delimiter = 1; if (Matchdelim) find_delimiter(); } // ++[TB] 9/22/2006 - v 1.1.0 additions by Jim Morris // indent if the d of end is typed so end indents immediately like } does // command ruby_maybe_indent_end() on ruby_tab['d'], ruby_tab['e'], ruby_tab['f'], ruby_tab['n'] { normal_character(); recolor_range(give_begin_line(), point); if (matches_at(point, -1, "(end|when|else|elsif|rescue|ensure)")) fix_ruby_indentation(); } // [TB]++ 9/22/2006 - v 1.1.0 additions by Jim Morris // recompute this line's indentation without moving point // fix_ruby_indentation() { if (in_shell_buffer) return; save_spot point; to_indentation(); ruby_indenter(); } command ruby_mode() { mode_default_settings(); mode_keys = ruby_tab; /* Use these keys. */ major_mode = _ruby_mode_name; compile_buffer_cmd = compile_ruby_cmd; // can compile this strcpy(comment_start, "#[ \t]*"); strcpy(comment_pattern, "#.*$"); strcpy(comment_begin, "# "); strcpy(comment_end, ""); recolor_range = color_ruby_range; // set up coloring rules recolor_from_here = recolor_from_top; if (want_code_coloring) // maybe turn on coloring when_setting_want_code_coloring(); if (auto_show_ruby_delimiters) auto_show_matching_characters = ruby_auto_show_delim_chars; indent_with_tabs = ruby_indent_with_tabs; indenter = ruby_indenter; auto_indent = 1; fill_mode = misc_language_fill_mode; try_calling("ruby-mode-hook"); drop_all_colored_regions(); make_mode(); } when_loading() { ruby_tab[ALT('q')] = (short) find_index("fill_comment"); } // show_debug_text(int from, int to, char* message, int level) // { // char buf[100]; // to = MIN(to, from + ptrlen(buf) - 1); // grab(from, to, buf); // say("%s '%s' at %d (%d:%d) level %d", message, buf, from, // lines_between(0, from, 0)+1, get_column(from)+1, level); // } // Set display_func_name to the name of the function we're editing, and // return 1. If not in a function, set display_func_name to "" and // return 1. If user pressed a key and we gave up for now, return 0. // ruby_func_name_finder() { int from, to; char classname[FNAMELEN]; // char defname[FNAMELEN]; *classname = '\0'; *defname = '\0'; char buf[FNAMELEN]; char isclass = 1; int level = 0; save_var point, case_fold = 0; // if we are at the start of a 'class' or 'def' line, it'll count // as being inside the class or method, so go to the end of the // line to enable the search function to find the keyword. // to_end_line(); int startingLine = lines_between(0, point, 0); // For things like "def <=>(other)" //#define RUBY_METH RUBY_NAME #define RUBY_METH "[^ \t\n(;]+" while (do_color_searching(SEARCH_REGEX | SEARCH_REVERSE, "((def|class|module)[ \t\n]+" "(" RUBY_METH "))" "|(end|do)" "|(^|;|=)[ \t]*(" "if|unless|case|while|until|begin|for" ")")) { grab(matchend, MIN(matchstart, matchend + ptrlen(buf) - 1), buf); if (!strncmp(buf, "end", 3)) { // Ignore one 'end' on the starting line. // This allows the 'end' line for a class or method to // count as part of the class or method. // if (lines_between(0, matchstart, 0) == startingLine) { startingLine = -1; } else { ++level; } } else if (!strncmp(buf, "class", 5) || !strncmp(buf, "module", 6)) { --level; if (level < 0) { isclass = buf[0] == 'c'; from = find_group(3, 1); to = find_group(3, 0); to = MIN(to, from + ptrlen(classname) - 1); grab(from, to, classname); break; } } else if (!strncmp(buf, "def", 3)) { if (level <= 0 && !*defname) { from = find_group(3, 1); to = find_group(3, 0); if (matches_at(to, 1, "(%.|::)(" RUBY_NAME ")" )) { from = find_group(2, 1); to = find_group(2, 0); } to = MIN(to, from + ptrlen(defname) - 1); grab(from, to, defname); } else { --level; } } else { // prevent plain old scripting outside a class/def from // picking up a name on the same level.. // if (level > 0) --level; if (buf[0] != ';') do_color_searching(SEARCH_REGEX | SEARCH_REVERSE, "(\n|;)"); } } if (*classname && *defname) { if (strlen(classname) + strlen(defname) + 10 >= ptrlen(display_func_name)) { sprintf(display_func_name, "def ...::%.*s", ptrlen(display_func_name) - 10, defname); } else { sprintf(display_func_name, "def %s::%s", classname, defname); } } else if (*classname) { sprintf(display_func_name, "%s %.*s", isclass ? "class" : "module", ptrlen(display_func_name) - 10, classname); } else if (*defname) { sprintf(display_func_name, "def %.*s", ptrlen(display_func_name) - 10, defname); } else { *display_func_name = 0; } return 1; } tag_mode_ruby() { char func[TAGLEN]; int start; save_var case_fold = 1; while (re_search(1, "^[ \t]*(def|class|module)[ \t]+(" RUBY_IDENT "(\\." RUBY_NAME ")?)")) { grab(start = find_group(2, 1), find_group(2, 0), func); add_tag(func, start); } } suffix_rb() { ruby_mode(); } /* Ruby is a line-oriented language. Ruby expressions and statements are * terminated at the end of a line unless the statement is obviously * incomplete---for example if the last token on a line is an operator * or comma. A semicolon can be used to separate multiple expressions on * a line. You can also put a backslash at the end of a line to continue * it onto the next. Comments start with `#' and run to the end of the * physical line. Comments are ignored during compilation. * * if, unless, case, while, until, begin, for, do (might not be * initial), def, class, module */ // if bool-expr [then] // body // elsif bool-expr [then] // body // else // body // end // // unless bool-expr [then] // body // else // body // end // // expr if bool-expr // // expr unless bool-expr // // case target-expr // when comparison [, comparison]... [then] // body // [else // body] // end // // (case comparisons may be regexen) // // while bool-expr [do] // body // end // // until bool-expr [do] // body // end // // begin // body // end while bool-expr // // begin // body // end until bool-expr // // for name[, name]... in expr [do] // body // end // // expr.each do | name[, name]... | // body // end // // expr while bool-expr // // expr until bool-expr // // // Also significant for indenting: // // expr.each { | name[, name]... | // body // } // // square bracket for array, curly brace for hash // ++[TB] 9/22/2006 - v 1.1.0 additions by Jim Morris /* * Get Ruby help using the Ruby ri command * ri must be installed on your path * whatever is highlighted will be passed as the argument to ri, if * nothing is highighted you will prompted for the string to lookup. */ command get_ruby_help() on ruby_tab[NUMALT(FKEY(1))] { char helpstr[512]; char tag[128]; save_spot point, mark; say(""); fix_region(); // find the function name to get help on, either use selected // region or prompt for it if (is_highlight_on() && mark - point < sizeof(tag)){ grab(point, mark, tag); highlight_off(); }else{ get_string(tag, "Ruby Help: "); } sprintf(helpstr, "ri -T %s", tag); pipe_text(NULL, "-rubyhelp", helpstr, NULL, PIPE_SYNCH|PIPE_CLEAR_BUF|PIPE_SKIP_SHELL , 0); view_buffer("-rubyhelp", 0); delete_buffer("-rubyhelp"); } // these need to be global as per eel manual char ruby_env1[FNAMELEN]; char ruby_env2[FNAMELEN]; // add some usefull things to the environment that the scripts can use void add_environment() { sprintf(ruby_env1, "EPS_FILENAME=%s", filename); putenv(ruby_env1); if(strlen(defname) > 0){ sprintf(ruby_env2, "EPS_CURRENT_METHOD=%s", defname); putenv(ruby_env2); }else{ putenv("EPS_CURRENT_METHOD"); // delete it } } /* * Pipe the current buffer through the ruby interpreter and display * results in a popup, unless there is an arg where bit 0 is set in which * case send results to a visible buffer and switch to it. * If there is a an arg and bit 1 is set it will prompt for arguments to send to the script */ command ruby_run() on ruby_tab[NUMALT(FKEY(11))] { char rubycmd[512]; char *outbuf; char args[500]; int showbuf= 0; // see if we want to switch to the output buffer if(has_arg && (iter & 1) != 0){ outbuf= "ruby_process"; showbuf= 1; }else outbuf= temp_buf(); strcpy(rubycmd, "ruby - "); // see if we need to prompt for arguments if(has_arg && (iter & 2) != 0){ get_str_auto_def(args, "script arguments"); strcat(rubycmd, args); } iter= 0; // add some useful environment variables add_environment(); pipe_text(bufname, outbuf, rubycmd, NULL, PIPE_SYNCH|PIPE_CLEAR_BUF|PIPE_SKIP_SHELL , 0); if(!showbuf){ view_buffer(outbuf, 0); delete_buffer(outbuf); }else{ to_buffer(outbuf); } } /* * Run a specific test case with a ruby test unit, the test case run is the method * that the cursor is currently in, it will save the file if needed * the results will be displayed in a popup, unless there is an arg in which case send * results to a visible buffer and switch to it */ command ruby_run_test_case() on ruby_tab[NUMALT(NUMSHIFT((FKEY(11))))] { if(strlen(defname) > 0 && strncmp(defname, "test_", 5) == 0){ if(is_unsaved_buffer()){ // switch (bufed_ask_save()) { // case 0: // case 1: error("Not saved or run"); // } save_file(); } // add some useful environment variables add_environment(); char rubycmd[512]; sprintf(rubycmd, "ruby %s --name %s", filename, defname); pipe_text(NULL, "ruby-test-run", rubycmd, NULL, PIPE_SYNCH|PIPE_CLEAR_BUF|PIPE_SKIP_SHELL , 0); if(has_arg) to_buffer("ruby-test-run"); else view_buffer("ruby-test-run", 0); }else{ error("Not in a ruby test case method"); } } /* * Run the given string through the ruby interpreter and return the buffer number * of where the results are * the number of arguments to pass are specifed followed by an array of strings of the arguments */ int ruby_execute(char *script, int nargs, char *vals[]) { char *inp_buffer= temp_buf(); int size; char cmd[512]; strcpy(cmd, "ruby - "); int i; for(i=0;i 0){ inp_buffer= temp_buf(); xfer(inp_buffer, point, mark); highlight_off(); } // run the program with the selection as input pipe_text(inp_buffer, "-ruby-pipe-output", cmd, NULL, PIPE_SYNCH|PIPE_CLEAR_BUF|PIPE_SKIP_SHELL , 0); // get the output into a string bufname= "-ruby-pipe-output"; char *res= NULL; grab_expanding(0, size(), &res, 256); bufnum= oldbufnum; return res; } // the show_table_script defined in-line char show_table_script_rb[]= "\ #! /usr/local/bin/ruby\n\ if ARGV.size < 2\n\ STDOUT << \"Usage: show_tables model project_path\"\n\ exit 1\n\ end\n\ \n\ word= ARGV[0]\n\ project= ARGV[1]\n\ \n\ require \"#{project}/config/boot\"\n\ require \"#{project}/config/environment\"\n\ \n\ klass = Object.const_get(word) rescue nil\n\ if klass and klass.class == Class and klass.ancestors.include?(ActiveRecord::Base)\n\ columns = klass.columns_hash\n\ \n\ data = []\n\ data += [%w[column primary sql_type default]]\n\ data += [%w[------ ------- -------- -------]]\n\ data += columns.collect { |col, attrs| [col, attrs.primary.to_s, attrs.sql_type.to_s, attrs.default.to_s] }\n\ \n\ STDOUT << data.inject('') do |output, array|\n\ output + array.inject('') { |row_str, value| row_str + value.ljust(20) } + \"\\n\"\n\ end\n\ elsif klass and klass.class == Class and not klass.ancestors.include?(ActiveRecord::Base)\n\ STDOUT << \"#{word} is not an Active Record derived class\"\n\ else\n\ STDOUT << \"#{word} was not recognised as a class\"\n\ end\n"; // Example of how to use ruby_execute_to_string() // // command ruby_test() // { // char *args[2]; // args[0]= "User"; // args[1]= "/home/morris/work/ruby/rails/testapp"; // char *res= ruby_execute_to_string(show_table_script_rb, 2, args); // stuff(res); // free(res); // } /* * Show columns of the selected Model in a pop up. * * (Thanks to http://blog.seagul.co.uk/articles/2006/07/14/textmate-command-to-display-active-record-column-attributes) * The current buffer must be a file that is either in app/views/aview/... or app/controllers or app/models * */ command ruby_show_columns() { char model[128]; char project_path[FNAMELEN]; save_spot point, mark; say(""); fix_region(); // find the Model name to get columns for // region or prompt for it if (is_highlight_on() && mark - point < sizeof(model)){ grab(point, mark, model); highlight_off(); }else{ get_string(model, "Model name: "); } // figure out project path from file we are looking at strcpy(project_path, filename); char *t= get_tail(project_path); char *ext= get_extension(filename); if(strcmp(ext, ".rhtml") == 0 || strcmp(ext, ".rjs") == 0){ // presume we are in a view strcpy(t, "../../.."); }else if(strcmp(ext, ".rb") == 0){ // presume we are in a controller or model strcpy(t, "../.."); } // run the show_columns ruby script which extracts columns for the given model in project path char *args[2]; args[0]= model; args[1]= project_path; int resbuf= ruby_execute(show_table_script_rb, 2, args); view_buf(resbuf, 0); buf_delete(resbuf); } /* * Toggle between the rails view and controller for the current function * If it is an .rhtml file then find ../../controllers/[name]-controller.rb where name is the current * leaf directory node. * If it is a .rb file then find ../views/[name].rhtml where name is the current method * */ command ruby_toggle_view_controller() on reg_tab[NUMCTRL(NUMALT('v'))] { char name[FNAMELEN]; char path[FNAMELEN]; char path2[FNAMELEN]; char action[132]; char *ext= get_extension(filename); if(strcmp(ext, ".rhtml") == 0 || strcmp(ext, ".rjs") == 0){ strcpy(action, "def "); char *tail= get_tail(filename); strncat(action, tail, strlen(tail)-strlen(ext)); strcpy(name, filename); char *taile= rindex(name, path_sep); if(taile == NULL) return; *taile= 0; char *tails= rindex(name, path_sep); if(tails == NULL) return; strcpy(path, filename); char *p1= strstr(path, "/views/"); // TODO need to be path_sep if(p1 == NULL) return; *p1 = 0; strcat(path, "/controllers"); strcat(path, tails); strcat(path, "_controller.rb"); find_it(path, 0); point = 0; search(1, action); }else if(strcmp(ext, ".rb") == 0){ if(strlen(defname) > 0){ // get view name to use strcpy(name, filename); char *tail= get_tail(name); char *p1= strstr(tail, "_controller"); if(p1 == NULL) return; *p1= 0; strcpy(path, filename); char *p2= strstr(path, "/controllers/"); // TODO need to be path_sep if(p2 == NULL) return; *p2 = 0; strcat(path, "/views/"); strcat(path, tail); strcat(path, "/"); strcat(path, defname); strcpy(path2, path); strcat(path, ".rhtml"); strcat(path2, ".rjs"); if(check_file(path)){ find_it(path, 0); }else if(check_file(path2)){ find_it(path2, 0); }else{ say("No matching view found"); } } } } #define UNDO_FLAG_PRE_SNIPPET (UNDO_FLAG|'S') #define UNDO_FLAG_POST_SNIPPET (UNDO_FLAG|'T') // expands the # if in a string or regexp to #{} placing cursor between // the brackets unless it is escaped with a \. // command ruby_pound_sign() on ruby_tab['#'] { normal_character(); if (ruby_expand_pound != 1) return; int c = get_character_color(point); if (c == color_class ruby_string || c == color_class ruby_shell_cmd || c == color_class ruby_regexp) { // check it is not escaped if (character(point-2) != '\\') { undo_mainloop(); undo_flag = UNDO_FLAG_PRE_SNIPPET; stuff("{}"); --point; undo_flag = UNDO_FLAG_POST_SNIPPET; } } } command ruby_undo() on ruby_tab[FKEY(9)] { if (UNDO_FLAG_POST_SNIPPET == undo_flag) { // say("undoing snippet"); undo_to_flag(UNDO_FLAG_PRE_SNIPPET); } else { // say("normal undo"); undo_it(1, 0); } } command ruby_normal_character() on ruby_tab['\n'] /* more bindings, see below */ { if (UNDO_FLAG_POST_SNIPPET == undo_flag) undo_flag = UNDO_INSERT; normal_character(); } when_loading() /* put it on the other keys, too */ { set_range(ruby_tab, ' ', (short) ruby_normal_character, '~' - ' ' + 1); set_range(ruby_tab, 128, (short) ruby_normal_character, MAX_CHAR - 128 + 1); ruby_tab[')'] = (short) ruby_close; ruby_tab[']'] = (short) ruby_close; ruby_tab['}'] = (short) ruby_close; ruby_tab['d'] = (short) ruby_maybe_indent_end; ruby_tab['e'] = (short) ruby_maybe_indent_end; ruby_tab['f'] = (short) ruby_maybe_indent_end; ruby_tab['n'] = (short) ruby_maybe_indent_end; ruby_tab['#'] = (short) ruby_pound_sign; } // [TB]++ 9/22/2006 - v 1.1.0 additions by Jim Morris