head 1.10; access; symbols perseant-exfatfs-base-20261102:1.10; locks; strict; comment @ * @; 1.10 date 2026.11.02.08.21.02; author nia; state Exp; branches; next 1.9; commitid tKAAUR0P9E9fvR1H; 1.9 date 2026.10.31.23.23.52; author christos; state Exp; branches; next 1.8; commitid YJ8f8q5AKERzyG1H; 1.8 date 2026.10.31.23.17.49; author christos; state Exp; branches; next 1.7; commitid IawtjckRJJrLwG1H; 1.7 date 2026.10.31.23.15.28; author christos; state Exp; branches; next 1.6; commitid uzAlrXLkemg1wG1H; 1.6 date 2026.10.31.23.13.18; author christos; state Exp; branches; next 1.5; commitid anwQCWCJLKjfvG1H; 1.5 date 2026.10.31.22.50.44; author christos; state Exp; branches; next 1.4; commitid NIAeb2Ee54SbnG1H; 1.4 date 2026.10.30.15.34.32; author christos; state Exp; branches; next 1.3; commitid nXvSecFlMPtTZv1H; 1.3 date 2026.10.30.15.19.03; author christos; state Exp; branches; next 1.2; commitid jATBWOvQOdgiUv1H; 1.2 date 2026.10.29.21.58.36; author christos; state Exp; branches; next 1.1; commitid qk7j1cGLFkjq9q1H; 1.1 date 2026.10.26.07.52.39; author nia; state Exp; branches; next ; commitid vNbxcBbERAW3xX0H; desc @@ 1.10 log @atto(1): don't cast malloc return value @ text @/* command.c, Atto Emacs, Public Domain, Hugh Barney, 2016, Derived from: Anthony's Editor January 93 */ #include // for MIN() #include "header.h" void quit(void) { done = 1; } void up(void) { curbp->b_point = lncolumn(curbp, upup(curbp, curbp->b_point),curbp->b_col); } void down(void) { curbp->b_point = lncolumn(curbp, dndn(curbp, curbp->b_point),curbp->b_col); } void lnbegin(void) { curbp->b_point = segstart(curbp, lnstart(curbp,curbp->b_point), curbp->b_point); } void version(void) { msg(VERSION); } void top(void) { curbp->b_point = 0; } void bottom(void) { curbp->b_point = pos(curbp, curbp->b_ebuf); if (curbp->b_epage < pos(curbp, curbp->b_ebuf)) curbp->b_reframe = 1;} void block(void) { curbp->b_mark = curbp->b_point; } void copy(void) { copy_cut(FALSE); } void cut(void) { copy_cut(TRUE); } void resize_terminal(void) { one_window(curwp); } void quit_ask(void) { if (modified_buffers() > 0) { mvaddstr(MSGLINE, 0, "Modified buffers exist; really exit (y/n) ?"); clrtoeol(); if (!yesno(FALSE)) return; } quit(); } /* flag = default answer, FALSE=n, TRUE=y */ int yesno(int flag) { int ch; addstr(flag ? " y\b" : " n\b"); refresh(); ch = getch(); if (ch == '\r' || ch == '\n') return (flag); return (tolower(ch) == 'y'); } void redraw(void) { window_t *wp; clear(); for (wp=wheadp; wp != NULL; wp = wp->w_next) wp->w_update = TRUE; update_display(); } void left(void) { int n = prev_utf8_char_size(); while (0 < curbp->b_point && n-- > 0) --curbp->b_point; } void right(void) { if (curbp->b_point >= pos(curbp, curbp->b_ebuf)) return; int n = utf8_size(*ptr(curbp,curbp->b_point)); while ((curbp->b_point < pos(curbp, curbp->b_ebuf)) && n-- > 0) ++curbp->b_point; } /* work out number of bytes based on first byte */ int utf8_size(char_t c) { if (c >= 192 && c < 224) return 2; if (c >= 224 && c < 240) return 3; if (c >= 240 && c < 248) return 4; return 1; /* if in doubt it is 1 */ } int prev_utf8_char_size(void) { int n; for (n=2;n<5;n++) if (-1 < curbp->b_point - n && (utf8_size(*(ptr(curbp, curbp->b_point - n))) == n)) return n; return 1; } void lnend(void) { if (curbp->b_point == pos(curbp, curbp->b_ebuf)) return; /* do nothing if EOF */ curbp->b_point = dndn(curbp, curbp->b_point); point_t p = curbp->b_point; left(); curbp->b_point = (*ptr(curbp, curbp->b_point) == '\n') ? curbp->b_point : p; } void wleft(void) { while (curbp->b_point > 0 && !isspace(*ptr(curbp, curbp->b_point))) --curbp->b_point; while (curbp->b_point > 0 && isspace(*ptr(curbp, curbp->b_point))) --curbp->b_point; } void pgdown(void) { curbp->b_page = curbp->b_point = upup(curbp, curbp->b_epage); while (0 < curbp->b_row--) down(); curbp->b_epage = pos(curbp, curbp->b_ebuf); } void pgup(void) { int i = curwp->w_rows; while (0 < --i) { curbp->b_page = upup(curbp, curbp->b_page); up(); } } void wright(void) { point_t end_p = pos(curbp, curbp->b_ebuf); while (curbp->b_point < end_p && !isspace(*ptr(curbp, curbp->b_point))) ++curbp->b_point; while (curbp->b_point < end_p && isspace(*ptr(curbp, curbp->b_point))) ++curbp->b_point; } void insert(void) { assert(curbp->b_gap <= curbp->b_egap); if (curbp->b_gap == curbp->b_egap && !growgap(curbp, CHUNK)) return; curbp->b_point = movegap(curbp, curbp->b_point); /* overwrite if mid line, not EOL or EOF, CR will insert as normal */ if ((curbp->b_flags & B_OVERWRITE) && *input != '\r' && curbp->b_point < pos(curbp,curbp->b_ebuf) && *(ptr(curbp, curbp->b_point)) != '\n') { *(ptr(curbp, curbp->b_point)) = *input; if (curbp->b_point < pos(curbp, curbp->b_ebuf)) ++curbp->b_point; } else { *curbp->b_gap++ = *input == '\r' ? '\n' : *input; curbp->b_point = pos(curbp, curbp->b_egap); // force reframe if scrolled off bottom of screen and at EOF if (curbp->b_point == pos(curbp, curbp->b_ebuf) && curbp->b_point >= curbp->b_epage) curbp->b_reframe = 1; } curbp->b_flags |= B_MODIFIED; } void backsp(void) { curbp->b_point = movegap(curbp, curbp->b_point); if (curbp->b_buf < curbp->b_gap) { curbp->b_gap -= prev_utf8_char_size(); curbp->b_flags |= B_MODIFIED; } curbp->b_point = pos(curbp, curbp->b_egap); } void delete(void) { curbp->b_point = movegap(curbp, curbp->b_point); if (curbp->b_egap < curbp->b_ebuf) { curbp->b_egap += MIN(utf8_size(*curbp->b_egap), curbp->b_ebuf - curbp->b_egap); curbp->b_point = pos(curbp, curbp->b_egap); curbp->b_flags |= B_MODIFIED; } } void gotoline(void) { long lline; int line; char *ep; point_t p; if (getinput("Goto line: ", temp, STRBUF_S, F_CLEAR)) { errno = 0; lline = strtol(temp, &ep, 10); if (ep == temp || *ep != '\0' || errno != 0 || lline < INT_MIN || INT_MAX < lline) { msg("Line %s, not valid", temp); return; } line = (int) lline; p = line_to_point(line); if (p != -1) { curbp->b_point = p; msg("Line %d", line); } else { msg("Line %d, not found", line); } } } void insertfile(void) { if (getfilename("Insert file: ", temp, sizeof(temp))) (void)insert_file(temp, TRUE); } void readfile(void) { buffer_t *bp; temp[0] = '\0'; int result = getfilename("Find file: ", (char*)temp, sizeof(temp)); if (result) { bp = find_buffer(temp, TRUE); if (bp == NULL) { msg("Failed to allocate buffer."); return; } disassociate_b(curwp); /* we are leaving the old buffer for a new one */ curbp = bp; associate_b2w(curbp, curwp); /* load the file if not already loaded */ if (bp->b_fname[0] == '\0') { if (!load_file(temp)) { msg("New file %s", temp); } strlcpy(curbp->b_fname, temp, sizeof(curbp->b_fname)); } } } void savebuffer(void) { if (curbp->b_fname[0] != '\0') { save(curbp->b_fname); return; } else { writefile(); } refresh(); } void writefile(void) { strlcpy(temp, curbp->b_fname, sizeof(temp)); if (getinput("Write file: ", temp, sizeof(temp), F_NONE)) if (save(temp) == TRUE) strlcpy(curbp->b_fname, temp, sizeof(curbp->b_fname)); } void killbuffer(void) { buffer_t *kill_bp = curbp; int bcount = count_buffers(); /* do nothing if only buffer left is the scratch buffer */ if (bcount == 1 && 0 == strcmp(get_buffer_name(curbp), "*scratch*")) return; if (curbp->b_flags & B_MODIFIED) { mvaddstr(MSGLINE, 0, "Discard changes (y/n) ?"); clrtoeol(); if (!yesno(FALSE)) return; } if (bcount == 1) { /* create a scratch buffer */ if (create_scratch() == NULL) { msg("Failed to allocate buffer."); return; } } next_buffer(); assert(kill_bp != curbp); delete_buffer(kill_bp); } void iblock(void) { block(); msg("Mark set"); } void toggle_overwrite_mode(void) { if (curbp->b_flags & B_OVERWRITE) curbp->b_flags &= ~B_OVERWRITE; else curbp->b_flags |= B_OVERWRITE; } void killtoeol(void) { if (curbp->b_point == pos(curbp, curbp->b_ebuf)) return; /* do nothing if at end of file */ if (*(ptr(curbp, curbp->b_point)) == 0xa) { delete(); /* delete CR if at start of empty line */ } else { curbp->b_mark = curbp->b_point; lnend(); if (curbp->b_mark != curbp->b_point) copy_cut(TRUE); } } void copy_cut(int cut) { char_t *p; /* if no mark or point == marker, nothing doing */ if (curbp->b_mark == NOMARK || curbp->b_point == curbp->b_mark) return; if (scrap != NULL) { free(scrap); scrap = NULL; } if (curbp->b_point < curbp->b_mark) { /* point above marker: move gap under point, region = marker - point */ (void) movegap(curbp, curbp->b_point); p = ptr(curbp, curbp->b_point); nscrap = curbp->b_mark - curbp->b_point; } else { /* if point below marker: move gap under marker, region = point - marker */ (void) movegap(curbp, curbp->b_mark); p = ptr(curbp, curbp->b_mark); nscrap = curbp->b_point - curbp->b_mark; } if ((scrap = malloc(nscrap)) == NULL) { nscrap = 0; msg("No more memory available."); } else { (void) memcpy(scrap, p, nscrap * sizeof (char_t)); if (cut) { curbp->b_egap += nscrap; /* if cut expand gap down */ curbp->b_point = pos(curbp, curbp->b_egap); /* set point to after region */ curbp->b_flags |= B_MODIFIED; msg("%ld bytes cut.", nscrap); } else { msg("%ld bytes copied.", nscrap); } curbp->b_mark = NOMARK; /* unmark */ } } void paste(void) { if(curbp->b_flags & B_OVERWRITE) return; if (nscrap <= 0) { msg("Scrap is empty. Nothing to paste."); } else if (nscrap < curbp->b_egap - curbp->b_gap || growgap(curbp, nscrap)) { curbp->b_point = movegap(curbp, curbp->b_point); memcpy(curbp->b_gap, scrap, nscrap * sizeof (char_t)); curbp->b_gap += nscrap; curbp->b_point = pos(curbp, curbp->b_egap); curbp->b_flags |= B_MODIFIED; } } void showpos(void) { int current, lastln; point_t end_p = pos(curbp, curbp->b_ebuf); get_line_stats(¤t, &lastln); if (curbp->b_point == end_p) { msg("[EOB] Line = %d/%d Point = %ld/%td", current, lastln, curbp->b_point, ((curbp->b_ebuf - curbp->b_buf) - (curbp->b_egap - curbp->b_gap))); } else { msg("Char = %s 0x%x Line = %d/%d Point = %ld/%td", unctrl(*(ptr(curbp, curbp->b_point))), *(ptr(curbp, curbp->b_point)), current, lastln, curbp->b_point, ((curbp->b_ebuf - curbp->b_buf) - (curbp->b_egap - curbp->b_gap))); } } @ 1.9 log @prevent negative offsets hiding bugs (Kilian Feess) @ text @d324 1 a324 1 if ((scrap = (char_t*) malloc(nscrap)) == NULL) { @ 1.8 log @Add checks to unchecked allocations (Kilian Feess) @ text @d97 1 a97 2 char_t *p; while (!isspace(*(p = ptr(curbp, curbp->b_point))) && curbp->b_buf < p) d99 1 a99 1 while (isspace(*(p = ptr(curbp, curbp->b_point))) && curbp->b_buf < p) @ 1.7 log @avoid buffer overreads (Kilian Feess) @ text @d212 4 d221 1 a221 1 if (bp != NULL && bp->b_fname[0] == '\0') { d267 4 a270 1 create_scratch(); @ 1.6 log @set nscrap to 0 on malloc failure (Kilian Feess) @ text @d61 2 d123 2 a124 2 char_t *p; while (!isspace(*(p = ptr(curbp, curbp->b_point))) && p < curbp->b_ebuf) d126 1 a126 1 while (isspace(*(p = ptr(curbp, curbp->b_point))) && p < curbp->b_ebuf) d138 1 a138 1 if ((curbp->b_flags & B_OVERWRITE) && *input != '\r' && *(ptr(curbp, curbp->b_point)) != '\n' && curbp->b_point < pos(curbp,curbp->b_ebuf) ) { @ 1.5 log @Avoid filename trucation of paths > NAME_MAX, change it to PATH_MAX and detect truncation and beep (completion) or print an error message (save) (Kilian Feess) @ text @d317 1 @ 1.4 log @mark msg __printflike, and fix the broken formats @ text @d197 1 a197 1 if (getfilename("Insert file: ", temp, NAME_MAX)) d206 1 a206 2 int result = getfilename("Find file: ", (char*)temp, NAME_MAX); /* int result = getinput("Find file: ", (char*)temp, NAME_MAX, F_CLEAR); */ d219 1 a219 1 strlcpy(curbp->b_fname, temp, NAME_MAX); d237 2 a238 2 strlcpy(temp, curbp->b_fname, NAME_MAX); if (getinput("Write file: ", temp, NAME_MAX, F_NONE)) d240 1 a240 1 strlcpy(curbp->b_fname, temp, NAME_MAX); @ 1.3 log @move by the space available. (reported by Kilian Feess) @ text @d356 1 a356 1 msg("[EOB] Line = %d/%d Point = %d/%d", current, lastln, d359 1 a359 1 msg("Char = %s 0x%x Line = %d/%d Point = %d/%d", unctrl(*(ptr(curbp, curbp->b_point))), *(ptr(curbp, curbp->b_point)), @ 1.2 log @- sprintf -> snprintf, strcpy/strcat -> strlcpy/strcat - merge dup scratch buffer creation - use sizeof more @ text @d3 1 d163 2 a164 1 curbp->b_egap += utf8_size(*curbp->b_egap); @ 1.1 log @atto(1): Initial import of tiny text editor, as discussed on tech-userlevel@@. This is Hugh Barney's atto text editor modified for inclusion in NetBSD. Since the upstream repository is inactive and the original author wishes to keep the code base below 2000 lines of C, this is effectively a fork that lives in usr.bin/atto. It serves as an alternative to vi(1) for users more accustomed to modeless text editors. Notable changes include reworked keyboard input code to handle a wider range of terminals (via libcurses cursor key translation), more useful help for unrecognized key presses, and standardized option handling using getopt (with syntax highlighting disabled by default). Thanks martin and other folks for feedback and testing. ATTO(1) General Commands Manual ATTO(1) NAME atto - simple modeless text editor SYNOPSIS atto [-C] file ... DESCRIPTION atto is a screen-based text editor. It serves as an alternative to vi(1) for users who are not accustomed to text editors with separate insertion and editing modes, and includes support for editing UTF-8 text. Perhaps all that needs to be known to use atto at a basic level is that holding the control key, pressing x, then pressing w will write the currently open file, and holding the control key, pressing x, then pressing c will exit the editor. The options are as follows: -C Use colours, if available, for basic language-unaware syntax highlighting. Cursor movement commands C is used as an abbreviation for the control key, commonly rendered as "ctrl" on English language keyboards. Arrow keys or the navigation cluster can also be used to move the cursor when the keyboard has such features. C-a move cursor to beginning of current line (i.e. "home") C-b move cursor back one character (i.e. "left") C-e move cursor to end of current line (i.e. "end") C-f move cursor forward one character (i.e. "right") C-n move cursor to next line (i.e. "down") C-p move cursor to previous line (i.e. "up") C-v move cursor to next page of text (i.e. "page down") Esc-b move cursor back one word Esc-f move cursor forward one word Esc-g jump to a specific line number in the file Esc-v move cursor to previous page of text (i.e. "page up") Esc-< jump to the start of the file Esc-> jump to the end of the file Text editing commands C-d delete character under cursor (i.e. "del") C-h move cursor backwards and delete previous character (i.e. "backspace") C-g abort at prompts (cancel task when asked a question) C-i insert tab character C-j insert newline character (i.e. "enter", "return") C-k cut text up to the end of the line (i.e. "kill line") C-l refresh the screen C-m insert carriage return character C-r search for text backwards, jump to next result C-s search for text forwards, jump to next result C-y paste the text in the cut buffer (i.e. "yank") Esc-r search for text and replace it Buffer commands atto supports multiple editing buffers, allowing users to edit multiple files from a single instance of the program. C-x C-c close all open buffers and exit atto, prompting if changes would be discarded C-x C-f open a file into a new buffer C-x C-k close the current buffer, prompting if changes would be discarded (i.e. "kill") C-x C-s save current buffer to disk, overwriting the file C-x C-w save current buffer to disk, prompting for file name ("write") C-x n cycle to next buffer C-x i read another file and insert it into the current buffer C-x = display cursor position in current buffer Region commands As well as cutting text on a line basis (i.e. C-k), a region can be selected by setting a mark at the beginning of the region, and then moving the cursor to the end of the region. C-space set the beginning of the region at the current position C-w cut the marked region Esc-w copy the marked region without deleting it Window management commands Simple split-screen window management is included. For more advanced use cases, consider using tmux(1). C-x 1 delete windows except the currently active one C-x 2 split current window into two windows C-x o switch to the next open window SEE ALSO ed(1), ex(1), vi(1), xedit(1) STANDARDS atto is not the standard editor. AUTHORS atto was written primarily by Hugh Barney, and was originally based on public domain code from Anthony Howe's editor (commonly known as Anthony's Editor). It has been modified for inclusion in NetBSD, including with enhanced option handling. NetBSD 11.99.9 October 15, 2026 NetBSD 11.99.9 @ text @a244 1 buffer_t *bp; d260 1 a260 2 bp = find_buffer("*scratch*", TRUE); strcpy(bp->b_bname, "*scratch*"); @