--- /dev/null
+/* $OpenBSD: cmode.c,v 1.8 2012/05/18 02:13:44 lum Exp $ */
+/*
+ * This file is in the public domain.
+ *
+ * Author: Kjell Wooding <kjell@openbsd.org>
+ */
+
+/*
+ * Implement an non-irritating KNF-compliant mode for editing
+ * C code.
+ */
+#include <ctype.h>
+
+#include "def.h"
+#include "kbd.h"
+#include "funmap.h"
+
+/* Pull in from modes.c */
+extern int changemode(int, int, char *);
+
+static int cc_strip_trailp = TRUE; /* Delete Trailing space? */
+static int cc_basic_indent = 8; /* Basic Indent multiple */
+static int cc_cont_indent = 4; /* Continued line indent */
+static int cc_colon_indent = -8; /* Label / case indent */
+
+static int getmatch(int, int);
+static int getindent(const struct line *, int *);
+static int in_whitespace(struct line *, int);
+static int findcolpos(const struct buffer *, const struct line *, int);
+static struct line *findnonblank(struct line *);
+static int isnonblank(const struct line *, int);
+
+void cmode_init(void);
+int cc_comment(int, int);
+
+/* Keymaps */
+
+static PF cmode_brace[] = {
+ cc_brace, /* } */
+};
+
+static PF cmode_cCP[] = {
+ compile, /* C-c P */
+};
+
+
+static PF cmode_cc[] = {
+ NULL, /* ^C */
+ rescan, /* ^D */
+ rescan, /* ^E */
+ rescan, /* ^F */
+ rescan, /* ^G */
+ rescan, /* ^H */
+ cc_tab, /* ^I */
+ rescan, /* ^J */
+ rescan, /* ^K */
+ rescan, /* ^L */
+ cc_lfindent, /* ^M */
+};
+
+static PF cmode_spec[] = {
+ cc_char, /* : */
+};
+
+static struct KEYMAPE (1 + IMAPEXT) cmode_cmap = {
+ 1,
+ 1 + IMAPEXT,
+ rescan,
+ {
+ { 'P', 'P', cmode_cCP, NULL }
+ }
+};
+
+static struct KEYMAPE (3 + IMAPEXT) cmodemap = {
+ 3,
+ 3 + IMAPEXT,
+ rescan,
+ {
+ { CCHR('C'), CCHR('M'), cmode_cc, (KEYMAP *) &cmode_cmap },
+ { ':', ':', cmode_spec, NULL },
+ { '}', '}', cmode_brace, NULL }
+ }
+};
+
+/* Funtion, Mode hooks */
+
+void
+cmode_init(void)
+{
+ funmap_add(cmode, "c-mode");
+ funmap_add(cc_char, "c-handle-special-char");
+ funmap_add(cc_brace, "c-handle-special-brace");
+ funmap_add(cc_tab, "c-tab-or-indent");
+ funmap_add(cc_indent, "c-indent");
+ funmap_add(cc_lfindent, "c-indent-and-newline");
+ maps_add((KEYMAP *)&cmodemap, "c");
+}
+
+/*
+ * Enable/toggle c-mode
+ */
+int
+cmode(int f, int n)
+{
+ return(changemode(f, n, "c"));
+}
+
+/*
+ * Handle special C character - selfinsert then indent.
+ */
+int
+cc_char(int f, int n)
+{
+ if (n < 0)
+ return (FALSE);
+ if (selfinsert(FFRAND, n) == FALSE)
+ return (FALSE);
+ return (cc_indent(FFRAND, n));
+}
+
+/*
+ * Handle special C character - selfinsert then indent.
+ */
+int
+cc_brace(int f, int n)
+{
+ if (n < 0)
+ return (FALSE);
+ if (showmatch(FFRAND, 1) == FALSE)
+ return (FALSE);
+ return (cc_indent(FFRAND, n));
+}
+
+
+/*
+ * If we are in the whitespace at the beginning of the line,
+ * simply act as a regular tab. If we are not, indent
+ * current line according to whitespace rules.
+ */
+int
+cc_tab(int f, int n)
+{
+ int inwhitep = FALSE; /* In leading whitespace? */
+
+ inwhitep = in_whitespace(curwp->w_dotp, llength(curwp->w_dotp));
+
+ /* If empty line, or in whitespace */
+ if (llength(curwp->w_dotp) == 0 || inwhitep)
+ return (selfinsert(f, n));
+
+ return (cc_indent(FFRAND, 1));
+}
+
+/*
+ * Attempt to indent current line according to KNF rules.
+ */
+int
+cc_indent(int f, int n)
+{
+ int pi, mi; /* Previous indents */
+ int ci, dci; /* current indent, don't care */
+ struct line *lp;
+ int ret;
+
+ if (n < 0)
+ return (FALSE);
+
+ undo_boundary_enable(FFRAND, 0);
+ if (cc_strip_trailp)
+ deltrailwhite(FFRAND, 1);
+
+ /*
+ * Search backwards for a non-blank, non-preprocessor,
+ * non-comment line
+ */
+
+ lp = findnonblank(curwp->w_dotp);
+
+ pi = getindent(lp, &mi);
+
+ /* Strip leading space on current line */
+ delleadwhite(FFRAND, 1);
+ /* current indent is computed only to current position */
+ dci = getindent(curwp->w_dotp, &ci);
+
+ if (pi + ci < 0)
+ ret = indent(FFOTHARG, 0);
+ else
+ ret = indent(FFOTHARG, pi + ci);
+
+ undo_boundary_enable(FFRAND, 1);
+
+ return (ret);
+}
+
+/*
+ * Indent-and-newline (technically, newline then indent)
+ */
+int
+cc_lfindent(int f, int n)
+{
+ if (n < 0)
+ return (FALSE);
+ if (newline(FFRAND, 1) == FALSE)
+ return (FALSE);
+ return (cc_indent(FFRAND, n));
+}
+
+/*
+ * Get the level of indention after line lp is processed
+ * Note getindent has two returns:
+ * curi = value if indenting current line.
+ * return value = value affecting subsequent lines.
+ */
+static int
+getindent(const struct line *lp, int *curi)
+{
+ int lo, co; /* leading space, current offset*/
+ int nicol = 0; /* position count */
+ int ccol = 0; /* current column */
+ int c = '\0'; /* current char */
+ int newind = 0; /* new index value */
+ int stringp = FALSE; /* in string? */
+ int escp = FALSE; /* Escape char? */
+ int lastc = '\0'; /* Last matched string delimeter */
+ int nparen = 0; /* paren count */
+ int obrace = 0; /* open brace count */
+ int cbrace = 0; /* close brace count */
+ int contp = FALSE; /* Continue? */
+ int firstnwsp = FALSE; /* First nonspace encountered? */
+ int colonp = FALSE; /* Did we see a colon? */
+ int questionp = FALSE; /* Did we see a question mark? */
+ int slashp = FALSE; /* Slash? */
+ int astp = FALSE; /* Asterisk? */
+ int cpos = -1; /* comment position */
+ int cppp = FALSE; /* Preprocessor command? */
+
+ *curi = 0;
+
+ /* Compute leading space */
+ for (lo = 0; lo < llength(lp); lo++) {
+ if (!isspace(c = lgetc(lp, lo)))
+ break;
+ if (c == '\t'
+#ifdef NOTAB
+ && !(curbp->b_flag & BFNOTAB)
+#endif /* NOTAB */
+ ) {
+ nicol |= 0x07;
+ }
+ nicol++;
+ }
+
+ /* If last line was blank, choose 0 */
+ if (lo == llength(lp))
+ nicol = 0;
+
+ newind = 0;
+ ccol = nicol; /* current column */
+ /* Compute modifiers */
+ for (co = lo; co < llength(lp); co++) {
+ c = lgetc(lp, co);
+ /* We have a non-whitespace char */
+ if (!firstnwsp && !isspace(c)) {
+ contp = TRUE;
+ if (c == '#')
+ cppp = TRUE;
+ firstnwsp = TRUE;
+ }
+ if (c == '\\')
+ escp = !escp;
+ else if (stringp) {
+ if (!escp && (c == '"' || c == '\'')) {
+ /* unescaped string char */
+ if (getmatch(c, lastc))
+ stringp = FALSE;
+ }
+ } else if (c == '"' || c == '\'') {
+ stringp = TRUE;
+ lastc = c;
+ } else if (c == '(') {
+ nparen++;
+ } else if (c == ')') {
+ nparen--;
+ } else if (c == '{') {
+ obrace++;
+ firstnwsp = FALSE;
+ contp = FALSE;
+ } else if (c == '}') {
+ cbrace++;
+ } else if (c == '?') {
+ questionp = TRUE;
+ } else if (c == ':') {
+ /* ignore (foo ? bar : baz) construct */
+ if (!questionp)
+ colonp = TRUE;
+ } else if (c == ';') {
+ if (nparen > 0)
+ contp = FALSE;
+ } else if (c == '/') {
+ /* first nonwhitespace? -> indent */
+ if (firstnwsp) {
+ /* If previous char asterisk -> close */
+ if (astp)
+ cpos = -1;
+ else
+ slashp = TRUE;
+ }
+ } else if (c == '*') {
+ /* If previous char slash -> open */
+ if (slashp)
+ cpos = co;
+ else
+ astp = TRUE;
+ } else if (firstnwsp) {
+ firstnwsp = FALSE;
+ }
+
+ /* Reset matches that apply to next character only */
+ if (c != '\\')
+ escp = FALSE;
+ if (c != '*')
+ astp = FALSE;
+ if (c != '/')
+ slashp = FALSE;
+ }
+ /*
+ * If not terminated with a semicolon, and brace or paren open.
+ * we continue
+ */
+ if (colonp) {
+ *curi += cc_colon_indent;
+ newind -= cc_colon_indent;
+ }
+
+ *curi -= (cbrace) * cc_basic_indent;
+ newind += obrace * cc_basic_indent;
+
+ if (nparen < 0)
+ newind -= cc_cont_indent;
+ else if (nparen > 0)
+ newind += cc_cont_indent;
+
+ *curi += nicol;
+
+ /* Ignore preprocessor. Otherwise, add current column */
+ if (cppp) {
+ newind = nicol;
+ *curi = 0;
+ } else {
+ newind += nicol;
+ }
+
+ if (cpos != -1)
+ newind = findcolpos(curbp, lp, cpos);
+
+ return (newind);
+}
+
+/*
+ * Given a delimeter and its purported mate, tell us if they
+ * match.
+ */
+static int
+getmatch(int c, int mc)
+{
+ int match = FALSE;
+
+ switch (c) {
+ case '"':
+ match = (mc == '"');
+ break;
+ case '\'':
+ match = (mc == '\'');
+ break;
+ case '(':
+ match = (mc == ')');
+ break;
+ case '[':
+ match = (mc == ']');
+ break;
+ case '{':
+ match = (mc == '}');
+ break;
+ }
+
+ return (match);
+}
+
+static int
+in_whitespace(struct line *lp, int len)
+{
+ int lo;
+ int inwhitep = FALSE;
+
+ for (lo = 0; lo < len; lo++) {
+ if (!isspace(lgetc(lp, lo)))
+ break;
+ if (lo == len - 1)
+ inwhitep = TRUE;
+ }
+
+ return (inwhitep);
+}
+
+
+/* convert a line/offset pair to a column position (for indenting) */
+static int
+findcolpos(const struct buffer *bp, const struct line *lp, int lo)
+{
+ int col, i, c;
+ char tmp[5];
+
+ /* determine column */
+ col = 0;
+
+ for (i = 0; i < lo; ++i) {
+ c = lgetc(lp, i);
+ if (c == '\t'
+#ifdef NOTAB
+ && !(bp->b_flag & BFNOTAB)
+#endif /* NOTAB */
+ ) {
+ col |= 0x07;
+ col++;
+ } else if (ISCTRL(c) != FALSE)
+ col += 2;
+ else if (isprint(c)) {
+ col++;
+ } else {
+ col += snprintf(tmp, sizeof(tmp), "\\%o", c);
+ }
+
+ }
+ return (col);
+}
+
+/*
+ * Find a non-blank line, searching backwards from the supplied line pointer.
+ * For C, nonblank is non-preprocessor, non C++, and accounts
+ * for complete C-style comments.
+ */
+static struct line *
+findnonblank(struct line *lp)
+{
+ int lo;
+ int nonblankp = FALSE;
+ int commentp = FALSE;
+ int slashp;
+ int astp;
+ int c;
+
+ while (lback(lp) != curbp->b_headp && (commentp || !nonblankp)) {
+ lp = lback(lp);
+ slashp = FALSE;
+ astp = FALSE;
+
+ /* Potential nonblank? */
+ nonblankp = isnonblank(lp, llength(lp));
+
+ /*
+ * Search from end, removing complete C-style
+ * comments. If one is found, ignore it and
+ * test for nonblankness from where it starts.
+ */
+ slashp = FALSE;
+ /* Scan backwards from end to find C-style comment */
+ for (lo = llength(lp) - 1; lo >= 0; lo--) {
+ if (!isspace(c = lgetc(lp, lo))) {
+ if (commentp) { /* find comment "open" */
+ if (c == '*')
+ astp = TRUE;
+ else if (astp && c == '/') {
+ commentp = FALSE;
+ /* whitespace to here? */
+ nonblankp = isnonblank(lp, lo);
+ }
+ } else { /* find comment "close" */
+ if (c == '/')
+ slashp = TRUE;
+ else if (slashp && c == '*')
+ /* found a comment */
+ commentp = TRUE;
+ }
+ }
+ }
+ }
+
+ /* Rewound to start of file? */
+ if (lback(lp) == curbp->b_headp && !nonblankp)
+ return (curbp->b_headp);
+
+ return (lp);
+}
+
+/*
+ * Given a line, scan forward to 'omax' and determine if we
+ * are all C whitespace.
+ * Note that preprocessor directives and C++-style comments
+ * count as whitespace. C-style comments do not, and must
+ * be handled elsewhere.
+ */
+static int
+isnonblank(const struct line *lp, int omax)
+{
+ int nonblankp = FALSE; /* Return value */
+ int slashp = FALSE; /* Encountered slash */
+ int lo; /* Loop index */
+ int c; /* char being read */
+
+ /* Scan from front for preprocessor, C++ comments */
+ for (lo = 0; lo < omax; lo++) {
+ if (!isspace(c = lgetc(lp, lo))) {
+ /* Possible nonblank line */
+ nonblankp = TRUE;
+ /* skip // and # starts */
+ if (c == '#' || (slashp && c == '/')) {
+ nonblankp = FALSE;
+ break;
+ } else if (!slashp && c == '/') {
+ slashp = TRUE;
+ continue;
+ }
+ }
+ slashp = FALSE;
+ }
+ return (nonblankp);
+}