/* ** BRIEF -- Basic Reconfigurable Interactive Editing Facility ** ** Written by Dave Nanian and Michael Strickman. ** ** History: ** 12/10/90 Jim Rodriquez - move assign_to_keys from _init macro ** to smart_first macro to conserve ** memory. */ /* ** ** ada.cb: ** ** Smart indenting and template editing for ADA. This package ** works for files with the extensions .ada. Other languages ** extensions can be supported by SETUP or manual extension ** equivalence. */ #define FALSE 0 #define TRUE 1 #define MIN_ABBREV 1 #define ADA_SKIP_PAT "{{--*\n}|{\\<\\<[~<>\n]@\\>\\>}|[ \xc\t\n]}@" /* ** Note: the "!" found after the two letter abbreviations (i.e. AG, DR, ** DG, etc) is an internal convention in order to ensure that the ** abbreviation's length is less than the completion length. Thus, ** the user does NOT have to type in the "!" to ensure the two letter ** abbreviations' expansions. */ #define ABBR_LIST_1 "~ACCEPT~ABORT~BEGIN~CASE~ELSE~ELSIF~EI!~ENTRY~EXCEPTION~EXIT~EXIT WHEN~EW!~FUNCTION~FOR~IF~LOOP~OR~PROCEDURE~" #define ABBR_LIST_2 "~PACKAGE~PACKAGE BODY~PB!~PACKAGE IS NEW~PN!~RETURN~RECORD~SUBTYPE~SELECT~TYPE~TASK~TASK BODY~TB!~TERMINATE~" #define ABBR_LIST_3 "~USE~WITH~WHEN~WHEN OTHERS~WO!~WHILE~" #define KW_LIST "~ACCEPT~BEGIN~CASE~ELSE~ELSIF~FOR~FUNCTION~IF~LOOP~OR~PACKAGE~PROCEDURE~RECORD~SELECT~TASK~WHEN~WHILE~" #define KW_LOWER 0 #define KW_UPPER 1 #define KW_MIXED 2 #define MAX_COL 20736 #define NEG_MAX_COL -20736 /* ** Function Prototypes */ extern void slide_in (); extern void open_line (); extern void back_tab (); extern int .c_previous_word (); extern int .c_next_word (); string .ada_smart_first (); string .ada_template_first (~int, ~int); int .ada_indent_level (~int); void .ada_indent (int); void .ada_abbrev (); void .ada_first_nonwhite (); string .ada_keyword_cvt (string); void .ada_reindent (string, int); void .ada_expand_block (string); void .ada_expand_pair (string); void .ada_expand_full (string, string); int .ada_next_word (); int .ada_previous_word (); /* ** Allocate the global variables and set up the keymaps. */ int _ada_smart, _ada_template, _ada_alt_template, _ada_keyword_case, _ada_min_abbrev; /* ** Turn on smart indenting for ADA. This macro is designed to ** be run the first time a file is edited, but may also be run from ** the command line. */ string .ada_smart_first () { if (first_time()) { keyboard_push (); assign_to_key ("", ".ada_indent"); assign_to_key ("", "slide_in"); assign_to_key ("", "slide_out"); _ada_smart = inq_keyboard (); keyboard_pop (1); } use_local_keyboard (_ada_smart); returns (""); } /* ** Turn on template editing for ADA. This macro is designed to ** be run the first time a file is edited, but may also be run from ** the command line. ** ** Parameters: ** 0 -- the minimum length of abbreviations that should be expanded. ** If this is 0, abbreviations are only expanded when ** is pressed. If it is 1, b will expand to a BEGIN/END ** block; if it is 2, you would need to type be in order ** to expand the template. ** 1 -- controls case of expanded keywords. If this is 0, r ** expands to "return"; if 1, it expands to "RETURN"; and if 2, ** it expands to "Return". ** ** Defaults: 1 0 */ string .ada_template_first (~int, ~int) { _ada_min_abbrev = MIN_ABBREV; _ada_keyword_case = KW_LOWER; get_parm (0, _ada_min_abbrev); get_parm (1, _ada_keyword_case); if (first_time()) { keyboard_push (); assign_to_key ("", ".ada_indent"); assign_to_key ("", ".ada_abbrev"); assign_to_key ("", "slide_out"); assign_to_key ("", "just_space"); _ada_alt_template = inq_keyboard (); keyboard_pop (1); keyboard_push (); assign_to_key ("", ".ada_indent"); assign_to_key ("", "slide_in"); assign_to_key ("", "slide_out"); assign_to_key ("", ".ada_abbrev"); assign_to_key ("", "just_space"); _ada_template = inq_keyboard (); keyboard_pop (1); } if (_ada_min_abbrev == 0) { use_local_keyboard (_ada_alt_template); _ada_min_abbrev = MIN_ABBREV; } else use_local_keyboard (_ada_template); returns (""); } /* ** These definitions are used as Ada "language sensitive" word patterns */ int .ada_next_word () { returns (.c_next_word ()); } int .ada_previous_word () { returns (.c_previous_word ()); } /* ** Maps between indent levels (in tab stops) and column positions in a ** file. Column 1 to just before the first tab stop is level 0; from ** the first to just before the second tab stop is level 1; etc. ** ** If a parameter is passed, we treat it as an indent level, and we ** calculate the column corresponding to it. Otherwise, we calculate ** the indent level corresponding to the current column. */ int .ada_indent_level (~int) { int curr_col, lev_to_col, level; save_position (); curr_col = 1; if (get_parm (0, lev_to_col)) { beginning_of_line (); while (level < lev_to_col) { move_abs (0, curr_col += distance_to_tab ()); ++level; } level = curr_col; } else { inq_position (NULL, lev_to_col); beginning_of_line (); while ((curr_col += distance_to_tab ()) <= lev_to_col) { move_abs (0, curr_col); ++level; } } restore_position (); returns (level); } /* ** .ada_indent: ** ** This macro does syntax-sensitive indenting ("smart indenting") for ** ADA language files. ** ** Parameters: ** 0 -- if TRUE, the line is reindented if necessary, but the line ** is never split, no newline is inserted, and the cursor is left ** at the end of the line. */ void .ada_indent (int scratch) { int curr_line, // Line cursor is on when called. code_line, // Line where last code char is found. code_indent_level, // Current indent level, in tab stops. prev_indent_level, // Indent level of previous line. code_trail, // The last code character. prev_trail; // Last code char before code_line. string following_string, // Remainder of line being split. code_text, // Trimmed text of code line. token, // Tokenized version of line. prev_text, // Trimmed text of previous line. prev_token; // First token on previous line. /* ** Initialize. */ inq_position (curr_line, NULL); code_trail = prev_trail = '\;'; /* ** If we're in overstrike mode, we act as if we were at the ** end of the line when the command was invoked. ** ** If we're in insert mode, we may have to split the line. */ if (!inq_mode ()) end_of_line (); /* ** If line is being split, cut text to following_string. ** Leave all trailing whitespace except the newline. */ else if (read (1) != "\n") { following_string = ltrim (read ()); following_string = substr (following_string, 1, strlen (following_string) - 1); delete_to_eol (); } /* ** Find the last "code" character; skip back over whitespace and ** comments until we get something different. The cursor will be ** left on the next character from the "different" one; we do a ** (prev_char) to get to the code character. (If it fails, we're ** at the top of the buffer.) */ search_back (ADA_SKIP_PAT, -2); /* ** Remember which line we found the code character on. Classify ** the code character by looking it up in a string, and save ** a "tokenized" version of the whole line. */ if (prev_char ()) { /* ** We save the identity of the last code character on ** the "code line". */ inq_position (code_line); code_trail = atoi (read (1), 0); /* ** Find the first non-white character on the line, so ** we may determine its indenting level. Save the ** text of the line for later parsing. */ .ada_first_nonwhite (); code_indent_level = .ada_indent_level (); code_text = trim (read ()); /* ** Find the last code line before this, as well as its ** last code character and whether or not it contains ** ADA keywords that affect indenting. */ if (up ()) { end_of_line (); if (search_back (ADA_SKIP_PAT, -2) && prev_char ()) { prev_trail = atoi (read (1), 0); .ada_first_nonwhite (); prev_indent_level = .ada_indent_level (); prev_text = trim (read ()); /* ** Get the first token on the previous line. */ prev_token = upper (substr (prev_text, 1, index (prev_text + " ", " ") - 1)); } } /* ** Get the first token on the code line. This assumes ** that tokens are separated by spaces. */ token = upper (substr (code_text, 1, index (code_text + " ", " ") - 1)); } /* ** Move to the beginning of the line the cursor was originally on. ** Occasionally, we have to adjust its indent level. However, ** if the line contains no code, we don't need to worry about it. */ move_abs (curr_line, 1); if (code_line == curr_line) { code_line = prev_indent_level; switch (token) { /* ** If the last token on the previous line was IS, we align ** a BEGIN with that line. If not, we outdent the BEGIN ** one level. */ case "BEGIN": if ("IS" != upper (substr (prev_text, rindex (" " + prev_text, " ")))) --code_line; /* ** ELSE should be outdented one level from the ** previous line, unless the last line was a clause ** like "IF x THEN y;". */ case "ELSE": case "ELSIF": if (!((prev_token == "IF" || prev_token == "ELSIF") && prev_trail == ';')) --code_line; /* ** END should usually be outdented one level from the ** previous line. Exceptions are at the end of a CASE ** statement, when we have to outdent two levels, ** and at the end of any block with no intervening ** indented text, when we want to stay put. (We guess ** that a line beginning with any of certain keywords ** and ending with a semicolon is a block of the form ** "IF x THEN y;"). */ case "END;": case "END": { if (!(index (KW_LIST, "~" + (prev_token + "~")) && prev_trail == ';')) if (index (upper (substr (code_text, 5)), "CASE") && prev_token != "WHEN") code_line -= 2; else --code_line; code_trail = '\;'; } /* ** If the previous line began with CASE or EXCEPTION, ** we indent the code line one level from it. If the ** previous line began with WHEN, we align the lines. ** Otherwise, we outdent the code line one level. */ case "WHEN": switch (prev_token) { case "CASE": case "EXCEPTION": ++code_line; case "WHEN": nothing (); default: --code_line; } /* ** OR should always be outdented. Maybe EXCEPTION should as well. */ case "OR": case "EXCEPTION": --code_line; default: code_line = code_indent_level; } /* ** Reindent only when necessary. */ if (code_indent_level != code_line) .ada_reindent (code_text, code_indent_level = code_line); } /* ** Check the parameter. If it's TRUE, we put the line back ** together and return. */ if (scratch) { end_of_line (); if (following_string != "") insert (following_string); return; } /* ** Move to the next line, inserting a new line below the current ** one if in insert mode. */ if (inq_mode ()) { end_of_line (); insert ("\n"); } else down (); if (code_indent_level < 0) code_indent_level = 0; /* ** We indent the cursor unless the code line ended in a semicolon. */ if (code_trail != '\;') ++code_indent_level; /* ** Move to the new position. ** ** If we cut characters from the previous line, reinsert them. */ move_abs (0, .ada_indent_level (code_indent_level)); if (following_string != "") { save_position (); insert (following_string); restore_position (); } } /* ** .ada_abbrev: ** ** This macro performs template expansion for ADA. When it is ** invoked, the characters before the cursor are checked to see if ** they are the start of a ADA keyword, preceded by a space or a ** tab, and followed only by whitespace. If a match is found, the ** remainder of the statement is filled in automatically. ** ** Expansion is only done when we're at or past the end of the ** line, and when the line is less than 10 characters long. This ** makes expansion faster and avoids unwanted expansion. */ void .ada_abbrev () { int done; // index into keywords lists /* ** Expand only when we're at the end of the line. */ if (read (1) == "\n") { int loc; string line, which_abbrev; /* ** Get a trimmed representation of the line into a string. */ save_position (); beginning_of_line (); loc = strlen (line = (upper (trim (ltrim (read ()))))); /* ** Only do template expansion if the trimmed version ** of the line is at least _ada_min_abbrev characters long, ** at most 10 characters long, matches an expansion in ** one of the ABBR_LISTs, and is shorter than that token. ** We do all comparisons in upper case. ** */ if (done = index (ABBR_LIST_1, "~" + line)) which_abbrev = ABBR_LIST_1; else if (done = index (ABBR_LIST_2, "~" + line)) which_abbrev = ABBR_LIST_2; else if (done = index (ABBR_LIST_3, "~" + line)) which_abbrev = ABBR_LIST_3; if ((loc <= 10) && (loc >= _ada_min_abbrev) && loc && done) { string completion; /* ** Extract the full, expanded keyword from the ** abbreviation list, and make sure it's longer ** than the abbreviation. */ completion = substr (which_abbrev, ++done); completion = substr (completion, 1, index (completion, "~") - 1); if (loc < strlen (completion)) { /* ** Delete the abbreviation from the buffer, and ** replace it with the expanded version. */ .ada_first_nonwhite (); delete_to_eol (); /* ** Insert necessary context for each keyword. */ switch (completion) { case "ABORT": case "EXIT": case "TERMINATE": { insert (.ada_keyword_cvt (completion)); insert (";"); } case "RETURN": case "USE": case "WITH": { insert (.ada_keyword_cvt (completion)); insert (" ;"); } case "ACCEPT": { insert (.ada_keyword_cvt (completion)); .ada_expand_full ("END ;", "DO"); } case "BEGIN": case "EXCEPTION": { insert (.ada_keyword_cvt (completion)); open_line (); } case "CASE": { insert (.ada_keyword_cvt (completion)); .ada_expand_full ("END CASE;", "IS"); } case "ELSE": case "OR": { insert (.ada_keyword_cvt (completion)); .ada_indent (1); open_line (); } /* ** Note: the "!" is an internal convention to ensure ** that the abbreviation length is less than the ** completion length. */ case "WHEN OTHERS": case "WO!": { insert (.ada_keyword_cvt ("WHEN OTHERS =>")); .ada_indent (1); open_line (); } /* ** Note: the "!" is an internal convention to ensure ** that the abbreviation length is less than the ** completion length. */ case "EI!": case "ELSIF": { insert (.ada_keyword_cvt ("ELSIF")); .ada_indent (1); .ada_expand_pair ("THEN"); } case "FOR": case "WHILE": { insert (.ada_keyword_cvt (completion)); .ada_expand_full ("END LOOP;", "LOOP"); } case "FUNCTION": case "PROCEDURE": { insert (.ada_keyword_cvt (completion)); save_position (); .ada_expand_block ("END ;"); back_tab (); insert (.ada_keyword_cvt ("BEGIN")); restore_position (); .ada_expand_pair ("IS"); } /* ** Note: the "!" is an internal convention to ensure ** that the abbreviation length is less than the ** completion length. */ case "TASK BODY": case "TB!": { insert (.ada_keyword_cvt ("TASK BODY")); save_position (); .ada_expand_block ("END ;"); back_tab (); insert (.ada_keyword_cvt ("BEGIN")); restore_position (); .ada_expand_pair ("IS"); } case "IF": { insert (.ada_keyword_cvt (completion)); .ada_expand_full ("END IF;", "THEN"); } case "LOOP": { insert (.ada_keyword_cvt (completion)); .ada_expand_block ("END LOOP;"); } case "PACKAGE": case "TASK": { insert (.ada_keyword_cvt (completion)); .ada_expand_full ("END ;", "IS"); } /* ** Note: the "!" is an internal convention to ensure ** that the abbreviation length is less than the ** completion length. */ case "PACKAGE BODY": case "PB!": { insert (.ada_keyword_cvt ("PACKAGE BODY")); .ada_expand_full ("END ;", "IS"); } /* ** Note: the "!" is an internal convention to ensure ** that the abbreviation length is less than the ** completion length. */ case "PACKAGE IS NEW": case "PN!": { insert (.ada_keyword_cvt ("PACKAGE IS NEW ;")); prev_char (9); } case "RECORD": { insert (.ada_keyword_cvt (completion)); .ada_expand_block ("END RECORD;"); } case "SELECT": { insert (.ada_keyword_cvt (completion)); .ada_expand_block ("END SELECT;"); } case "SUBTYPE": case "TYPE": { insert (.ada_keyword_cvt (completion)); .ada_expand_pair ("IS"); } case "WHEN": { insert (.ada_keyword_cvt (completion)); .ada_indent (1); .ada_expand_pair ("=>"); } /* ** Note: the "!" is an internal convention to ensure ** that the abbreviation length is less than the ** completion length. */ case "EW!": { insert (.ada_keyword_cvt ("EXIT WHEN")); insert (" "); } default: { insert (.ada_keyword_cvt (completion)); insert (" "); } } done = strlen (completion); } else done = FALSE; } else done = FALSE; restore_position (!done); } /* ** If we couldn't expand an abbreviation, we perform the ** normal task associated with the key that called us: insert ** a space, or shift a marked block. */ if (!done) if (inq_local_keyboard () == _ada_alt_template) slide_in (); else self_insert (); } /* ** Moves the cursor to the first character on the current line that ** is not a space or a tab. */ void .ada_first_nonwhite () { beginning_of_line (); next_char (strlen (read ()) - strlen (ltrim (read ()))); } /* ** Converts keywords into the user-specified preferred case. If the ** case is MIXED, the first separate letter of each "word" in the ** keyword string is converted to upper case, and the others are ** converted to lower case. */ string .ada_keyword_cvt (string keyword) { switch (_ada_keyword_case) { case KW_UPPER: returns (upper (keyword)); case KW_LOWER: returns (lower (keyword)); case KW_MIXED: { int space; string retval; keyword = lower (keyword); while (space = index (keyword, " ")) { retval += upper (substr (keyword, 1, 1)); retval += substr (keyword, 2, space - 1); keyword = substr (keyword, ++space); } retval += upper (substr (keyword, 1, 1)); returns (retval += substr (keyword, 2)); } } } /* ** Repositions a line at a specified indent level. ** ** Parameters: ** 0 -- the text of the line to reindent. ** 1 -- the new indent level. */ void .ada_reindent (string text, int curr_indent_level) { beginning_of_line (); delete_to_eol (); move_abs (0, .ada_indent_level (curr_indent_level)); insert (text); } /* ** Adds a matching keyword below the expanded template, at the same ** indent level; then inserts a line between the pair and leaves the ** cursor on it. Since inserting a line may reindent the current ** line, it does this in a strange order. */ void .ada_expand_block (string keyword) { int column; open_line (); save_position (); move_rel (-1, NEG_MAX_COL); .ada_first_nonwhite (); inq_position (NULL, column); move_rel (1, NEG_MAX_COL); insert ("\n"); move_abs (0, column); insert (.ada_keyword_cvt (keyword)); restore_position (); } /* ** Expands a keyword pair, positioning the cursor between the two ** keywords. */ void .ada_expand_pair (string keyword) { save_position (); insert (" " + .ada_keyword_cvt (keyword)); restore_position (); right (); } /* ** Expands a keyword block with a paired keyword on the first line. */ void .ada_expand_full (string keyword1, string keyword2) { save_position (); .ada_expand_block (keyword1); delete_line (); restore_position (); .ada_expand_pair (keyword2); }