// // btm_mode // // v 1.1.0 // 2005-12-20 // // Timothy Byrd // timbyrd@pobox.com // // (Please contact me with comments/corrections.) // // This is an updated batch mode to work better with the JP Software // products 4NT and TakeCommand. // // History: // // 2005-12-20 - Update for version 7.0 of the JP products. // Also highlight the "Control Variables" (e.g. RecycleExclude) // when they immediately follow the SET keyword. // // 2005-04-06 - Added btm_delete_trailing_spaces variable // // 2005-04-05 - Smart indenting (shift-tab to unindent) // Alias coloring (get list from alias file or running shell) // // 2005-03-24 - Initial version: I've updated the keywords for // version 6.01 of the JP products, and distingish between // built-in commands and common externals (e.g. FORMAT). // // // See the user variables below for info on how the behavior of // btm-mode can be customized. // // // I also color the names of variable functions (distinguishing between // built-in and user defined). I suggest setting btm_function and // btm_userfunction to different colors so it's easy to tell when you // mis-type the name of a built-in function. For example: // // color_class btm_alias 0x8080ff on black; // color_class btm_backquote red on grey; // color_class btm_bat_param red on black; // color_class btm_comment grey on black; // color_class btm_external 0xC4DF02 on black; // color_class btm_function 0x41C0FE on black; // color_class btm_keyword 0xff8000 on black; // color_class btm_label red on black; // color_class btm_punctuation cyan on black; // color_class btm_userfunction red on black; // color_class btm_uservariable 0x00e0b0 on black; // color_class btm_variable 0x40C0F7 on black; // #if 0 // Add these lines to colclass.txt to get descriptions in set-color btm-alias An aliased command in a batch file. btm-backquote A back-quote character (`) in a batch file. btm-bat-param A batch file parameter, e.g., %1, %$, %2$, %#. btm-comment A comment in a batch file (rem or ::). btm-external A commonly used external for a batch file, e.g., FORMAT. btm-function An internal variable function in a batch file e.g., %@makedate[]. btm-keyword An internal keyword in a batch file, e.g., COPY. btm-label A label line in a batch file ( :label ). btm-punctuation Punctuation in a batch file. btm-userfunction A user-defined variable function in a batch file e.g., %@myfunc[]. btm-uservariable A user-defined variable in a batch file e.g., %myvar. btm-variable An internal variable in a batch file, e.g., %_TIME. #endif #include "eel.h" #include "proc.h" #include "colcode.h" #include "misclang.h" // This variable gets a list of words to color as aliases, of the form: // "|alias1|alias2|alias3|" // Set this variable to an empty string to cause it to be refreshed. // Or if your aliases do not change, save the value in your state file // or einit.ecm. // If you have a lot of aliases, you may need to make this larger. // user char btm_alias_list[1024] = ""; // If you use an alias file, set this variable to read the list of // aliases from the file. // If no path is specified, will look in epsilon\bin and epsilon dirs. // //user char btm_alias_file[FNAMELEN] = "aliases.txt"; user char btm_alias_file[FNAMELEN] = ""; // If you don't use an alias list, set this variable so we can fire up // a copy of the shell to list out the aliases. // The value of btm_alias_command is the full command line to get the // alias list. // It should include '/c alias' but may include other options like a // directive to specify the .ini file. // //user char btm_alias_command[128] // = "%JP_EXE_DIR%\\4nt\\4nt.exe /@c:\\jp\\4nt.ini /L /c alias"; user char btm_alias_command[128] = ""; // Alter this variable if you use a command separator other than the // default ampersand '&' // user char btm_command_sep = '&'; // Alter this variable if you use an escape character other than the // default caret '^' // user char btm_escape_char = '^'; // Alter this variable if you use an parameter character other than the // default caret '$' // user char btm_bat_param_char = '$'; // indent by this amt; 0 means use tab size // user buffer short btm_indent = 4; // Since CHCP is internal to 4NT and external to TakeCommand // user char btm_chcp_is_internal = 0; // Since different versions have different keywords.. // user int btm_major_version = 7; user int reindent_after_btm_yank = 20000; user char btm_reindent_previous_line = 1; user char btm_delete_trailing_spaces = 1; buffer char in_shell_buffer; ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// color_class perl_comment; color_class perl_function; color_class perl_string; color_class perl_variable; color_class perl_keyword; color_class btm_alias = color_class perl_function; color_class btm_backquote = color_class perl_string; color_class btm_bat_param = color_class perl_string; color_class btm_comment = color_class perl_comment; color_class btm_external = color_class perl_function; color_class btm_function = color_class perl_function; color_class btm_keyword = color_class perl_keyword; color_class btm_label = color_class perl_variable; color_class btm_punctuation = color_class perl_string; color_class btm_userfunction = color_class perl_function; color_class btm_uservariable = color_class perl_variable; color_class btm_variable = color_class perl_variable; char _btm_mode_name[] = "btm"; keytable btm_tab; // Get the user's alias list // btm_get_aliases() { int aliasBuf; #if 0 aliasBuf = create("my_aliases"); // a named buf we can examine #else aliasBuf = tmp_buf(); // read help text for classes into buf on_exit buf_delete(aliasBuf); #endif save_var bufnum = aliasBuf; // Try to grab an alias file into the temp buffer // if (btm_alias_file && *btm_alias_file) { char* new; if (new = lookpath(btm_alias_file)) file_read(new, FILETYPE_AUTO); else say("Could not open alias file '%s'", btm_alias_file); } // Try run btm_alias_command to get text into temp buffer. // if (size() == 0 && btm_alias_command && *btm_alias_command) { int retval = buf_pipe_text(0, aliasBuf, btm_alias_command, // char *cmdline, "", PIPE_SYNCH|PIPE_NOREFRESH|PIPE_SKIP_SHELL); if (!retval) say("Could not run alias command '%s'", btm_alias_file); } strcpy(btm_alias_list, "|"); char abuf[100]; int start, stop, aliasesOK = 1; point = 0; for (;;) { if (!re_search(1, "^[ \t]*([^= \t\f\n\r]+)=")) { break; } start = find_group(1, 1); stop = find_group(1, 0); grab(start, MIN(stop, start + ptrlen(abuf) - 1), abuf); to_end_line(); // Skip keystroke macros // if ('@' == abuf[0]) { continue; } char* cp = strchr(abuf, '*'); if (!cp) { aliasesOK = aliasesOK && btm_append_alias(abuf); } else { // This expands partial alias names to all possibilities. // e.g.: WHER*EIS => WHER, WHERE, WHEREI and WHEREIS // while (*cp) { *cp = 0; aliasesOK = aliasesOK && btm_append_alias(abuf); *cp = *(cp + 1); ++cp; } } } if (!aliasesOK) { say("Alias list truncated - make btm_alias_list larger!"); } } int btm_append_alias(char* abuf) { if (strlen(btm_alias_list) + strlen(abuf) + 3 > ptrlen(btm_alias_list)) { return 0; } strcat(btm_alias_list, abuf); strcat(btm_alias_list, "|"); return 1; } // Return code for word from here to point (something with alpha or // digits). // // Return values: // // 0 if unknown // 1 for command // 2 for comment // 3 for system function // 4 for user function (any %@ not in the list) // 5 for internal variable // 6 for batch file parameter // 7 for known external // 8 for an aliased command // 9 for punctuation // 10 for user variable // is_btm_keyword(int from) { char buf[200], *s; save_var case_fold = 1; if (point - from > sizeof(buf) / sizeof(char) - 10) save_var point = from + sizeof(buf) / sizeof(char) - 10; buf[0] = '|'; // get identifier, between | chars grab(from, point, buf + 1); // Does it start a command? // (first thing on the line or following a '%+') // // Substitute in the btm_command_sep value for the ampersand // 012345678v char* start_pattern = "(^|%%%+|[&])([ \t]*%*)?[ \t]*[^ ]"; start_pattern[9] = btm_command_sep; char starts_command = matches_at(from+1, -1, start_pattern); // If it's the first thing on the line, remove text after dot (file // extension) to match things like 'format.exe'. // if (starts_command && buf[1] != '.' && (s = strchr(buf + 1, '.'))) *s = 0; strcat(buf, "|"); // Check for aliases // (an astrisk disables an alias) // if (starts_command && !matches_at(from, -1, "%*[ \t]*")) { // Load aliases if necessary // if (!btm_alias_list || strlen(btm_alias_list) == 0) { btm_get_aliases(); } if (btm_alias_list && strstr(btm_alias_list, buf)) return 8; } if (strstr("|rem|", buf) && !matches_at(from, -1, "[^ \n\t@][@ \t]*")) return 2; if (buf[1] == '%' && buf[2] == '@') { if (strstr( "|%@abs|%@agedate|%@alias|%@altname|%@ascii|%@attrib" "|%@caps|%@cdrom|%@ceiling|%@char|%@clip|%@clipw|%@color" "|%@comma|%@console|%@convert|%@crc32|%@cwd|%@cwds|%@date" "|%@day|%@dec|%@decimal|%@descript|%@device|%@digits" "|%@dirstack|%@diskfree|%@disktotal|%@diskused|%@domain" "|%@dow|%@dowf|%@dowi|%@doy|%@enumservers|%@enumshares" "|%@errtext|%@eval|%@exec|%@execstr|%@exetype|%@expand" "|%@ext|%@field|%@fields|%@fileage|%@fileclose|%@filedate" "|%@filename|%@fileopen|%@fileread|%@files|%@fileseek" "|%@fileseekl|%@filesize|%@filetime|%@filewrite" "|%@filewriteb|%@findclose|%@findfirst|%@findnext|%@floor" "|%@format|%@formatn|%@fstype|%@full|%@function|%@getdir" "|%@getfile|%@getfolder|%@history|%@idow|%@idowf|%@if" "|%@inc|%@index|%@iniread|%@iniwrite|%@insert|%@instr" "|%@int|%@ipaddress|%@ipname|%@isalnum|%@isalpha|%@isascii" "|%@iscntrl|%@isdigit|%@isprint|%@ispunct|%@isspace" "|%@isxdigit|%@label|%@left|%@len|%@lfn|%@line|%@lines" "|%@lower|%@ltrim|%@makeage|%@makedate|%@maketime|%@max" "|%@md5|%@min|%@month|%@name|%@numeric|%@option|%@path" "|%@ping|%@random|%@readscr|%@ready|%@regcreate" "|%@regdelkey|%@regexist|%@regquery|%@regset|%@regsetenv" "|%@remote|%@removable|%@repeat|%@replace|%@rexx|%@right" "|%@rtrim|%@search|%@select|%@sfn|%@strip|%@subst|%@substr" "|%@time|%@timer|%@trim|%@truename|%@truncate|%@unc" "|%@unicode|%@unique|%@upper|%@verinfo|%@wattrib|%@wild" "|%@winclass|%@winexename|%@wininfo|%@winmemory" "|%@winmetrics|%@winstate|%@winsystem|%@word|%@words" "|%@workgroup|%@year" "|", buf)) return 3; if (btm_major_version >= 7 && strstr( "|%@afscell|%@afsmount|%@afspath|%@afssymlink|%@afsvolid" "|%@afsvolname|%@assoc|%@average|%@compare|%@count|%@drivetype" "|%@ftype|%@group|%@inode|%@junction|%@lcs|%@links|%@perl" "|%@quote|%@regex|%@regexindex|%@regexsub|%@reverse|%@ruby" "|%@sha1|%@sha256|%@sha384|%@sha512|%@similar|%@snapshot" "|%@summary|%@unquote|%@winapi" "|", buf)) return 3; return 4; } if (buf[1] == '%') { char* b = buf; char* bp; // Change variables of the form %[foo] to %foo // if (buf[2] == '[') { ++b; buf[1] = '|'; buf[2] = '%'; bp = strchr(b, ']'); if (bp) { bp[0] = '|'; bp[1] = '\0'; } } // Remove trailing percent if not bracketed // else if (bp = strchr(b+2, '%')) { bp[0] = '|'; bp[1] = '\0'; } if (strstr( "|%+|%=|%?||%_4ver|%_?||%_acstatus|%_alt|%_ansi|%_batch" "|%_batchline|%_batchname|%_batchtype|%_battery" "|%_batterylife|%_batterypercent|%_bg|%_boot|%_build" "|%_capslock|%_childpid|%_ci|%_cmdline|%_cmdproc|%_cmdspec" "|%_co|%_codepage|%_column|%_columns|%_country|%_cpu" "|%_cpuusage|%_ctrl|%_cwd|%_cwds|%_cwp|%_cwps|%_date" "|%_datetime|%_day|%_detachpid|%_disk|%_dname|%_dos" "|%_dosver|%_dow|%_dowf|%_dowi|%_doy|%_echo|%_fg" "|%_ftperror|%_hlogfile|%_host|%_hour|%_hwprofile|%_idow" "|%_idowf|%_imonth|%_imonthf|%_ininame|%_ip|%_isodate" "|%_kbhit|%_lalt|%_lastdisk|%_lctrl|%_logfile|%_lshift" "|%_minute|%_month|%_monthf|%_numlock|%_pid|%_pipe|%_ppid" "|%_ralt|%_rctrl|%_row|%_rows|%_rshift|%_scrolllock" "|%_second|%_selected|%_shell|%_shift|%_shralias" "|%_startpath|%_startpid|%_syserr|%_time|%_transient" "|%_unicode|%_windir|%_winfgwindow|%_winname|%_winsysdir" "|%_winticks|%_wintitle|%_winuser|%_winver|%_xpixels" "|%_year|%_ypixels|%cdpath|%cmdline|%colordir|%comspec" "|%date|%errorlevel|%filecompletion|%historyexclusion" "|%path|%pathext|%prompt|%recycleexclude|%temp|%time" "|%titleprompt|%tmp|%treeexclude" "|", b)) return 5; if (btm_major_version >= 7 && strstr( "|%!|%_afswcell|%_drives|%_dst|%_exit|%_idleticks|%_iftp|%_iftps" "|%_openafs|%_osbuild|%_stderr|%_stdin|%_stdout|%_stzn|%_stzo" "|%_tzn|%_tzo|%debugvariableexclude|%variableexclude" "|", b)) return 5; char* bat_param_re = "%%[0-9]+|%%%[[0-9]+]|%%[0-9]*%$|%%%[[0-9]*%$]|%%#|%%%[#]"; // 012345678901234567890123456789^123456789012^ // 1 2 3 4 bat_param_re[30] = bat_param_re[43] = btm_bat_param_char; // check for a batch file parameter // if (matches_at(from, 1, bat_param_re)) { point = matchend; return 6; } // Check for just a percent sign // if (!buf[3]) return 9; return 10; // unknown variable } if (strstr( "|.and.|.or.|.xor.|activate|alias|assoc|attrib|batcomp" "|bdebugger|beep|break|call|cancel|case|cd|cdd|chdir|cls|color" "|copy|date|ddeexec|default|defined|del|delay|describe|detach" "|dir|direxist|dirhistory|dirs|do|drawbox|drawhline|drawvline" "|echo|echoerr|echos|echoserr|else|elseiff|enddo|endiff" "|endlocal|endswitch|endtext|eq|eqc|eql|equ|erase|errorlevel" "|eset|eventlog|except|exist|exit|ffind|for|forever|free|ftype" "|function|ge|geq|global|gosub|goto|gt|gtr|head|help|history" "|if|iff|iftp|in|inkey|input|isalias|isapp|isdir|isfile" "|isfunction|isinternal|islabel|iswindow|iterate|keybd|keys" "|keystack|le|leave|leq|list|loadbtm|log|lss|md|memory|mkdir" "|mklnk|move|msgbox|ne|neq|not|on|option|path|pause|pdir" "|playavi|playsound|popd|print|prompt|pushd|querybox|quit|rd" "|reboot|recycle|ren|rename|return|rmdir|screen|scrput|select" "|sendmail|set|setdos|setlocal|shift|shortcut|shralias|smpp" "|snpp|start|switch|tail|taskend|tasklist|tctoolbar|tee|text" "|then|time|timer|title|touch|tree|truename|type|unalias" "|unfunction|unset|until|ver|verify|vol|vscrput|which|window|y" "|", buf)) return 1; if (btm_major_version >= 7 && strstr( "|abortretryignore|breakpoint|canceltrycontinue|continueabort" "|debugstring|ejectmedia|isplugin|jabber|plugin|postmsg" "|retrycancel|rexec|rshell|transient" "|", buf)) return 1; if (btm_chcp_is_internal && strstr("|chcp|", buf)) return 1; // Common externals // if (strstr( "|at|cacls|chcp|chkdsk|chkntfs|cmd|cmdextversion|command|comp" "|compact|convert|diskcomp|diskcopy|doskey|fc|find|findstr" "|format|graftabl|label|mode|more|recover|replace|sort|subst" "|xcopy" "|", buf)) return 7; if (!btm_chcp_is_internal && strstr("|chcp|", buf)) return 7; if (strstr( "|unknown_cmd" "|", buf)) return 8; // Does it immediately follow the word SET? // char* set_pattern = "set[ \t]+[^ ]"; char set_command = matches_at(from+1, -1, set_pattern); if (set_command) { if (strstr( "|cdpath|cmdline|colordir|comspec|date|errorlevel|filecompletion" "|historyexclusion|path|pathext|prompt|recycleexclude|temp|time" "|titleprompt|tmp|treeexclude" "|", buf)) return 5; if (btm_major_version >= 7 && strstr( "|debugvariableexclude|variableexclude" "|", buf)) return 5; } // Check for punctuation in here // if (1 == point - from && strchr("]-[<>|&/\\()+*?:.", character(from))) return 9; return 0; } color_btm_range(from, to) { int t = -1; if (from >= to) return to; save_var point, matchstart, matchend, case_fold = 1; point = to; // Color entire lines. nl_forward(); to = point; save_var narrow_end = size() - to; set_character_color(from, to, -1); point = from; while (point < to) { if (!re_search(1, "(%%@)?[-a-z0-9_.$]+|%%[-a-z0-9_.$]+(%%)?|%%%%" "|%%%[[^]%]+%]|%%[+=?#!]|%%_%?|[]-[<>|&/\\()+*?:`%]")) { t = size(); break; } t = matchstart; // colon at beginning of line. // if (character(t) == ':' && matches_at(t+1, -1, "^[ \t]*:")) { // :: is a comment if (character(t+1) == ':') { nl_forward(); set_character_color(t, point, color_class btm_comment); } // a label else if (matches_at(t+1, 1, "[ \t]*[a-z0-9_.&]+")) { point = matchend; set_character_color(t, point, color_class btm_label); } else { point = t + 1; set_character_color(t, point, color_class btm_punctuation); } continue; } else if (character(point - 1) == '`') { set_character_color(t, point, color_class btm_backquote); continue; } switch (is_btm_keyword(t)) { case 1: set_character_color(t, point, color_class btm_keyword); break; case 2: nl_forward(); set_character_color(t, point, color_class btm_comment); break; case 3: set_character_color(t, point, color_class btm_function); break; case 4: set_character_color(t, point, color_class btm_userfunction); break; case 5: set_character_color(t, point, color_class btm_variable); break; case 6: set_character_color(t, point, color_class btm_bat_param); break; case 7: set_character_color(t, point, color_class btm_external); break; case 8: set_character_color(t, point, color_class btm_alias); break; case 9: set_character_color(t, point, color_class btm_punctuation); break; case 10: set_character_color(t, point, color_class btm_uservariable); break; default: set_character_color(t, point, -1); break; } } if (to < t) set_character_color(to, t, -1); return point; } // Indent the following line more? // // True for lines following one of: // // iff | else | elseiff | do | switch | case | default // // // iff exist regina.dll then // echo Already have a regina.dll here. // elseiff exist "%[JP_EXE_DIR]\regina.dll" then // echo Linking "%[JP_EXE_DIR]\regina.dll" to regina.dll. // mklnk "%[JP_EXE_DIR]\regina.dll" regina.dll // else // echo Cannot link to regina.dll. // endiff // // // do loop_control // commands // iterate // commands // leave // commands // enddo // // // switch %key // case A // echo It's an A // case B .or. C // echo It's either B or C // default // echo It's none of A, B, or C // endswitch // // int btm_indent_more() { if (parse_string(1, "[*@ \t]*(" "iff|else|elseiff|do|switch|case|default" ")")) { return 1; } return 0; } // Indent *this* line less? // // True for lines starting with one of: // // else | elseiff | endiff | enddo | case | default | endswitch // int btm_indent_less() { return parse_string(1, "[*@ \t]*(" "else|elseiff|endiff|enddo|case|default|endswitch" ")") != 0 ? 1 : 0; } // Is this a continuation line? // btm_is_continuation() { save_var point; to_begin_line(); // Substitute in the btm_escape_char value for the caret // 012345v char* continuation_pattern = "(%%=|%^)\n"; continuation_pattern[6] = btm_escape_char; return parse_string(RE_REVERSE, continuation_pattern); } // If this is a continuation line, move back to the actual start. // btm_before_continuation() { while (btm_is_continuation()) nl_reverse(); to_begin_line(); } btm_step() { return (btm_indent > 0) ? btm_indent : tab_size; } // Indent line at orig based on this one. // btm_indent_continuation(orig) { int ind; btm_before_continuation(); ind = get_indentation(point); point = orig; indent_to_column(ind + 2 * btm_step()); } do_btm_indent() on btm_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; } btm_indenter(); } btm_indenter() { int orig = point, ind; // Put labels and 'rem' comments at column 0. // ('::' comments indent normally) // char* cmnt_or_lbl = "^[ \t]*(:[^:]|[*@]*rem)"; if (matches_at(give_begin_line(), 1, cmnt_or_lbl) || point <= narrow_start) { indent_to_column(0); return; } if (btm_is_continuation()) { btm_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, cmnt_or_lbl)); // that's not a comment or label btm_before_continuation(); ind = get_indentation(point); ind += btm_step() * btm_indent_more(); point = orig; if (btm_indent_less()) ind -= btm_step(); to_indentation(); /* go to current line's indent */ indent_to_column(ind); } // recompute this line's indentation without moving point // fix_btm_indentation() { if (in_shell_buffer) return; save_spot point; to_indentation(); btm_indenter(); } // move back to nearest tab stop // unindent if in the initial whitespace of the line // command btm_unindent() on btm_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--; } command btm_mode() { mode_default_settings(); major_mode = _btm_mode_name; mode_keys = btm_tab; /* Use these keys. */ strcpy(comment_start, "rem[ \t]*"); strcpy(comment_pattern, "rem.*$"); strcpy(comment_begin, "rem "); strcpy(comment_end, ""); comment_column = 0; recolor_range = color_btm_range; // set up coloring rules recolor_from_here = recolor_by_lines; idle_coloring_size = 10000; if (want_code_coloring) // maybe turn on coloring when_setting_want_code_coloring(); buffer_maybe_break_line = generic_maybe_break_line; fill_mode = misc_language_fill_mode; // indent_with_tabs = btm_indent_with_tabs; indenter = btm_indenter; auto_indent = 1; try_calling("btm-mode-hook"); make_mode(); } when_loading() { btm_tab[ALT('q')] = (short) fill_comment; } suffix_btm() { btm_mode(); } // Comment out the following if you don't want to use this mode on .bat // and .cmd files. // suffix_bat() { btm_mode(); } suffix_cmd() { btm_mode(); }