]> pd.if.org Git - pd_readline/blobdiff - mg/cmode.c
Added mg from an OpenBSD mirror site. Many thanks to the OpenBSD team and the mg...
[pd_readline] / mg / cmode.c
diff --git a/mg/cmode.c b/mg/cmode.c
new file mode 100644 (file)
index 0000000..674d89d
--- /dev/null
@@ -0,0 +1,528 @@
+/* $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);
+}