/* ** BRIEF -- Basic Reconfigurable Interactive Editing Facility ** ** Written by Dave Nanian and Michael Strickman. */ /* ** indent.cb: ** ** This macro performs automatic indenting for a number of languages. ** Other languages can be supported through the standard BPACKAGES ** interface. */ #define MIN_ABBREV 1 // Increase this to specify longer // default minimum abbreviations. #define REG_MARK 1 // Regular marks. #define COL_MARK 2 // Column marks. #define LINE_MARK 3 // Line marks. #define NI_MARK 4 // Non-inclusive marks. #define C_SKIP_PAT "{{/\\*{{[~*/]{/+[~*/]}@}|\\*}@\\*/}|{//*}|{<{[A-Za-z0-9_]+:}|{[ \xc\t]@#*}}|[ \xc\t\n]}@" /* ** ABBREV_LIST_2 contains the list of abbreviations that don't require ** cursor positioning. ABBREV_LIST_1 requires the cursor to back up a ** character (usually placing it inside a parenthesis). */ #define ABBREV_LIST_1 "\xffreturn \xffbreak;\xffdo\xffdefault:\xffelse \xffcase :\xffcontinue;\xff" #define ABBREV_LIST_2 "\xffei\xffelse if ()\xffif ()\xffwhile ()\xfffor ()\xffswitch ()\xff" void back_tab (void); void open_line (void); string .c_template_first (~int, ~int, ~int, ~int); string .c_smart_first (~int, ~int, ~int); string .cb_template_first (~int, ~int, ~int, ~int); string .cb_smart_first (~int, ~int, ~int); string _regular_first (void); int .c_indent (~int indent_query); void r_indent (void); void slide_in (void); void slide_out (void); void .c_abbrev (void); void brace_expand (void); void .c_open_brace (void); void .c_close_brace (void); void just_brace (void); void just_cbrace (void); void just_space (void); void comment_block (void); void uncomment_block (void); int .c_min_abbrev, // Minimum template abbreviation. .c_indent_open, // Location of the open brace. .c_indent_close, // Location of the close brace. .c_indent_first, // Location of the first braces. .c_smart, // C smart indenting keymap. .c_template, // C template editing keymap, expands on . .c_alt_template, // C template editing keymap, expands on . _r_keymap; // Regular indenting keymap. /* ** .c_template_first, .c_smart_first, ** .cb_template_first, .cb_smart_first, ** _regular_first: ** ** These macros are called by the BPACKAGES parser in language.m. ** They initialize the local keymaps for the various indenting functions, ** set the abbreviation length and adjust the indenting style. */ string .c_template_first (~int, ~int, ~int, ~int) { .c_min_abbrev = MIN_ABBREV; get_parm (3, .c_min_abbrev); if (first_time()) { keyboard_push (); assign_to_key ("", ".c_indent"); assign_to_key ("", ".c_abbrev"); assign_to_key ("", "slide_out"); assign_to_key ("<{>", "brace_expand"); assign_to_key ("<}>", ".c_close_brace"); assign_to_key ("", "just_brace"); assign_to_key ("", "just_cbrace"); assign_to_key ("", "just_space"); .c_alt_template = inq_keyboard (); keyboard_pop (1); keyboard_push (); assign_to_key ("", ".c_indent"); assign_to_key ("", "slide_in"); assign_to_key ("", "slide_out"); assign_to_key ("", ".c_abbrev"); assign_to_key ("<{>", "brace_expand"); assign_to_key ("<}>", ".c_close_brace"); assign_to_key ("", "just_brace"); assign_to_key ("", "just_cbrace"); assign_to_key ("", "just_space"); .c_template = inq_keyboard (); keyboard_pop (1); } if (.c_min_abbrev == 0) { use_local_keyboard (.c_alt_template); .c_min_abbrev = 1; } else use_local_keyboard (.c_template); .c_indent_open = 1; .c_indent_close = 1; .c_indent_first = 0; get_parm (0, .c_indent_open); get_parm (1, .c_indent_close); get_parm (2, .c_indent_first); if (!.c_indent_open || !.c_indent_close) .c_indent_first = 0; returns (""); } string .c_smart_first (~int, ~int, ~int) { if (first_time()) { keyboard_push (); assign_to_key ("", ".c_indent"); assign_to_key ("", "slide_in"); assign_to_key ("", "slide_out"); assign_to_key ("<{>", ".c_open_brace"); assign_to_key ("<}>", ".c_close_brace"); assign_to_key ("", "just_brace"); assign_to_key ("", "just_cbrace"); .c_smart = inq_keyboard (); keyboard_pop (1); } use_local_keyboard (.c_smart); .c_indent_open = 1; .c_indent_close = 1; .c_indent_first = 0; get_parm (0, .c_indent_open); get_parm (1, .c_indent_close); get_parm (2, .c_indent_first); if (!.c_indent_open || !.c_indent_close) .c_indent_first = 0; returns (""); } string .cb_template_first (~int p1, ~int p2, ~int p3, ~int p4) { returns ( .c_template_first(p1, p2, p3, p4) ); } string .cb_smart_first (~int p1, ~int p2, ~int p3) { returns ( .c_smart_first(p1, p2, p3) ); } string _regular_first (void) { if (first_time()) { keyboard_push (); assign_to_key ("", "r_indent"); assign_to_key ("", "slide_in"); assign_to_key ("", "slide_out"); _r_keymap = inq_keyboard (); keyboard_pop (1); } use_local_keyboard (_r_keymap); returns (""); } /* ** .c_indent: ** ** This macro does syntax-sensitive indenting ("smart indenting") for ** C language files. */ int .c_indent (~int indent_query) { int curr_indent_col, // Current unmodified indent col. prev_indent_col, // Previous unmodified indent col. curr_line, // Line cursor is on when called. curr_col, // Column cursor is on when called. prev_end_char, // Character at end of current line. curr_end_char, // Character at end of previous line. indent_level; // Current indenting level. string following_text, // All characters following the cursor. curr_text; // The text of the current line. /* ** Gather information on the two previous non-blank lines. */ if (!inq_mode ()) end_of_line (); else if ((following_text = ltrim (read ())) != "") { /* ** If there are non-whitespace characters following ** the cursor, we save them in following_text. These ** characters are re-inserted after the new indent column ** has been determined. */ following_text = substr (following_text, 1, strlen (following_text) - 1); delete_to_eol (); } /* ** We now search back for the previous line, skipping over ** all intervening comments, compiler directives and labels. ** The cursor is left one character after the character we're ** interested in. */ prev_end_char = curr_end_char = ';'; prev_indent_col = 1; inq_position (curr_line, curr_col); search_back (C_SKIP_PAT, -2); if (prev_char ()) { string line_end = read (); // The text after the cursor on this line. curr_end_char = atoi (substr (line_end, 1, 1), 0); /* ** We trim the end of the preceding line to ensure that any ** spaces left by template expansion of else statements (or ** accidental user typing) are removed. */ delete_to_eol (); /* ** If we're dealing with an open brace, later indenting code ** needs the entire text of the line. We retrieve that information ** here. */ if (curr_end_char == '{') { save_position (); beginning_of_line (); curr_text = trim (ltrim (read ())) + "{"; restore_position (); } insert (trim (line_end)); /* ** Now we move to the first non-blank character on the ** line, and get that line's indent column. We then search ** back for the previous line, which may or may not exist. ** If it doesn't exist, we assume it ends in a semi-colon -- ** this ensures that the indent level doesn't change. */ beginning_of_line (); next_char (strlen (read ()) - strlen (ltrim (read ()))); inq_position (NULL, curr_indent_col); if (prev_char () && search_back (C_SKIP_PAT, -2) > 1) { prev_char (); prev_end_char = atoi (read (1), 0); if (.c_indent_first && .c_indent_open) { beginning_of_line (); next_char (strlen (read ()) - strlen (ltrim (read ()))); inq_position (NULL, prev_indent_col); } } } move_abs (curr_line, curr_indent_col); /* ** We've determined the last two non-blank lines' last characters ** as well as the column position of the first non-blank character. ** Now we position the cursor on the new line's proper level. */ if (curr_indent_col == 1) { if (!.c_indent_first && curr_end_char == '{') curr_indent_col += distance_to_tab (); } else { int prev_char_known; /* ** Some indenting methods need to know if we've ** actually recognized a given character at all. Rather ** than duplicate the compound if statements required, we ** set a variable here. */ switch (prev_end_char) { case ';': case '{': case '}': case ':': ++prev_char_known; case ')': --prev_char_known; } switch (curr_end_char) { /* ** If the current line ends with a semicolon, we make ** an indenting decision based on the previous line. If ** it ends with a "known" character, we leave the indenting ** level as is. Otherwise, we assume we've hit the end of ** an indented clause and outdent. */ case ';': { if (prev_char_known <= 0) --curr_indent_col; } /* ** If close braces are supposed to be indented, we ** outdent after encountering one. Otherwise, we stay ** at the same level. */ case '}': { if (.c_indent_close) --curr_indent_col; } /* ** The rule for open braces is the opposite of that for ** close braces. If they're supposed to be indented, we ** leave the indenting level the same (since we've already ** indented. ** ** There is a special case, however. If the open brace ** is placed on the same line as the statement, we want to ** indent the block. So, we assume that if the open brace ** isn't the only text on the line, we're dealing with this ** case, and should indent. */ case '{': { if (!.c_indent_open || (curr_text != "{" && !(.c_indent_first && prev_indent_col == 1))) curr_indent_col += distance_to_tab (); } /* ** Finally, if we didn't recognize the end of the ** previous line, we assume we're supposed to indent. This ** ends up indenting for statement continuations, case ** statements, if statements, etc. */ default: { if (prev_char_known) curr_indent_col += distance_to_tab (); } } } /* ** Here we move the cursor back to the original ** column we started at (where the newline should go). ** We then execute the correct command for the pressed ** key. If we were assigned to "", we insert the ** Enter keystroke, thereby ensuring that overstrike and ** insert modes are handled properly. If not, we insert ** the keystroke for Ctrl-m, which always inserts a newline. */ move_abs (0, curr_col); /* ** Since actually differently in insert and overstrike modes, ** we try to ensure that we do the right thing based on the command that's ** running. ** ** We assume that a direct assignment to .c_indent means we should ** use "normal" processing: otherwise, we assume we're assigned ** to something like open_line, and use . */ if (!indent_query) { if (inq_command () == ".c_indent") self_insert (key_to_int ("")); else self_insert (key_to_int ("")); } /* ** We now move the cursor to the new indenting level on ** the next line, and we retain the indenting "level" so that ** we can return it to the calling function. */ beginning_of_line (); curr_col = distance_to_tab () + 1; while (curr_col <= curr_indent_col) { move_abs (0, curr_col); ++indent_level; curr_col += distance_to_tab (); } /* ** Finally, if we split the original line, we insert the ** characters that were following the cursor here. They're ** inserted, of course, at the new, indented column. */ save_position (); insert (following_text); restore_position (); returns (indent_level); } /* ** r_indent: ** ** This macro does "standard" style indenting, indenting new lines ** to the same column as the previous non-blank line. */ void r_indent (void) { int curr_indent_col, curr_line, curr_col; string following_text; /* ** First, check to see if there are any following characters ** on the line. If so, read them into a temporary variable and ** delete from the first line. Then, get the column of the first ** non-blank line. */ if (!inq_mode ()) end_of_line (); else if ((following_text = ltrim (read ())) != "") { /* ** If there are non-whitespace characters following ** the cursor, we save them in following_text. These ** characters are re-inserted after the new indent column ** has been determined. */ following_text = substr (following_text, 1, strlen (following_text) - 1); delete_to_eol (); } inq_position (curr_line, curr_col); if (search_back ("<*\\c[~ \\t\\n]")) inq_position (NULL, curr_indent_col); else curr_indent_col = 1; move_abs (curr_line, curr_col); /* ** We've determined the last non-blank lines' indent level -- ** do mode-sensitive return, indent, line split, and clean up. */ if (inq_command () == "r_indent") self_insert (key_to_int ("")); else self_insert (key_to_int ("")); move_abs (0, curr_indent_col); insert (following_text); move_abs (0, curr_indent_col); } /* ** slide_in: ** ** This macros slides a marked block of text in one tab stop per press ** of the tab key (-->|). */ void slide_in (void) { int start_line, start_col, end_line, mark_type, tab_key = key_to_int (""); if (mark_type = inq_marked (start_line, start_col, end_line)) { int cursor_line, cursor_col, mark_line, mark_col, num_lines, old_mode; inq_position (cursor_line, cursor_col); swap_anchor (); inq_position (mark_line, mark_col); if (mark_type != COL_MARK) start_col = 1; move_abs (start_line, start_col); raise_anchor (); old_mode = inq_mode (); insert_mode (1); while (start_line <= end_line) { search_fwd ("[~ \t]"); /* ** Note that self_inserting the complete ** key definition ensures the Tab is converted ** to spaces if -t is used. */ if (read (1) != "\n") self_insert (tab_key); move_abs (++start_line, start_col); } move_abs (mark_line, mark_col); drop_anchor (mark_type); move_abs (cursor_line, cursor_col); insert_mode (old_mode); } else self_insert (tab_key); } /* ** slide_out: ** ** This macros slides a marked block of text out one tab stop per press ** of the back-tab key (|<--). */ void slide_out (void) { int start_line, start_col, end_line, mark_type; if (mark_type = inq_marked (start_line, start_col, end_line)) { int num_lines; save_position (); if (mark_type != COL_MARK) start_col = 1; move_abs (start_line, start_col); while (start_line <= end_line) { int curr_col; search_fwd ("[~ \t]"); inq_position (NULL, curr_col); if (curr_col > start_col) { drop_anchor (4); back_tab (); inq_position (NULL, curr_col); if (curr_col < start_col) move_abs (0, start_col); delete_block (); } move_abs (++start_line, start_col); } restore_position (); } else back_tab (); } /* ** Template editing macros: ** ** These macro performs simple template editing for C programs. Each ** time the space bar is pressed, the characters before the cursor are ** checked to see if they are "if", "else if", "while", "for", "do", ** "switch" or "case" (or abbreviations for them). These keywords must ** only be preceded with spaces or tabs, and be typed at the end of a ** line. If a match is found, the remainder of the statement is filled ** in automatically. ** ** In addition, a brace pairer is included -- this automatically ** inserts a matching brace at the proper indentation level when an ** opening brace is typed in. To insert a brace without a matching ** brace (it attempts to be smart about matching braces, but you never ** can make this type of thing quite smart enough), type either Ctrl-{ ** or quote the brace with Alt-q. */ /* ** .c_abbrev: ** ** This function checks to see if the characters before the space just ** typed (and actually inserted by this function) are destined to be ** followed by the remainder of a C construct. */ void .c_abbrev (void) { int done; if (read (1) == "\n") { int loc; string line; save_position (); beginning_of_line (); line = trim (ltrim (read ())); restore_position (); if (!index (" \t\n", substr (line, 1, 1)) && (strlen (line) && strlen (line) < 8)) { int length; string abbrev_list = ABBREV_LIST_1; line = "\xff" + line; if (.c_min_abbrev < strlen (line) && ((loc = search_string (line, abbrev_list, length, 0)) || (loc = search_string (line, abbrev_list = ABBREV_LIST_2, length, 0)))) { string completion; completion = substr (abbrev_list, loc + length); completion = substr (completion, 1, index (completion, "\xff") - 1); if (line + completion == "\xffei") { left (); delete_char (); completion = "lse if ()"; } done = strlen (completion); insert (completion); if (line + completion == "\xffdo") { open_line (); brace_expand (); save_position (); move_rel (1, 0); open_line (); insert ("while ();"); restore_position (); done++; } /* ** Only back up if the last character of the completion is ** ) or :. */ else if (strlen (completion) && index (":)", substr (completion, strlen (completion)))) prev_char (); } } } if (!done) if (inq_local_keyboard () == .c_alt_template) slide_in (); else self_insert (); } /* ** brace_expand: ** ** This function checks to see if the typed brace should be indented ** and matched to the current indenting level. */ void brace_expand (void) { string line; save_position (); beginning_of_line (); line = read (); restore_position (); .c_open_brace (); if (search_string ("<|{{else}|{do}}|{{typedef}|{struct}[ \t]@[~ \t\n]@}|[):][ \t]@>", line) && "" == trim (ltrim (read ()))) { int col; inq_position (NULL, col); .c_indent (); save_position (); if (col == 2) insert ("\n}"); else { inq_position (NULL, col); insert ("\n"); if (.c_indent_close == 1) { move_abs (0, col); insert ("}"); } else .c_close_brace (); } restore_position (); } } /* ** .c_open_brace: ** ** This function inserts an open brace at the correct column, given the ** current indenting conventions. */ void .c_open_brace (void) { save_position (); beginning_of_line (); if (trim (ltrim (read ())) == "") { restore_position (); .c_indent (1); if (!.c_indent_open) back_tab (); else if (.c_indent_first) { int curr_col; inq_position (NULL, curr_col); if (curr_col == 1) move_rel (0, distance_to_tab ()); } } else restore_position (); self_insert ('{'); } /* ** .c_close_brace: ** ** This function inserts a close brace at the correct column, given the ** current indenting conventions. */ void .c_close_brace (void) { save_position (); beginning_of_line (); if ("" == trim (ltrim (read ()))) { restore_position (); if (!.c_indent_first && .c_indent (1) == 1 || !.c_indent_close) back_tab (); } else restore_position (); self_insert ('}'); } /* ** just_brace, just_cbrace, just_space: ** ** These functions insert unexpanded and expanded braces and spaces. */ void just_brace (void) { insert ("{"); } void just_cbrace (void) { insert ("}"); } void just_space (void) { insert (" "); } /* ** comment_block: ** ** This macro comments out a block of code. It tries to be intelligent ** about things like comments inside the block -- if it finds one, it ** escapes it and moves on. ** ** Two different types of blocks are created, depending on the cursor ** start and end positions. If both are in column one, comment_block ** assumes a "block" type comment is desired. Block comments have "**" ** inserted in rows other than the first and last. ** ** If the start and end columns are not both in column one, the area is ** simply bracketed with "/*" and "* /". */ void comment_block (void) { int start_line, start_col, end_line, end_col, curr_col; if (inq_marked (start_line, start_col, end_line, end_col)) { string pattern; raise_anchor (); save_position (); move_abs (end_line, end_col); insert ("\xff"); move_abs (start_line, start_col); if (start_col == 1) insert ("/* "); else insert (" /* "); if (start_col == 1 && end_col == 1) pattern = "{/\\*}|{\\*/}|{\\*\\*}|<|\xff"; else pattern = "{/\\*}|{\\*/}|{\\*\\*}|\xff"; while (search_fwd (pattern) && "\xff" != read (1)) { inq_position (NULL, curr_col); if (curr_col == 1 && (start_col == 1 && end_col == 1)) insert ("**\t"); else { insert ("\\"); right (); } } delete_char (); if (end_col == 1) insert ("*/ "); else insert (" */ "); restore_position (); } else error ("No marked block."); } /* ** uncomment_block: ** ** This routine uncomments a block of code that was commented out by ** comment_block. It removed the comment and leading "**" characters (if ** appropriate), and restores the internal comments to their original, ** un-escaped state. */ void uncomment_block (void) { int start_line, start_col, end_line, end_col; if (inq_marked (start_line, start_col, end_line, end_col)) { raise_anchor (); save_position (); move_abs (end_line, end_col); insert ("\xff"); move_abs (start_line, start_col); while (read (1) != "\xff") { search_fwd ("{