kilo

Kilo: minimal text editor written in C
git clone git://git.dimitrijedobrota.com/kilo.git
Log | Files | Refs

commit 06dacdfa12189128fb57b9f611c2eff6e3d4169b
parent 1b9884eb1d08734ad600fd451764208332434652
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Sun,  9 Apr 2023 09:12:52 +0200

A Text viewer, clang-format

Diffstat:
A.clang-format | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mkilo.c | 329++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
2 files changed, 414 insertions(+), 93 deletions(-)

diff --git a/.clang-format b/.clang-format @@ -0,0 +1,178 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: true +AlignArrayOfStructures: Right +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Right +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: true +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/kilo.c b/kilo.c @@ -1,17 +1,25 @@ /*** includes ***/ +#define _DEFAULT_SOURCE +#define _BSD_SOURCE +#define _GNU_SOURCE + #include <ctype.h> #include <errno.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> +#include <sys/types.h> #include <termios.h> +#include <time.h> #include <unistd.h> /*** defines ***/ -#define KILO_VERSION "0.0.1" +#define KILO_VERSION "0.0.1" +#define KILO_TAB_STOP 8 #define CTRL_KEY(k) ((k)&0x1f) @@ -29,10 +37,25 @@ enum editorKey { /*** data ***/ +typedef struct erow { + int size; + int rsize; + char *chars; + char *render; +} erow; + struct editorConfig { int cx, cy; + int rx; + int rowoff; + int coloff; int screenrows; int screencols; + int numrows; + erow *row; + char *filename; + char statusmsg[80]; + time_t statusmsg_time; struct termios orig_termios; }; @@ -54,8 +77,7 @@ void disableRawMode() { } void enableRawMode() { - if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) - die("tcgetattr"); + if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) die("tcgetattr"); atexit(disableRawMode); struct termios raw = E.orig_termios; @@ -67,70 +89,50 @@ void enableRawMode() { raw.c_cc[VMIN] = 0; raw.c_cc[VTIME] = 1; - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) - die("tcsetattr"); + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) die("tcsetattr"); } int editorReadKey() { int nread; char c; while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { - if (nread == -1 && errno != EAGAIN) - die("read"); + if (nread == -1 && errno != EAGAIN) die("read"); } if (c == '\x1b') { char seq[3]; - if (read(STDIN_FILENO, &seq[0], 1) != 1) - return '\x1b'; - if (read(STDIN_FILENO, &seq[1], 1) != 1) - return '\x1b'; + if (read(STDIN_FILENO, &seq[0], 1) != 1) return '\x1b'; + if (read(STDIN_FILENO, &seq[1], 1) != 1) return '\x1b'; if (seq[0] == '[') { if (seq[1] >= '0' && seq[1] <= '9') { - if (read(STDIN_FILENO, &seq[2], 1) != 1) - return '\x1b'; + if (read(STDIN_FILENO, &seq[2], 1) != 1) return '\x1b'; if (seq[2] == '~') { switch (seq[1]) { - case '1': - return HOME_KEY; - case '3': - return DEL_KEY; - case '4': - return END_KEY; - case '5': - return PAGE_UP; - case '6': - return PAGE_DOWN; - case '7': - return HOME_KEY; - case '8': - return END_KEY; + case '1': return HOME_KEY; + case '3': return DEL_KEY; + case '4': return END_KEY; + case '5': return PAGE_UP; + case '6': return PAGE_DOWN; + case '7': return HOME_KEY; + case '8': return END_KEY; } } } else { switch (seq[1]) { - case 'A': - return ARROW_UP; - case 'B': - return ARROW_DOWN; - case 'C': - return ARROW_RIGHT; - case 'D': - return ARROW_LEFT; - case 'H': - return HOME_KEY; - case 'F': - return END_KEY; + case 'A': return ARROW_UP; + case 'B': return ARROW_DOWN; + case 'C': return ARROW_RIGHT; + case 'D': return ARROW_LEFT; + case 'H': return HOME_KEY; + case 'F': return END_KEY; } } } else if (seq[0] == 'O') { switch (seq[1]) { - case 'H': - return HOME_KEY; - case 'F': - return END_KEY; + case 'H': return HOME_KEY; + case 'F': return END_KEY; } } return '\x1b'; @@ -143,22 +145,17 @@ int getCursorPosition(int *rows, int *cols) { char buf[32]; unsigned int i = 0; - if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) - return -1; + if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1; while (i < sizeof(buf) - 1) { - if (read(STDIN_FILENO, &buf[i], 1) != 1) - break; - if (buf[i] == 'R') - break; + if (read(STDIN_FILENO, &buf[i], 1) != 1) break; + if (buf[i] == 'R') break; i++; } buf[i] = '\0'; - if (buf[0] != '\x1b' || buf[1] != '[') - return -1; - if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) - return -1; + if (buf[0] != '\x1b' || buf[1] != '[') return -1; + if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) return -1; return 0; } @@ -167,8 +164,7 @@ int getWindowSize(int *rows, int *cols) { struct winsize ws; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) - return -1; + if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) return -1; return getCursorPosition(rows, cols); } else { *cols = ws.ws_col; @@ -177,6 +173,77 @@ int getWindowSize(int *rows, int *cols) { return 0; } +/*** row operations ***/ + +int editorRowCxToRx(erow *row, int cx) { + int rx = 0; + for (int j = 0; j < cx; j++) { + if (row->chars[j] == '\t') rx += (KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP); + rx++; + } + return rx; +} + +void editorUpdateRow(erow *row) { + int tabs = 0; + for (int j = 0; j < row->size; j++) + if (row->chars[j] == '\t') tabs++; + + free(row->render); + row->render = malloc(row->size + tabs * (KILO_TAB_STOP - 1) + 1); + + int idx = 0; + for (int j = 0; j < row->size; j++) { + if (row->chars[j] == '\t') { + row->render[idx++] = ' '; + while (idx % KILO_TAB_STOP != 0) row->render[idx++] = ' '; + } else { + row->render[idx++] = row->chars[j]; + } + } + row->render[idx] = '\0'; + row->rsize = idx; +} + +void editorAppendRow(char *s, size_t len) { + E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1)); + + int at = E.numrows; + E.row[at].size = len; + E.row[at].chars = malloc(len + 1); + memcpy(E.row[at].chars, s, len); + E.row[at].chars[len] = '\0'; + + E.row[at].rsize = 0; + E.row[at].render = NULL; + editorUpdateRow(&E.row[at]); + + E.numrows++; +} + +/*** file i/o ***/ + +void editorOpen(char *filename) { + free(E.filename); + E.filename = strdup(filename); + + FILE *fp = fopen(filename, "r"); + if (!fp) die("fopen"); + + char *line = NULL; + size_t linecap = 0; + ssize_t linelen; + while ((linelen = getline(&line, &linecap, fp)) != -1) { + while (linelen > 0 && + (line[linelen - 1] == '\n' || line[linelen - 1] == '\r')) + linelen--; + + editorAppendRow(line, linelen); + } + free(line); + fclose(fp); +} + /*** append buffer ***/ struct abuf { @@ -190,8 +257,7 @@ struct abuf { void abAppend(struct abuf *ab, const char *s, int len) { char *new = realloc(ab->b, ab->len + len); - if (new == NULL) - return; + if (new == NULL) return; memcpy(&new[ab->len], s, len); ab->b = new; ab->len += len; @@ -202,28 +268,36 @@ void abFree(struct abuf *ab) { free(ab->b); } /*** input ***/ void editorMoveCursor(int key) { + erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; + switch (key) { case ARROW_LEFT: if (E.cx != 0) { E.cx--; + } else if (E.cy > 0) { + E.cy--; + E.cx = E.row[E.cy].size; } break; case ARROW_RIGHT: - if (E.cx != E.screencols - 1) { + if (row && E.cx < row->size) { E.cx++; + } else if (row && E.cx == row->size) { + E.cy++; + E.cx = 0; } break; case ARROW_UP: - if (E.cy != 0) { - E.cy--; - } + if (E.cy != 0) { E.cy--; } break; case ARROW_DOWN: - if (E.cx != E.screenrows - 1) { - E.cy++; - } + if (E.cy < E.numrows) { E.cy++; } break; } + + row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; + int rowlen = row ? row->size : 0; + if (E.cx > rowlen) { E.cx = rowlen; } } void editorProcessKeypress() { @@ -236,68 +310,118 @@ void editorProcessKeypress() { exit(0); break; - case HOME_KEY: - E.cx = 0; - break; + case HOME_KEY: E.cx = 0; break; case END_KEY: - E.cx = E.screencols - 1; + if (E.cy < E.numrows) E.cx = E.row[E.cy].size; break; case PAGE_UP: case PAGE_DOWN: { + if (c == PAGE_UP) { + E.cy = E.rowoff; + } else if (c == PAGE_DOWN) { + E.cy = E.rowoff + E.screenrows - 1; + if (E.cy > E.numrows) E.cy = E.numrows; + } + int times = E.screenrows; - while (times--) - editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN); + while (times--) editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN); + break; } case ARROW_UP: case ARROW_DOWN: case ARROW_LEFT: - case ARROW_RIGHT: - editorMoveCursor(c); - break; + case ARROW_RIGHT: editorMoveCursor(c); break; } } /*** output ***/ +void editorScroll() { + E.rx = 0; + if (E.cy < E.numrows) { E.rx = editorRowCxToRx(&E.row[E.cy], E.cx); } + if (E.cy < E.rowoff) { E.rowoff = E.cy; } + if (E.cy >= E.rowoff + E.screenrows) { E.rowoff = E.cy - E.screenrows + 1; } + if (E.rx < E.coloff) { E.coloff = E.rx; } + if (E.rx >= E.coloff + E.screencols) { E.coloff = E.rx - E.screencols + 1; } +} + void editorDrawRows(struct abuf *ab) { for (int y = 0; y < E.screenrows; y++) { - if (y == E.screenrows / 3) { - char welcome[80]; - int welcomelen = snprintf(welcome, sizeof(welcome), - "Kilo editor -- version %s", KILO_VERSION); - if (welcomelen > E.screencols) - welcomelen = E.screencols; - int padding = (E.screencols - welcomelen) / 2; - if (padding) { + int filerow = y + E.rowoff; + if (filerow >= E.numrows) { + if (E.numrows == 0 && y == E.screenrows / 3) { + char welcome[80]; + int welcomelen = snprintf(welcome, sizeof(welcome), + "Kilo editor -- version %s", KILO_VERSION); + if (welcomelen > E.screencols) welcomelen = E.screencols; + int padding = (E.screencols - welcomelen) / 2; + if (padding) { + abAppend(ab, "~", 1); + padding--; + } + while (padding--) abAppend(ab, " ", 1); + abAppend(ab, welcome, welcomelen); + } else { abAppend(ab, "~", 1); - padding--; } - while (padding--) - abAppend(ab, " ", 1); - abAppend(ab, welcome, welcomelen); } else { - abAppend(ab, "~", 1); + int len = E.row[filerow].rsize - E.coloff; + if (len < 0) len = 0; + if (len > E.screencols) len = E.screencols; + abAppend(ab, E.row[filerow].render + E.coloff, len); } abAppend(ab, "\x1b[K", 3); // clear current row - if (y < E.screenrows - 1) { - abAppend(ab, "\r\n", 2); + abAppend(ab, "\r\n", 2); + } +} + +void editorDrawStatusBar(struct abuf *ab) { + abAppend(ab, "\x1b[7m", 4); + char status[80], rstatus[80]; + int len = snprintf(status, sizeof(status), "%.20s - %d lines", + E.filename ? E.filename : "[No Name]", E.numrows); + int rlen = snprintf(rstatus, sizeof(rstatus), "%d/%d", E.cy + 1, E.numrows); + if (len > E.screencols) len = E.screencols; + abAppend(ab, status, len); + while (len < E.screencols) { + if (E.screencols - len == rlen) { + abAppend(ab, rstatus, rlen); + break; + } else { + abAppend(ab, " ", 1); + len++; } } + abAppend(ab, "\x1b[m", 3); + abAppend(ab, "\r\n", 2); +} + +void editorDrawMessageBar(struct abuf *ab) { + abAppend(ab, "\x1b[K", 3); + int msglen = strlen(E.statusmsg); + if (msglen > E.screencols) msglen = E.screencols; + if (msglen && time(NULL) - E.statusmsg_time < 5) + abAppend(ab, E.statusmsg, msglen); } void editorRefreshScreen() { + editorScroll(); + struct abuf ab = ABUF_INIT; abAppend(&ab, "\x1b[?25l", 6); // hide cursor abAppend(&ab, "\x1b[H", 3); editorDrawRows(&ab); + editorDrawStatusBar(&ab); + editorDrawMessageBar(&ab); char buf[32]; - snprintf(buf, sizeof(buf), "\x1b[%d;%dH", E.cy + 1, E.cx + 1); + snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1, + (E.rx - E.coloff) + 1); abAppend(&ab, buf, strlen(buf)); abAppend(&ab, "\x1b[?25h", 6); // show cursor @@ -306,19 +430,38 @@ void editorRefreshScreen() { abFree(&ab); } +void editorSetStatusMessage(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap); + va_end(ap); + E.statusmsg_time = time(NULL); +} + /*** init ***/ void initEditor() { E.cx = 0; E.cy = 0; - - if (getWindowSize(&E.screenrows, &E.screencols) == -1) - die("getWindowSize"); + E.rx = 0; + E.rowoff = 0; + E.coloff = 0; + E.numrows = 0; + E.row = NULL; + E.filename = NULL; + E.statusmsg[0] = '\0'; + E.statusmsg_time = 0; + + if (getWindowSize(&E.screenrows, &E.screencols) == -1) die("getWindowSize"); + E.screenrows -= 2; } -int main() { +int main(int argc, char *argv[]) { enableRawMode(); initEditor(); + if (argc >= 2) { editorOpen(argv[1]); } + + editorSetStatusMessage("HELP: Ctrl-Q = quit"); while (1) { editorRefreshScreen();