From e6596f3e27c7c094e310e46c552be1250670b2ff Mon Sep 17 00:00:00 2001 From: Anders Holck Date: Wed, 8 Apr 2026 16:37:32 +0200 Subject: [PATCH] Adding ved --- ved/Makefile | 17 ++ ved/WORKPLAN.md | 80 +++++++++ ved/desc.txt | 1 + ved/src/buffer.c | 157 ++++++++++++++++++ ved/src/buffer.o | Bin 0 -> 5704 bytes ved/src/command.c | 61 +++++++ ved/src/command.o | Bin 0 -> 3912 bytes ved/src/editor.c | 230 ++++++++++++++++++++++++++ ved/src/editor.h | 99 +++++++++++ ved/src/editor.o | Bin 0 -> 10360 bytes ved/src/input.c | 25 +++ ved/src/input.o | Bin 0 -> 2312 bytes ved/src/insert.c | 134 +++++++++++++++ ved/src/insert.o | Bin 0 -> 4480 bytes ved/src/main.c | 73 ++++++++ ved/src/main.o | Bin 0 -> 3560 bytes ved/src/nano.c | 246 +++++++++++++++++++++++++++ ved/src/nano.o | Bin 0 -> 10560 bytes ved/src/normal.c | 238 ++++++++++++++++++++++++++ ved/src/normal.o | Bin 0 -> 9664 bytes ved/src/ollama.c | 404 +++++++++++++++++++++++++++++++++++++++++++++ ved/src/ollama.o | Bin 0 -> 18360 bytes ved/src/syntax.c | 326 ++++++++++++++++++++++++++++++++++++ ved/src/syntax.o | Bin 0 -> 22384 bytes ved/src/terminal.c | 16 ++ ved/src/terminal.o | Bin 0 -> 1944 bytes vedit/vedit.py | 3 + 27 files changed, 2110 insertions(+) create mode 100644 ved/Makefile create mode 100644 ved/WORKPLAN.md create mode 100644 ved/desc.txt create mode 100644 ved/src/buffer.c create mode 100644 ved/src/buffer.o create mode 100644 ved/src/command.c create mode 100644 ved/src/command.o create mode 100644 ved/src/editor.c create mode 100644 ved/src/editor.h create mode 100644 ved/src/editor.o create mode 100644 ved/src/input.c create mode 100644 ved/src/input.o create mode 100644 ved/src/insert.c create mode 100644 ved/src/insert.o create mode 100644 ved/src/main.c create mode 100644 ved/src/main.o create mode 100644 ved/src/nano.c create mode 100644 ved/src/nano.o create mode 100644 ved/src/normal.c create mode 100644 ved/src/normal.o create mode 100644 ved/src/ollama.c create mode 100644 ved/src/ollama.o create mode 100644 ved/src/syntax.c create mode 100644 ved/src/syntax.o create mode 100644 ved/src/terminal.c create mode 100644 ved/src/terminal.o diff --git a/ved/Makefile b/ved/Makefile new file mode 100644 index 0000000..85a1ade --- /dev/null +++ b/ved/Makefile @@ -0,0 +1,17 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -pedantic +LDFLAGS = -lncurses -lcurl -lpthread +SRC = $(wildcard src/*.c) +OBJ = $(SRC:.c=.o) +BIN = ved + +$(BIN): $(OBJ) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +src/%.o: src/%.c src/editor.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f src/*.o $(BIN) + +.PHONY: clean diff --git a/ved/WORKPLAN.md b/ved/WORKPLAN.md new file mode 100644 index 0000000..6816900 --- /dev/null +++ b/ved/WORKPLAN.md @@ -0,0 +1,80 @@ +# Vi-style Editor with Ollama Code Prediction + +## Overview +Terminal-based code editor written in C with nvi-style modal editing and local LLM-powered code completion via Ollama. For Linux CLI. Target users: personal + colleagues. + +## Editor Scope (nvi-style) + +### Modal Editing +- Normal, Insert, Command-line (`:`) modes + +### Navigation +- `h/j/k/l` — basic movement +- `w/b/e` — word movement +- `0/$` — line start/end +- `^f/^b` — page up/down +- `G/gg` — file start/end +- `f/t/F/T` — find char on line, with counts + +### Operators + Motions +- `d`, `y`, `c` combined with any motion (e.g. `c3td`, `d$`, `y2w`) +- Single unnamed buffer for yank/delete +- `p/P` — paste after/before + +### Other +- `.` — repeat last command +- `u` — undo +- `:w`, `:q`, `:wq`, `:e` — file commands +- `:/regex` — search +- `:%s/pat/rep/g` — substitution + +### Out of Scope +- Macros, named registers, splits, tabs + +## Language Support +- C +- Assembly + +## Ollama Integration + +### Models (user-configurable) +- gemma2:2b +- deepseek-r1:1.5b +- qwen2.5-coder:3b +- gemma3:4b + +### Completion Behavior +- Idle timer in insert mode (~300ms) triggers completion request +- Tab to accept ghost text (rendered dim) +- Context sent: current function/block (enclosing `{}`) + current line +- Endpoint: `POST http://localhost:11434/api/generate` (streaming JSON) + +## Architecture + +``` +src/ + main.c — entry point, arg parsing + terminal.c — raw mode, ncurses screen management + buffer.c — gap buffer or piece table, line indexing + editor.c — editor state, viewport, cursor + input.c — keypress reading, modal dispatch + normal.c — normal mode commands, operator-motion parsing + insert.c — insert mode, char input, trigger completion + command.c — : command line parsing and execution + search.c — regex search (POSIX regex.h) + undo.c — undo list + ollama.c — HTTP client (libcurl), prompt building, response parsing + syntax.c — minimal scope detection for C/asm (brace matching) + config.c — runtime config (~/.editorrc or similar) +``` + +## Dependencies +- ncurses — terminal UI +- libcurl — Ollama HTTP communication +- POSIX regex.h — search/replace (libc) +- cJSON (or hand-rolled) — parse Ollama JSON responses + +## Design Decisions to Finalize +- [ ] Text buffer: gap buffer (simpler) vs piece table (better for large files) +- [ ] Visual mode: include char/line visual mode? +- [ ] Config file format and location diff --git a/ved/desc.txt b/ved/desc.txt new file mode 100644 index 0000000..f7e4b84 --- /dev/null +++ b/ved/desc.txt @@ -0,0 +1 @@ +Make a hello world in C diff --git a/ved/src/buffer.c b/ved/src/buffer.c new file mode 100644 index 0000000..c0c911a --- /dev/null +++ b/ved/src/buffer.c @@ -0,0 +1,157 @@ +#define _GNU_SOURCE +#include "editor.h" +#include +#include +#include + +void buf_init(Buffer *b) +{ + b->lines = NULL; + b->numlines = 0; + b->cap = 0; + b->filename = NULL; + b->dirty = 0; +} + +void buf_free(Buffer *b) +{ + for (int i = 0; i < b->numlines; i++) + free(b->lines[i].chars); + free(b->lines); + free(b->filename); +} + +static void line_ensure(Line *l, int need) +{ + if (need <= l->cap) return; + l->cap = need * 2; + l->chars = realloc(l->chars, l->cap); +} + +static void buf_ensure_lines(Buffer *b, int need) +{ + if (need <= b->cap) return; + b->cap = need * 2; + b->lines = realloc(b->lines, b->cap * sizeof(Line)); +} + +int buf_load(Buffer *b, const char *filename) +{ + FILE *f = fopen(filename, "r"); + if (!f) { + /* new file: start with one empty line */ + buf_ensure_lines(b, 1); + b->lines[0] = (Line){NULL, 0, 0}; + b->numlines = 1; + b->filename = strdup(filename); + return 0; + } + b->filename = strdup(filename); + + char *line = NULL; + size_t linecap = 0; + ssize_t len; + while ((len = getline(&line, &linecap, f)) != -1) { + /* strip trailing newline */ + while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r')) + len--; + buf_ensure_lines(b, b->numlines + 1); + Line *l = &b->lines[b->numlines]; + l->len = (int)len; + l->cap = (int)len + 1; + l->chars = malloc(l->cap); + memcpy(l->chars, line, len); + l->chars[len] = '\0'; + b->numlines++; + } + free(line); + fclose(f); + + if (b->numlines == 0) { + buf_ensure_lines(b, 1); + b->lines[0] = (Line){NULL, 0, 0}; + b->numlines = 1; + } + return 0; +} + +int buf_save(Buffer *b) +{ + if (!b->filename) return -1; + FILE *f = fopen(b->filename, "w"); + if (!f) return -1; + for (int i = 0; i < b->numlines; i++) { + if (b->lines[i].len > 0) + fwrite(b->lines[i].chars, 1, b->lines[i].len, f); + fputc('\n', f); + } + fclose(f); + b->dirty = 0; + return 0; +} + +int buf_line_len(Buffer *b, int row) +{ + if (row < 0 || row >= b->numlines) return 0; + return b->lines[row].len; +} + +void buf_insert_char(Buffer *b, int row, int col, int c) +{ + Line *l = &b->lines[row]; + line_ensure(l, l->len + 2); + memmove(l->chars + col + 1, l->chars + col, l->len - col); + l->chars[col] = (char)c; + l->len++; + l->chars[l->len] = '\0'; + b->dirty = 1; +} + +void buf_delete_char(Buffer *b, int row, int col) +{ + Line *l = &b->lines[row]; + if (col >= l->len) return; + memmove(l->chars + col, l->chars + col + 1, l->len - col - 1); + l->len--; + if (l->chars) l->chars[l->len] = '\0'; + b->dirty = 1; +} + +void buf_insert_line(Buffer *b, int at) +{ + buf_ensure_lines(b, b->numlines + 1); + memmove(b->lines + at + 1, b->lines + at, + (b->numlines - at) * sizeof(Line)); + b->lines[at] = (Line){NULL, 0, 0}; + b->numlines++; + b->dirty = 1; +} + +void buf_break_line(Buffer *b, int row, int col) +{ + Line *cur = &b->lines[row]; + int tail = cur->len - col; + + buf_insert_line(b, row + 1); + Line *newl = &b->lines[row + 1]; + if (tail > 0) { + line_ensure(newl, tail + 1); + memcpy(newl->chars, cur->chars + col, tail); + newl->len = tail; + newl->chars[tail] = '\0'; + } + /* cur pointer may be invalidated by insert_line, re-fetch */ + cur = &b->lines[row]; + cur->len = col; + if (cur->chars) cur->chars[col] = '\0'; + b->dirty = 1; +} + +void buf_delete_line(Buffer *b, int at) +{ + free(b->lines[at].chars); + memmove(b->lines + at, b->lines + at + 1, + (b->numlines - at - 1) * sizeof(Line)); + b->numlines--; + b->dirty = 1; +} diff --git a/ved/src/buffer.o b/ved/src/buffer.o new file mode 100644 index 0000000000000000000000000000000000000000..2586bf75bc6981d80be6ba8483e66b1c2820faf5 GIT binary patch literal 5704 zcmbtXTWlLu8a}pLnhi;7vZa)ph(NHwwpJ=yXrUlC(X{i>(5kJ4DzS1MXR?iIJC!|# z0!gSaMGT{e3JIwK9xBQM7YTtts7R=ru-ZD^hmuwm%0qW$5L>z1NCaUa?-B^#e=g(W zu?+}LG;_}R{>%B#e;em%WBb-%Ai&fJu!mS}r%=Y){#aW_`7+8nSqF=m&VNj2tZKSO z)hu!XEsN1MT8m|eTAIaZSgniE)wDeNm%norg}H91DHn|eLa*Eg$+GD(H@09V&znuLOQpil+uEJQE<@bU*eTqUzw7rXqiv-X;M08|iicuAnX&{`P zv`Y61BXRc5@JTx6@{8c$$2KyUMPr6|Fwb({f6=CpE(KsviH)y;9DEronT7ub5XBWA zvCZxFznh?)HtaN}5z~pC6y0gz`ImoGcL+~&_(n+`O=Ah#cekp}qctjEI9Rdz#-{w}}pkmCcF%S#tsx(*zwz;L3|Djs%6< zK^5r=Wo2zm>W)n2Hv+DO>fImIHGeTwJSMQdI%wY0!4tz?%FDt_^KL38eyWQqz*+4l zVbbE|&nPy$v;a4xP!;A^nc_VG-l9>Vm|n>WA=G1~R*(D^>B zMLbQ;hUH#{l6q*Un$GFJwPP*IyHYFf%IGtKq2^23Jh&g^okdv}#wzTPtYLJ9%IGrA zbLBd&f%-|k{a*L!A%{8nubf!P>A&b*>8sjpsrE<y!c znPs?ws<+mTc<9AVAk6(AYkrMQMrrJ6c@#`9lmp6o-Pnos?e$k`u%3H_+Dy$-hMcok z5%jZGw{D}d1xO#i^ncCA6syTXw@AX z!_eSxN}X9Jf5C~(;P6?l`9fXZ%mYUwLVnCR#+++h5G`s}`?Ayt7Cwd5;**D@D~!#t z@kd-^PDn|Ytoh&KA}~>9IG~NXED}gLz;|CaHdjj{lA~M`Db(8H*o=^YdLGa&x(5O( zk5cjt9tp!E>Lc6WXlk}zC5x)5h}3HqR4NzVIMlslV4DWDArWhqW!dA5ZR_j1zx&SE zKt5yVyYGu^i1e(zC(n25#@B6(^sK*2ps1&ncxVKk+{FTi!hst*mbD#4q1^enDwz9n`Zcq7WgB;>D-u%5zj$j`&#hR`OWmFTi|vJ{HYdrp#}az z3;b2!WdG3+78txnFu#GPnVs*nz~2W>dY+Z3!fOfGDTz->oYxSrvv`{6|6KCBGF5m@ z0lOseJ{fvmOTfO9_=3cF4FOwtF{f#LD&u#M?p z-jwx&L)$m(2T=!2z!>RWv{V!6anm`zU|c@IJZU=fl|H1hOC2;+H~9P~ScWR<$rM8voQ58<+`2;!rx7!@FA|M_cx6YFo4 zUk6#zspl}ZOr1}6(}(|j`h7xJkIsbkoV5RLAFkxp+zYx|>#VE(pzmsHKQH~RA9zAYm&o5>%ij}AscOy1is{}ZGdbGrZl literal 0 HcmV?d00001 diff --git a/ved/src/command.c b/ved/src/command.c new file mode 100644 index 0000000..cb7fbbc --- /dev/null +++ b/ved/src/command.c @@ -0,0 +1,61 @@ +#include "editor.h" +#include +#include +#include + +static char cmdbuf[256]; +static int cmdlen = 0; + +void command_process(Editor *e, int c) +{ + switch (c) { + case 27: /* ESC */ + cmdlen = 0; + e->mode = MODE_NORMAL; + editor_set_status(e, ""); + break; + case '\r': + case '\n': + case KEY_ENTER: + cmdbuf[cmdlen] = '\0'; + if (strcmp(cmdbuf, "q") == 0 || strcmp(cmdbuf, "q!") == 0) { + buf_free(&e->buf); + term_end(); + _exit(0); + } else if (strcmp(cmdbuf, "w") == 0) { + if (buf_save(&e->buf) == 0) + editor_set_status(e, "\"%s\" written", e->buf.filename); + else + editor_set_status(e, "Error writing file"); + } else if (strcmp(cmdbuf, "wq") == 0 || strcmp(cmdbuf, "wq!") == 0 || + strcmp(cmdbuf, "x") == 0) { + buf_save(&e->buf); + buf_free(&e->buf); + term_end(); + _exit(0); + } else { + editor_set_status(e, "Unknown command: %s", cmdbuf); + } + cmdlen = 0; + e->mode = MODE_NORMAL; + break; + case KEY_BACKSPACE: + case 127: + if (cmdlen > 0) { + cmdlen--; + cmdbuf[cmdlen] = '\0'; + editor_set_status(e, ":%s", cmdbuf); + } else { + e->mode = MODE_NORMAL; + editor_set_status(e, ""); + } + break; + default: + if (c >= 32 && c < 127 && cmdlen < (int)sizeof(cmdbuf) - 1) { + cmdbuf[cmdlen++] = (char)c; + cmdbuf[cmdlen] = '\0'; + editor_set_status(e, ":%s", cmdbuf); + } + break; + } +} diff --git a/ved/src/command.o b/ved/src/command.o new file mode 100644 index 0000000000000000000000000000000000000000..3b4f9cc85099bdb4dac5e595527dd71ef6c9fb53 GIT binary patch literal 3912 zcmbW3Uuaup6u?iCy3X0IaVkzaG25(jYA-F@uokLmOWKH?owcQVP(qsAc5zM8CO5WK z)`(eUArva}VK^VA_%PVp1QkIn2u6oseeun=35)t7^dZ7#J?Gx@rKcbFD(tl1{m%FM z&N<)t{@t5mWMs(i^MMi{JP+F~MFDoVZ0liL4MQumz(lnCTeS3NFj_uWFBdi|kcW5XSLOyr16OX~Wh?B#rjCaL1fRdwBh z+D)`}mU?Ac}4oE#y*`Wo96f%Vl$RE0viE zO{Zo}SeVEEgRlq_nX{SfLMD{Rrql6EvNzO`hh8i&JUG}JdU9f_kg*D(XN?m^SLd;U z-RfTM?lHQKA9biR{)3!`@2xTLEe3te{K@MNV?pIz$ZBkh_Vlh&ABZ-1JJnfwyk;upjoLq>HxUU zF95rC*iqvVEc?W~?6pF66iX0`E-xT(S{{1^#Hq?1#HX{u)N z>@knc<+2GgpNH7+$ml@-NNjXy=+(%0Y`lM9BoYHNnX6n>GLd;xDSvJqd-!w5DTTXIKM~wEY&g~h&X_szqIbty<+g8Dg%vGG* z+%D^!<9r}E#lOS+vfwm^8utowVtWO@#+=5bI2>l{y5KU-7tHlIRl(a(SL6ODIGr2C ze-ZqC!GC40@}ZjKrc*&(TS_x*F``u=_`IOX3-Nub;iapZn&2z$y^-N)ZGuI|^S2fyRN1AHJG zdD_|FUDi3D&jTL(u;4VWevZ?{oN|}*dP%ca=jXJC{j9LJ_m|@@dGL2V`1?BNd3~sJ zj{m97+5R&R{*~Zz9jeUrbqMh|)XgRiG4e}kD?SAVzQCROjM8%EY}~NSMGK5%+=_!S zh2H}smvtKSp)fPpm&sbDG5qpGCw>VMXVHM%%rp2Ih^HIxdmjF-s}Oq9(bB)fVPqug z`|GqETcuOoj)m4Fj77?R4mig@74~2!jiY`E>}TUAnCrh-+H}D7d=0n-@C<4cQ#TJW zXwn=)4Nphu#)?dK>>ak1|BH@U9L3OVf+`gt$|; zon(JK|4Z!OBZH!@)?eW +#include +#include +#include + +static char statusmsg[256]; + +static void load_desc(Editor *e) +{ + e->desc[0] = '\0'; + if (!e->buf.filename) return; + char tmp[512]; + strncpy(tmp, e->buf.filename, sizeof(tmp) - 1); + tmp[sizeof(tmp) - 1] = '\0'; + char *dir = dirname(tmp); + char path[512]; + snprintf(path, sizeof(path), "%s/desc.txt", dir); + FILE *f = fopen(path, "r"); + if (!f) return; + int pos = 0; + int c; + while ((c = fgetc(f)) != EOF && pos < (int)sizeof(e->desc) - 1) + e->desc[pos++] = (char)c; + e->desc[pos] = '\0'; + fclose(f); +} + +void editor_init(Editor *e) +{ + buf_init(&e->buf); + e->cx = e->cy = 0; + e->rowoff = e->coloff = 0; + e->mode = MODE_NORMAL; + e->ft = FT_NONE; + e->ghost[0] = '\0'; + e->idle_count = 0; + e->nano_mode = 0; + e->cutbuf = (Line){NULL, 0, 0}; + e->desc[0] = '\0'; + e->autocomplete_row = -1; + e->autocomplete_col = 0; + getmaxyx(stdscr, e->screenrows, e->screencols); + e->screenrows -= 2; /* reserve status + command line */ + statusmsg[0] = '\0'; +} + +void editor_open(Editor *e, const char *filename) +{ + buf_load(&e->buf, filename); + e->ft = syntax_detect(filename); + load_desc(e); +} + +void editor_scroll(Editor *e) +{ + 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->cx < e->coloff) + e->coloff = e->cx; + if (e->cx >= e->coloff + e->screencols) + e->coloff = e->cx - e->screencols + 1; +} + +void editor_set_status(Editor *e, const char *fmt, ...) +{ + (void)e; + va_list ap; + va_start(ap, fmt); + vsnprintf(statusmsg, sizeof(statusmsg), fmt, ap); + va_end(ap); +} + +void editor_draw(Editor *e) +{ + editor_scroll(e); + erase(); + + if (e->nano_mode) { + /* nano layout: title bar row 0, content rows 1..screenrows, + status at screenrows+1, shortcut bars at screenrows+2..+3 */ + int content_start = 1; + for (int y = 0; y < e->screenrows; y++) { + int filerow = y + e->rowoff; + int sy = y + content_start; + if (filerow < e->buf.numlines) { + Line *l = &e->buf.lines[filerow]; + int len = l->len - e->coloff; + if (len < 0) len = 0; + if (len > e->screencols) len = e->screencols; + if (len > 0 && l->chars) { + if (e->ft != FT_NONE) { + int bc = syntax_line_in_block_comment( + &e->buf, filerow, e->ft); + syntax_draw_line(l->chars + e->coloff, len, + sy, 0, e->screencols, e->ft, bc); + } else { + mvaddnstr(sy, 0, l->chars + e->coloff, len); + } + } + } + } + + /* ghost text */ + if (e->ghost[0] != '\0') { + int gy = e->cy - e->rowoff + content_start; + int gx = e->cx - e->coloff; + if (gy > 0 && gy <= e->screenrows && gx >= 0) { + attron(A_DIM); + int glen = (int)strlen(e->ghost); + if (gx + glen > e->screencols) glen = e->screencols - gx; + if (glen > 0) mvaddnstr(gy, gx, e->ghost, glen); + attroff(A_DIM); + } + } + + /* status message line */ + if (statusmsg[0]) + mvaddnstr(e->screenrows + 1, 0, statusmsg, e->screencols); + + /* nano title bar + shortcut bars */ + nano_draw_bars(e); + + /* cursor */ + move(e->cy - e->rowoff + content_start, e->cx - e->coloff); + } else { + /* vi layout */ + for (int y = 0; y < e->screenrows; y++) { + int filerow = y + e->rowoff; + if (filerow < e->buf.numlines) { + Line *l = &e->buf.lines[filerow]; + int len = l->len - e->coloff; + if (len < 0) len = 0; + if (len > e->screencols) len = e->screencols; + if (len > 0 && l->chars) { + if (e->ft != FT_NONE) { + int bc = syntax_line_in_block_comment( + &e->buf, filerow, e->ft); + syntax_draw_line(l->chars + e->coloff, len, + y, 0, e->screencols, e->ft, bc); + } else { + mvaddnstr(y, 0, l->chars + e->coloff, len); + } + } + } else { + mvaddch(y, 0, '~'); + } + } + + /* ghost text */ + if (e->mode == MODE_INSERT && e->ghost[0] != '\0') { + int gy = e->cy - e->rowoff; + int gx = e->cx - e->coloff; + if (gy >= 0 && gy < e->screenrows && gx >= 0) { + attron(A_DIM); + int glen = (int)strlen(e->ghost); + if (gx + glen > e->screencols) glen = e->screencols - gx; + if (glen > 0) mvaddnstr(gy, gx, e->ghost, glen); + attroff(A_DIM); + } + } + + /* status bar */ + attron(A_REVERSE); + char status[256]; + const char *modestr = e->mode == MODE_INSERT ? "-- INSERT --" : + e->mode == MODE_COMMAND ? ":" : ""; + const char *fname = e->buf.filename ? e->buf.filename : "[No Name]"; + int slen = snprintf(status, sizeof(status), " %s %s%s", + modestr, fname, e->buf.dirty ? " [+]" : ""); + char rinfo[64]; + int rlen = snprintf(rinfo, sizeof(rinfo), "%d/%d ", + e->cy + 1, e->buf.numlines); + for (int i = slen; i < e->screencols - rlen; i++) + status[i] = ' '; + memcpy(status + e->screencols - rlen, rinfo, rlen); + status[e->screencols] = '\0'; + mvaddnstr(e->screenrows, 0, status, e->screencols); + attroff(A_REVERSE); + + /* message/command line */ + mvaddnstr(e->screenrows + 1, 0, statusmsg, e->screencols); + + /* cursor */ + move(e->cy - e->rowoff, e->cx - e->coloff); + } + + refresh(); +} + +void editor_prompt_desc(Editor *e) +{ + int row = e->nano_mode ? e->screenrows + 1 : e->screenrows + 1; + char buf[sizeof(e->desc)]; + int len = 0; + + /* pre-fill with current desc */ + if (e->desc[0]) { + len = (int)strlen(e->desc); + memcpy(buf, e->desc, len); + } + buf[len] = '\0'; + + editor_set_status(e, "Desc: %s", buf); + editor_draw(e); + move(row, 6 + len); + refresh(); + + for (;;) { + int c = getch(); + if (c == '\r' || c == '\n' || c == KEY_ENTER) break; + if (c == 27) { return; } /* ESC cancels */ + if ((c == KEY_BACKSPACE || c == 127) && len > 0) { + buf[--len] = '\0'; + } else if (c >= 32 && c < 127 && len < (int)sizeof(buf) - 1) { + buf[len++] = (char)c; + buf[len] = '\0'; + } + editor_set_status(e, "Desc: %s", buf); + editor_draw(e); + move(row, 6 + len); + refresh(); + } + + memcpy(e->desc, buf, len); + e->desc[len] = '\0'; + editor_set_status(e, len > 0 ? "[Description set]" : "[Description cleared]"); +} diff --git a/ved/src/editor.h b/ved/src/editor.h new file mode 100644 index 0000000..3dacd08 --- /dev/null +++ b/ved/src/editor.h @@ -0,0 +1,99 @@ +#ifndef EDITOR_H +#define EDITOR_H + +#include + +/* Modes */ +enum mode { MODE_NORMAL, MODE_INSERT, MODE_COMMAND, MODE_NANO }; + +/* File types for syntax highlighting */ +enum filetype { FT_NONE, FT_C, FT_ASM, FT_PYTHON, FT_BASH }; + +/* Line in the buffer */ +typedef struct { + char *chars; + int len; + int cap; +} Line; + +/* Text buffer */ +typedef struct { + Line *lines; + int numlines; + int cap; + char *filename; + int dirty; +} Buffer; + +/* Editor state */ +typedef struct { + Buffer buf; + int cx, cy; /* cursor position in buffer */ + int rowoff, coloff; /* viewport scroll offsets */ + int screenrows, screencols; + enum mode mode; + enum filetype ft; + char ghost[512]; /* ghost text suggestion from LLM */ + int idle_count; /* idle ticks in insert mode */ + int nano_mode; /* launched with -nano */ + Line cutbuf; /* nano ^K cut buffer */ + char desc[1024]; /* project description for AI context */ + int autocomplete_row; /* row to auto-insert completion into, -1 if none */ + int autocomplete_col; /* column where completion starts */ +} Editor; + +/* buffer.c */ +void buf_init(Buffer *b); +void buf_free(Buffer *b); +int buf_load(Buffer *b, const char *filename); +int buf_save(Buffer *b); +void buf_insert_char(Buffer *b, int row, int col, int c); +void buf_delete_char(Buffer *b, int row, int col); +void buf_insert_line(Buffer *b, int at); +void buf_break_line(Buffer *b, int row, int col); +void buf_delete_line(Buffer *b, int at); +int buf_line_len(Buffer *b, int row); + +/* terminal.c */ +void term_init(void); +void term_end(void); + +/* editor.c */ +void editor_init(Editor *e); +void editor_open(Editor *e, const char *filename); +void editor_draw(Editor *e); +void editor_scroll(Editor *e); +void editor_set_status(Editor *e, const char *fmt, ...); + +/* input.c */ +void input_process(Editor *e); + +/* normal.c */ +void normal_process(Editor *e, int c); + +/* insert.c */ +void insert_process(Editor *e, int c); + +/* command.c */ +void command_process(Editor *e, int c); + +/* syntax.c */ +void syntax_init(void); +enum filetype syntax_detect(const char *filename); +void syntax_draw_line(const char *s, int len, int y, int x, + int maxcols, enum filetype ft, int in_block_comment); +int syntax_line_in_block_comment(Buffer *b, int row, enum filetype ft); + +/* ollama.c */ +int ollama_request(Editor *e); +int ollama_request_line(Editor *e, int row); +int ollama_poll(Editor *e); + +/* nano.c */ +void nano_process(Editor *e, int c); +void nano_draw_bars(Editor *e); + +/* editor.c */ +void editor_prompt_desc(Editor *e); + +#endif diff --git a/ved/src/editor.o b/ved/src/editor.o new file mode 100644 index 0000000000000000000000000000000000000000..8d071525aa8db5ca1324c2e702743efcf9bf0a52 GIT binary patch literal 10360 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 EKQ$i^3jhEB literal 0 HcmV?d00001 diff --git a/ved/src/input.c b/ved/src/input.c new file mode 100644 index 0000000..3e22508 --- /dev/null +++ b/ved/src/input.c @@ -0,0 +1,25 @@ +#include "editor.h" + +void input_process(Editor *e) +{ + timeout(100); + + int c = getch(); + + if (c == ERR) { + /* poll for async ollama result */ + if (e->mode == MODE_INSERT || e->mode == MODE_NANO) { + int r = ollama_poll(e); + if (r == 1) + editor_set_status(e, "[Tab to accept, any key to dismiss]"); + } + return; + } + + switch (e->mode) { + case MODE_NORMAL: normal_process(e, c); break; + case MODE_INSERT: insert_process(e, c); break; + case MODE_COMMAND: command_process(e, c); break; + case MODE_NANO: nano_process(e, c); break; + } +} diff --git a/ved/src/input.o b/ved/src/input.o new file mode 100644 index 0000000000000000000000000000000000000000..fe22673abc0ecf66ffaece0e83529840ae677e8d GIT binary patch literal 2312 zcmbuBUuzRV5Wx3x+Ez{fSQUke99X1kd$ibMwJ2$8+B}q2YJ(3}&P#II*yOI>ZD=dh zKg1S85%L8Dzk+W*iJ(yZ0{ZHU5Ue1A4|yomnY-P~dcAwm36q`q?ab`vW^c*kTdMp+NmbTrsZBTNZ4TyBm8}|vF~;3WvR+=R z@0Mm#o5|W{Vz^wFXoqjh?dvbyoMf$$uv1>!k=oav@rf5hZr#e(8yww5w(t8TB-#x% z?RdE!qkZMNOh>k-H`8^TuYBf3w@3D(p1tVU9#q|ys-NEDy0_-^j6y9%&t{D!+N^)Y8bQoD0Ar`HPxrS2I}X~xV#MK81RspxS0ebe2!1_+XCnB6 z2>v93S0gydn@c6CX1!5cvaPJ)IK-hjCuQLJo7r12`1zn}a3MFbz zr#ewH&{@^CJO`h=VZxkgQA3@aPWM5>vWpOaX~u$f*Vc=Mzs`Re3gqLR5quL9z)<-6 z=XqW%w{h$O#b=%X6(xZXqy>wE;2A-a-vlQRf}8I?Pq-O;4{{Rc|OXL6m literal 0 HcmV?d00001 diff --git a/ved/src/insert.c b/ved/src/insert.c new file mode 100644 index 0000000..5476546 --- /dev/null +++ b/ved/src/insert.c @@ -0,0 +1,134 @@ +#include "editor.h" +#include + +#define TAB_WIDTH 4 + +static int get_indent(Buffer *b, int row) +{ + if (row < 0 || row >= b->numlines) return 0; + char *s = b->lines[row].chars; + int len = b->lines[row].len; + int indent = 0; + while (indent < len && (s[indent] == ' ' || s[indent] == '\t')) + indent++; + return indent; +} + +static int line_ends_with(Buffer *b, int row, char c) +{ + int len = b->lines[row].len; + char *s = b->lines[row].chars; + if (!s || len == 0) return 0; + int i = len - 1; + while (i >= 0 && (s[i] == ' ' || s[i] == '\t')) i--; + return (i >= 0 && s[i] == c); +} + +void insert_process(Editor *e, int c) +{ + /* if there's a ghost suggestion and user presses Tab, accept it */ + if ((c == '\t' || c == KEY_STAB) && e->ghost[0] != '\0') { + for (int i = 0; e->ghost[i]; i++) + buf_insert_char(&e->buf, e->cy, e->cx++, e->ghost[i]); + e->ghost[0] = '\0'; + return; + } + + /* any other key clears ghost text */ + e->ghost[0] = '\0'; + + switch (c) { + case 27: /* ESC */ + e->mode = MODE_NORMAL; + if (e->cx > 0) e->cx--; + editor_set_status(e, ""); + break; + case 20: /* ^T — set description */ + editor_prompt_desc(e); + break; + case 16: /* ^P — request AI prediction */ + editor_set_status(e, "[predicting...]"); + ollama_request(e); + break; + case KEY_BACKSPACE: + case 127: + if (e->cx > 0) { + buf_delete_char(&e->buf, e->cy, e->cx - 1); + e->cx--; + } else if (e->cy > 0) { + e->cx = buf_line_len(&e->buf, e->cy - 1); + Line *prev = &e->buf.lines[e->cy - 1]; + Line *cur = &e->buf.lines[e->cy]; + if (cur->len > 0) { + for (int i = 0; i < cur->len; i++) + buf_insert_char(&e->buf, e->cy - 1, + prev->len, cur->chars[i]); + } + buf_delete_line(&e->buf, e->cy); + e->cy--; + } + break; + case '\t': + case KEY_STAB: + /* insert TAB_WIDTH spaces */ + for (int i = 0; i < TAB_WIDTH; i++) + buf_insert_char(&e->buf, e->cy, e->cx++, ' '); + break; + case KEY_BTAB: + /* shift-tab: remove up to TAB_WIDTH leading spaces */ + for (int i = 0; i < TAB_WIDTH && e->cx > 0; i++) { + if (e->buf.lines[e->cy].chars[e->cx - 1] == ' ') { + buf_delete_char(&e->buf, e->cy, e->cx - 1); + e->cx--; + } else break; + } + break; + case '\r': + case '\n': + case KEY_ENTER: { + int prev_row = e->cy; + int prev_len = buf_line_len(&e->buf, e->cy); + int indent = get_indent(&e->buf, e->cy); + if (indent > e->cx) indent = e->cx; + int extra = 0; + if (e->ft == FT_C && line_ends_with(&e->buf, e->cy, '{')) + extra = TAB_WIDTH; + if ((e->ft == FT_PYTHON || e->ft == FT_BASH) && + line_ends_with(&e->buf, e->cy, ':')) + extra = TAB_WIDTH; + + buf_break_line(&e->buf, e->cy, e->cx); + e->cy++; + e->cx = 0; + for (int i = 0; i < indent + extra; i++) + buf_insert_char(&e->buf, e->cy, e->cx++, ' '); + + /* fire autocomplete for previous line if it looks partial */ + int content_len = prev_len - indent; + if (content_len > 0 && content_len < 60 && e->ft != FT_NONE) + ollama_request_line(e, prev_row); + break; + } + case '}': + if (e->ft == FT_C) { + char *s = e->buf.lines[e->cy].chars; + int all_space = 1; + for (int i = 0; i < e->cx && s; i++) + if (s[i] != ' ' && s[i] != '\t') { all_space = 0; break; } + if (all_space && e->cx >= TAB_WIDTH) { + for (int i = 0; i < TAB_WIDTH; i++) + buf_delete_char(&e->buf, e->cy, e->cx - TAB_WIDTH); + e->cx -= TAB_WIDTH; + } + } + buf_insert_char(&e->buf, e->cy, e->cx, c); + e->cx++; + break; + default: + if (c >= 32 && c < 127) { + buf_insert_char(&e->buf, e->cy, e->cx, c); + e->cx++; + } + break; + } +} diff --git a/ved/src/insert.o b/ved/src/insert.o new file mode 100644 index 0000000000000000000000000000000000000000..6b57d508ed79dab4b5af1a804e74724bba65a323 GIT binary patch literal 4480 zcmbtWZ)_Ar6rbyr{=t?l2tiYb7d;FrTp(DXK+W-I=qg1Tq#^zx$Mw3!#RGhQ#=-AF3wCn1)nB;3(CD*x+dzXrd+!soKl=p=v)=#nks^XRh1rT|}K^ zcV>R?{ocHJ^WN&>FKxVWZ|{)NrZ!1Y>ucUT5Og?`F>gq zI;%}PbUKqQ)`Bw*EjB~C^SO36k88J>_By*{2b~1-82Icm7B%j^_VWpd?w}qW&|(AS zYWW+nfuo=m4S~yp(nYT~kyM*gbcls&r))G08gxj$AYI&Fqqy`PItFOx;8;1t< z(=P0c({9-Sn_ufA)5z245QweJL^3*r)GxeVl9^gYh?p33K%1+U68%V7*|S!gE0+@M*=)I#_!b74W?*Y`RZ`+pMp-Q-F2Epj5a7B}*ol-FfjJu@KPDwM z6eFWYN_>EdkD}r+rf7h@IzJo!4(~b)gp~Lg-kE#AK_64z9YA73*miF+zEVV>aUMr{ zBSe4>%6&CVO;52{vnb?j$a=~IvfOAsZDvI%j&MbD$(qGW8Zty1R=y?dE*67FnX@^* zyEiGF?^u}jakp;^vOdC$@ogAYrZKmCLB2LwENjzMSXw%k8|>09W}+Do%^2P?2+qM2 zW&6)QgBYkAbQmnL%95_!0C3u9EJIU<*WK5}A}Acu=rAM=vs6om&s||!jpkN!-U_M+ zzB|+JBqT*IWAw9|OtGs^X6dIt4`%~e7-re|nF~~M33?fd?6Na{D|3wTlOQ8m1^M|l zDfaDx%|M}wt0G!27Bv0&=abQB5*AK6dsc8}Z?fm@tfN1ZhEy~mw@$xfBm3TUcHs;f zR{X0!=I3=f%8k|1(fD#c1~f%);I>Qv3d<4Qz{Y0&TsjLfGl|@P&d>KI)lwiprYsPg zDqg;DRzNOZOx~Fz{{$}B$!0bc2#33`5f2(lhaGOp>jGPFhGW?Mn=>497)_%Fz2QjL z(y?sD=a^H3C8aZvT|5UxLAn3p_?xtI=^52&nQ}Ivy*P@yie$)?#U*9yT3z zX>-o_z7(BcjK?_G*LaVO>=%JXCm_2fobfx075OD)@MGy>aW@VPh|TjaX%#W&FkcquCUV57Es$lr#v3daYE|%?c3?v*wh>isZrN< zPmO2G=50|n+Zx}x!?We-Crp%()wpG7jx$Gyqr1vcT~S)r4*_2X7(UqE!`Ge_Rc{vU zt1Kyc7c@-SZp5Ko#aB*#DJOpwacJQZcnP^PSi%&TWa$gfx|sYjzf?kqGw&Y`;&8j< zX-=?YZ_5EFve;pJ18fb)#hmSllbr-UW_g;El;%Ov9|%yM<|G)1#mx=VRp8IdxN&GK zR?u&QrwRdEdk*IL@RthU?FI070lcpO{(b@c!vgrF0{G1WIB5$-l(6avkW-54YYVj~ zA(eErg%qC>YKiz>Yg1dfimxl&8Bih-;;U~symwE7@9=>GFVdsFqkHx?P#Rnt z=1YeXvI-Gn^X9PPZ)YktQziy`zRJ%RGqMt%d-G-+hA$E}>@b@I{yE1{AAjKme%pde z7W_w>oxiyK-z@r8JM$JC^GVqE@KVBcS6c9P3%<&N&suQnz8*#c(6DEa}&<+YnQ(Ao|!~UScm;K+J3oKwt+ix^4VA#fZoXnZ2E5_JY z!-FyR!eecpJYdcydA;C$jYDyxKE#zD=eYgmuMmn{PjuXzn37E}Le>Au-wIr`X+3M0 z60tt!lC{~_uLU7uTdYbjUeEo1(uNDZ==XwLjIFBm_qqRLoNxDE=KOA}Q5f6J9sw-h z`SoM5K$Fdm;Q1j}@WuI|@AkEklH`h2T!`s_kDXr*nEmVkB57O@+V^Icd)s4$=aY{= N!TtB=nC8IF{~NjyWeNZQ literal 0 HcmV?d00001 diff --git a/ved/src/main.c b/ved/src/main.c new file mode 100644 index 0000000..d8e6bde --- /dev/null +++ b/ved/src/main.c @@ -0,0 +1,73 @@ +#include "editor.h" +#include +#include +#include + +static void usage(void) +{ + printf("ved - vi editor with AI prediction\n\n" + "Usage: ved [options] [file]\n\n" + "Options:\n" + " -nano Launch in nano-style mode (no vi keybindings)\n" + " --help Show this help message\n\n" + "Vi mode keys:\n" + " hjkl Navigation\n" + " i/a/o/A Enter insert mode\n" + " dd Delete line\n" + " ZZ Save and quit\n" + " :w :q :wq Command mode\n" + " ^T Set AI project description\n" + " ^P Request AI prediction\n" + " Tab Accept AI suggestion (insert mode)\n\n" + "Nano mode keys:\n" + " ^X Exit ^O Save ^K Cut line\n" + " ^U Paste ^W Search ^T Set AI description\n" + " ^P Request AI prediction\n" + " Tab Accept AI suggestion\n\n" + "AI prediction:\n" + " Connects to Ollama (qwen2.5-coder:3b).\n" + " Host configured via ollama_host= in ~/.ahvibe.conf\n" + " Place desc.txt in the file's directory for project context.\n\n" + "Supported syntax: C (.c/.h), Assembly (.s/.S/.asm), Python (.py), Bash (.sh)\n"); +} + +int main(int argc, char *argv[]) +{ + int nano = 0; + const char *filename = NULL; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + usage(); + return 0; + } else if (strcmp(argv[i], "-nano") == 0) { + nano = 1; + } else { + filename = argv[i]; + } + } + + term_init(); + + Editor e; + editor_init(&e); + + if (nano) { + e.nano_mode = 1; + e.mode = MODE_NANO; + e.screenrows -= 2; + } + + if (filename) + editor_open(&e, filename); + else + editor_open(&e, "[No Name]"); + + for (;;) { + editor_draw(&e); + input_process(&e); + } + + term_end(); + return 0; +} diff --git a/ved/src/main.o b/ved/src/main.o new file mode 100644 index 0000000000000000000000000000000000000000..68bf12fbc428a933038ea195c81ec3b05970a439 GIT binary patch literal 3560 zcmbtX-)kI29G}azwytR#YZ0L$gI2wh-X^gY^=kE!GwDjxBs57|T9RXLZ<5{SZuhb~ zm*hlhHND~y19=hr1AOqwH>EFyLXp%L>4W%cL4=fw;Db=95WnBqnalQm6sq5{bKm*Q z=R4mY^WE9>I2a?6$E=jJ2Zzt8WO;wHv(e@%WV&H;->^ZCwj@K6Srf9O)JT?z{2= zgmCu&43D{EcbF9>aPQEgZR;ZGl`!FudaQ=!+V}ZRh)U#Stku>rUzy^{bx|_@(njs4 z2Vt2uYPWz82Y;)z8=$RhcGZPqtXvmGyFXbkU)#;Be2UOAfc`zz4MK17LB-b2V)$Fy~mBBK3k-Ml6`77iOIzo7S|k z5J+p3rd-qYD9VUgaV?uVE=4F6@M@9Kl2>3f0Sxdq&#HOHEjaF6kVMOr&5C8ToUpwm z%55j00x7Wooom_|C$tB{0DajP=ZnavbktmQ=1dVOrB1Ke>-7!*JmhlbLj(cyxiAHz zLP1!-@IEUt&S=qb8Q|y7D}2ISWYlyEbfMyKSV=F@^a6|vH0za0xPV!}!pvmDgz>Oo z-bH3{T3~_YJEAB^8&|Z`Y@xyeNo~}Y(n&KfDFc?p%7PG7=H`G4g{Fx}21!jD#R`i; zszS`1gIt##j+@MkK>;xXF*!+Tw!&48GgFj~n*k&ZytBY#rVmFHW-<}x&>H)rqHY(j zrajackNz>>*#sF@j-b8hk?C4SG%%%o)`rJOmXcU#6dRk z`cmuhUt=LdEVT?s-9i2o_jvPSyzQ5SkU0EnCvn)Roy5t`78qmhitI0PlB_t{)xt+0 zPSC1V;sicbO|lo}Jxv%+4|TwkfbWNRR2-s)fax9h_~~l5|9c(qlO6E09dN4yzR&?D zCDUz%shd!VA8+L>w13$iZ7=!KF-cxqzG2e9p4xbF-#Xgt$=D$#sE_r~VIo3Q@Xo5d=Hr zDR>lq2|*CWAC|3kHG~C!59o^jjl>oIl;nTcfv;Lj@y8{9H#R;9#uMHDd<4fHQhuEX zzAu7bi{RJ~ihoPu%I_KJ_jU*VFA@Cd$Uc8W@cj||uLzDktNc>XQP3|wN-|PWxaxDF z2h|cc^YF|4@YhzF&x)pwS0&L4CO3(mhjZ6`FPvELL1ZqhxgKZwiP0(e%tJ!}0nM15 zoAu%LQ~v+&FNF;-<>og%0~pE!S&wiQa+Qw12jRi9&cG9ub$O*+miG8(GOFsPc}|jF zk+?iYi4t-wTI2@W>i;mT;hz81z8`Ecu4Z=`2^5+(R`G+0khft(`@V467{8KN^J@(x zRAf1X4I+c^C@f@z&pH~(3?t(%Vf87^r@I_56$`GJKfGcBjY!>O@-EM|1WpL B*i8Tc literal 0 HcmV?d00001 diff --git a/ved/src/nano.c b/ved/src/nano.c new file mode 100644 index 0000000..accba59 --- /dev/null +++ b/ved/src/nano.c @@ -0,0 +1,246 @@ +#include "editor.h" +#include +#include +#include + +void nano_process(Editor *e, int c) +{ + /* Tab accepts ghost text */ + if ((c == '\t' || c == KEY_STAB) && e->ghost[0] != '\0') { + for (int i = 0; e->ghost[i]; i++) + buf_insert_char(&e->buf, e->cy, e->cx++, e->ghost[i]); + e->ghost[0] = '\0'; + return; + } + + /* any other key clears ghost text */ + e->ghost[0] = '\0'; + + switch (c) { + /* ^T set description */ + case 20: + editor_prompt_desc(e); + break; + /* ^P request AI prediction */ + case 16: + editor_set_status(e, "[predicting...]"); + ollama_request(e); + break; + + /* ^X exit */ + case 24: + if (e->buf.dirty) { + editor_set_status(e, "Save modified buffer? (Y/N)"); + editor_draw(e); + timeout(-1); + int ans = getch(); + if (ans == 'y' || ans == 'Y') { + buf_save(&e->buf); + } else if (ans != 'n' && ans != 'N') { + editor_set_status(e, "Cancelled"); + return; + } + } + buf_free(&e->buf); + term_end(); + _exit(0); + break; + + /* ^O write out */ + case 15: + if (buf_save(&e->buf) == 0) + editor_set_status(e, "[ Wrote %d lines ]", e->buf.numlines); + else + editor_set_status(e, "[ Error writing ]"); + break; + + /* ^K cut line */ + case 11: + free(e->cutbuf.chars); + e->cutbuf.chars = NULL; + e->cutbuf.len = 0; + if (e->cy < e->buf.numlines) { + Line *l = &e->buf.lines[e->cy]; + if (l->len > 0 && l->chars) { + e->cutbuf.chars = malloc(l->len + 1); + memcpy(e->cutbuf.chars, l->chars, l->len); + e->cutbuf.len = l->len; + e->cutbuf.chars[l->len] = '\0'; + } + if (e->buf.numlines > 1) { + buf_delete_line(&e->buf, e->cy); + if (e->cy >= e->buf.numlines) + e->cy = e->buf.numlines - 1; + } else { + l->len = 0; + if (l->chars) l->chars[0] = '\0'; + e->buf.dirty = 1; + } + e->cx = 0; + } + break; + + /* ^U paste */ + case 21: + if (e->cutbuf.chars && e->cutbuf.len > 0) { + buf_insert_line(&e->buf, e->cy); + Line *l = &e->buf.lines[e->cy]; + l->chars = malloc(e->cutbuf.len + 1); + memcpy(l->chars, e->cutbuf.chars, e->cutbuf.len); + l->len = e->cutbuf.len; + l->cap = e->cutbuf.len + 1; + l->chars[l->len] = '\0'; + e->buf.dirty = 1; + e->cx = 0; + } + break; + + /* ^W search (simple forward) */ + case 23: { + static char search[128]; + editor_set_status(e, "Search: "); + editor_draw(e); + int si = 0; + for (;;) { + int sc = getch(); + if (sc == '\r' || sc == '\n' || sc == KEY_ENTER) break; + if (sc == 27) { si = 0; break; } + if ((sc == KEY_BACKSPACE || sc == 127) && si > 0) si--; + else if (sc >= 32 && sc < 127 && si < (int)sizeof(search) - 1) + search[si++] = (char)sc; + search[si] = '\0'; + editor_set_status(e, "Search: %s", search); + editor_draw(e); + } + if (si > 0) { + for (int r = e->cy; r < e->buf.numlines; r++) { + char *s = e->buf.lines[r].chars; + if (!s) continue; + int start = (r == e->cy) ? e->cx + 1 : 0; + char *found = strstr(s + start, search); + if (found) { + e->cy = r; + e->cx = (int)(found - s); + editor_set_status(e, ""); + return; + } + } + editor_set_status(e, "[ Not found ]"); + } else { + editor_set_status(e, ""); + } + break; + } + + /* arrow keys */ + case KEY_UP: e->cy--; break; + case KEY_DOWN: e->cy++; break; + case KEY_LEFT: e->cx--; break; + case KEY_RIGHT: e->cx++; break; + case KEY_HOME: e->cx = 0; break; + case KEY_END: e->cx = buf_line_len(&e->buf, e->cy); break; + case KEY_PPAGE: e->cy -= e->screenrows; break; + case KEY_NPAGE: e->cy += e->screenrows; break; + + /* backspace */ + case KEY_BACKSPACE: + case 127: + if (e->cx > 0) { + buf_delete_char(&e->buf, e->cy, e->cx - 1); + e->cx--; + } else if (e->cy > 0) { + e->cx = buf_line_len(&e->buf, e->cy - 1); + Line *prev = &e->buf.lines[e->cy - 1]; + Line *cur = &e->buf.lines[e->cy]; + if (cur->len > 0) + for (int i = 0; i < cur->len; i++) + buf_insert_char(&e->buf, e->cy - 1, prev->len, cur->chars[i]); + buf_delete_line(&e->buf, e->cy); + e->cy--; + } + break; + + /* delete key */ + case KEY_DC: + if (e->cx < buf_line_len(&e->buf, e->cy)) + buf_delete_char(&e->buf, e->cy, e->cx); + else if (e->cy < e->buf.numlines - 1) { + /* join next line */ + Line *cur = &e->buf.lines[e->cy]; + Line *next = &e->buf.lines[e->cy + 1]; + if (next->len > 0) + for (int i = 0; i < next->len; i++) + buf_insert_char(&e->buf, e->cy, cur->len, next->chars[i]); + buf_delete_line(&e->buf, e->cy + 1); + } + break; + + /* enter */ + case '\r': + case '\n': + case KEY_ENTER: + buf_break_line(&e->buf, e->cy, e->cx); + e->cy++; + e->cx = 0; + break; + + /* tab */ + case '\t': + for (int i = 0; i < 4; i++) + buf_insert_char(&e->buf, e->cy, e->cx++, ' '); + break; + + /* printable chars */ + default: + if (c >= 32 && c < 127) { + buf_insert_char(&e->buf, e->cy, e->cx, c); + e->cx++; + } + break; + } + + /* clamp cursor */ + if (e->cy < 0) e->cy = 0; + if (e->cy >= e->buf.numlines) e->cy = e->buf.numlines - 1; + int len = buf_line_len(&e->buf, e->cy); + if (e->cx > len) e->cx = len; + if (e->cx < 0) e->cx = 0; +} + +void nano_draw_bars(Editor *e) +{ + /* title bar */ + attron(A_REVERSE); + char title[256]; + const char *fname = e->buf.filename ? e->buf.filename : "New Buffer"; + int tlen = snprintf(title, sizeof(title), " VED 1.0%*s%s%s", + (int)(e->screencols/2 - 10), "", fname, + e->buf.dirty ? " (modified)" : ""); + for (int i = tlen; i < e->screencols; i++) title[i] = ' '; + title[e->screencols] = '\0'; + mvaddnstr(0, 0, title, e->screencols); + attroff(A_REVERSE); + + /* shortcut bar — 2 rows at bottom */ + int r1 = e->screenrows + 2; + int r2 = e->screenrows + 3; + int col = e->screencols / 6; + + const char *keys1[] = {"^X", "^O", "^K", "^U", "^W", "^T"}; + const char *labs1[] = {"Exit", "Write", "Cut", "Paste", "Search", "Desc"}; + const char *keys2[] = {"^P", "^J", "^R", "^\\", "^_", "^G"}; + const char *labs2[] = {"Predict", "Justify", "Read", "Replace", "Go To", "Help"}; + + for (int i = 0; i < 6; i++) { + int x = i * col; + attron(A_REVERSE); + mvaddstr(r1, x, keys1[i]); + attroff(A_REVERSE); + mvaddnstr(r1, x + 2, labs1[i], col - 2); + + attron(A_REVERSE); + mvaddstr(r2, x, keys2[i]); + attroff(A_REVERSE); + mvaddnstr(r2, x + 2, labs2[i], col - 2); + } +} diff --git a/ved/src/nano.o b/ved/src/nano.o new file mode 100644 index 0000000000000000000000000000000000000000..aa75fa66a9f5126262b7a17cdce8dd638a1e5053 GIT binary patch literal 10560 zcmbta4{%h)8Q)97A0c>AQK(>fYCu4Yps2)3J^nm+A_FN9LD3w?-HXYP+=aWB1}sTn zF0s6MIlN9`8mo4!gCiqWX=@R5pdna-ov9PrqRh}bw6>NrI_hXk#!=Gm+qe7PcQ5y7 zr(L{z`~CK}-+udjyWj4+*sd;LQWOX)%wHMsoj!u!ml^+Za(&|p&c@%Z@G(RUaod|_k z?rf=J1Pztu+o*o<%>=-*1r7`W7rw1G zG1&1T!t(*1KMI}IF*Hh4+qL;69GQg(R@*1dFXG6>k$@EQ>bp?QU4-%HkKpnoB#-1( z?3a5Vn^tpqJvLz#^ep4ZmoV}9t9Uhvg#pWnugF3J=U#D5m8$uaevJkcnp|g3^Gu=8sv3PM)xW&# zl5@`ekhELdHCSTO0;#o+qiBgh`nFdAjQ~~^Y!N;?D>S;YH<+12m6kbe|BW! z=+{j3yk@EAfe(S%mF3D2Vj2c&yO5zd-4lRb!lRnmE3GoK+*VtM7KKfW-5poRsl4rU zMW*8_GmBG@x~x21-C*Adrx8sU474qmlW?YLzT>L4=k7FdFKp9fm(b`;)sPU(s`7zb z(bm4uXqY6JAZKli5p{R$zR*P25z@@Qkb*sOe}t{$SZ+rQeb(c`77q|Qc0U0f^M+Zj zSw1m3C#S#_4iANTPBWdZoWmnwFLZ0Ch!ALjMMWZEyGw*XJ!d*$u?^2uL3TyB6d&9; z^&I#V)&Y%7VecG<>><7LpZ9p5+J!zHwypQ3TlYeu#pD7t$jdK7ZAa@~J6ZkIh9@}E`XZSB_s-qt?JQoCT9W_6cUz1KX2 zUd!-ov$M1anq2urNs&99t6>yYD1pcc%S<^7QWlOM+=Uj&Aco3KHJj^29U>Q5%&N|6 z(cf#w{>pbH#!2nh(yje)GVFGVR;avE%{B)eO$w}qfe}3Sn(97F-DmH>e%#uQ*X_h* zs%Jpq8D=>9p*jn@K(ngPKzRGL8NHg>@9b-{N|DAndfXJBdmNmUO{-a0AWptfv%#*L zF-Kn_!Tt=6ze}}e+;*62-geDx_O!{dVG_A&+NRKcO0Lq==Jr!^*ST$STaR%eH=nv& zm3k=m9vu171J1U#LbJh_wiD2{!#<<{CG94pkkZ-Hwpi+g+mMsBEKMf(;oF$f_C=sSHXu4FcF4`Pr_EOj@} zB*B9$coc~}?zw=$Z*o?WXTZY~*H@|T-gZcO$nm^85!vKjL(|;T##$#=c%d*? z9&dFIc86HN{keC@&UP$;DW}f|@Cbg*ZT3y?2YlR!iQYM#wEeY&`Z!Mj-* zE^+h7;MYS#G0iHOIvq)-Iyk3CoUX59H+U;40odMHFc{J`D^v#8J_84r^1vu@AZljk z^ou;XV-vr~8<&YTmt^hXt*hpmr;g>O1b%z!%k+nEpUUvAnIoT~hV)0HtL`)3T5cb- zaOh_Ed3qGzMBO7sOTSrWc8-S*7XLOoOQAk6!QxM?`$WeG)TX4li~+Rmnp??%ZST5> zLxza)!(Dm+9V1f9;E-{n>`^sW@U%>ZTTGK}o`PX6gxsI*DKa}J;zXrM02P~^lL@5F zeSQCnFU~YiUii>Fd2DdXj9xf;I~8QuUIA~<&XeZlNufMR%af||q;+}HhCHb*Pio4O zw&Y3M^Q4`5QZ`TO&XbJl578WLcSs@a-;yG%T2#Y;BNvt*+n9CaNIIK$;;JZJ$MB zaRV|{MZ$(cgWPO!TQgM`>1Z3=(f*r(q<%3KT#BxF!={pTmB0SKPi6QZg{Q(}ZO4F2 zCCvjBNFTxQjR@{q1P@5Y>R?Q}-Og#o2!5}9>o^8il=V$XJystz>f?`AR8%~|R!6?8 zD~*X*eQmuSQ#Ll&*6PW7mFW-Ox?%=f6p2Umh6X*x)+=k12}4(=#*~KoxSmoT0Z2_I z6G^2dS&zy9S*=Hs(Ym`8r!qAKSyx$+FqGOvb36u=6?%)ZkdMKX>2lN=Oi>1}&nhO6|%{o@IlGWVLYN}byT2`}$saxs|wid?H*`j8HRYg)zvqxl$^;DGARI!?6 ztY#Ifd6?CNSV)iY!H*;xD+njk7v**sRQFP&W;m5#Zt5{%5SzuD>sL@&2HVWXAMcXsu zpt9idvd4=SjT@<@i;J`4fMs(+f-?wrXopk$c?lD`yB2@LfLmk9_c-_~0-2;0Jv0!#?^yeefzD{2?El#Ut@VMUD!yoAs0-39)3Pg{6#GDwcx=l*0{roc7@}!I^DL>XA*h3Up5xO$=|0BvY&uTt6Ff+8{0j#bsRzf#vXStqp3MZubuY)=LiAig@J_!E%=Hsr{c29A8GHo-YV~4Z$xGd;-D8i6aT)LhZp{Ua(*{ zwC6SypxjCL)Sml1eATA22YmRO2|f|H(*6>0wBh3$1izQy*Ao241fN9kHE1*xY{q+l z)c*v*ss6VJPW4xcBU1WdEh>ewMBu2O`k|WObX`S>9=fhJ5k6ff9}qr{A?^Q{4?bEP z{b(oU2MLZ-+yEaelLhXz|7Lig?GpRp8Rk zBAu{%eE2Ipe5wCoAN~-*zX^6oJG1cjCM;;r6!=K|PXwp@$Kda6SddTi+w%mc>k$9o ziUs*pPjD=UhmG4yaH{`Pf>Zt1ibRL{75GT|cMzQFIq8G1!K4huYv(b6V_fLE{R83O z0Bv&I3xtnxe+)iYuEI?MMaH+&7TIJEF5|e;2VdvGe<1WcDsZ&_M)*j3juSq$|1{y3 z6aIh?|7*gZO86ys2to1s=Nf^dJ=5SL?a>Jz{qr1ru%tZr^ES+Oc<_A!f5L&kHzx72S0AZ>@yGkmcWNRc)!5MVbXyj*U4#H zWRpDj8G+C6;AaJXmj{1O;Hn4zi@;ZV@V^N>;=%tR@Pr5dMBuF+{DQzO5B`p3TITyHnPN3OR|efT4>GZf^P!$;AIht*Pqu29Q{xI zGtGy8C*f0j*8A{l37^{Y1i^2Dv1Qy9sAEAp@gvJ(QIWWO-(wZ2t&K)xBRq|y{j8I< zq}~vzF!U`3tB6I62&>o#?^zYege^o9jg5L7+T!pFR>jg4)wl5X2q0iLy)Imvj5O+U z{Qvi3yInu;Sbp$wwV+ep0yaGkb~Hj19*9aa!8!Zc+6v8am`?Or^SM? zMO<8W5#r^vXIzPU$DaiasMFj3oanz@b`(#=BhLXDb<&t3MfShSGoVLgm7@P_>_p1v zcv*!q(|$PLvcG&TgcezMNKm;BUp5L9FBk9Ih +#include +#include + +static void clamp_cursor(Editor *e) +{ + if (e->cy >= e->buf.numlines) + e->cy = e->buf.numlines - 1; + if (e->cy < 0) e->cy = 0; + int len = buf_line_len(&e->buf, e->cy); + /* in normal mode cursor can't go past last char */ + if (e->mode == MODE_NORMAL && len > 0) len--; + if (e->cx > len) e->cx = len; + if (e->cx < 0) e->cx = 0; +} + +void normal_process(Editor *e, int c) +{ + static int pending_op = 0; + static int count = 0; + + /* accumulate count prefix */ + if (c >= '1' && c <= '9' && !pending_op) { + count = count * 10 + (c - '0'); + return; + } + if (c == '0' && count > 0) { + count = count * 10; + return; + } + int n = count > 0 ? count : 1; + count = 0; + + (void)pending_op; + pending_op = 0; + + switch (c) { + /* navigation */ + case 'h': e->cx -= n; break; + case 'l': e->cx += n; break; + case 'j': e->cy += n; break; + case 'k': e->cy -= n; break; + case '0': e->cx = 0; break; + case '$': e->cx = buf_line_len(&e->buf, e->cy); break; + case 'G': e->cy = e->buf.numlines - 1; break; + case 'g': /* gg handled simply: wait for next g */ + /* for now treat single g as gg */ + e->cy = 0; e->cx = 0; break; + case 6: /* ^F */ + e->cy += e->screenrows; break; + case 2: /* ^B */ + e->cy -= e->screenrows; break; + + /* word forward */ + case 'w': { + for (int i = 0; i < n; i++) { + int len = buf_line_len(&e->buf, e->cy); + char *s = e->buf.lines[e->cy].chars; + if (e->cx < len && s) { + /* skip current word chars */ + while (e->cx < len && s[e->cx] != ' ') e->cx++; + /* skip spaces */ + while (e->cx < len && s[e->cx] == ' ') e->cx++; + } + if (e->cx >= len && e->cy < e->buf.numlines - 1) { + e->cy++; e->cx = 0; + } + } + break; + } + case 'b': { + for (int i = 0; i < n; i++) { + char *s = e->buf.lines[e->cy].chars; + if (e->cx == 0 && e->cy > 0) { + e->cy--; + e->cx = buf_line_len(&e->buf, e->cy); + s = e->buf.lines[e->cy].chars; + } + if (s) { + if (e->cx > 0) e->cx--; + while (e->cx > 0 && s[e->cx] == ' ') e->cx--; + while (e->cx > 0 && s[e->cx-1] != ' ') e->cx--; + } + } + break; + } + + /* enter insert mode */ + case 'i': + e->mode = MODE_INSERT; + editor_set_status(e, "-- INSERT --"); + break; + case 'a': + e->cx++; + e->mode = MODE_INSERT; + editor_set_status(e, "-- INSERT --"); + break; + case 'o': + buf_insert_line(&e->buf, e->cy + 1); + e->cy++; + e->cx = 0; + e->mode = MODE_INSERT; + editor_set_status(e, "-- INSERT --"); + break; + case 'O': + buf_insert_line(&e->buf, e->cy); + e->cx = 0; + e->mode = MODE_INSERT; + editor_set_status(e, "-- INSERT --"); + break; + case 'A': + e->cx = buf_line_len(&e->buf, e->cy); + e->mode = MODE_INSERT; + editor_set_status(e, "-- INSERT --"); + break; + + /* delete char under cursor */ + case 'x': + for (int i = 0; i < n; i++) { + if (buf_line_len(&e->buf, e->cy) > 0) + buf_delete_char(&e->buf, e->cy, e->cx); + } + break; + + /* delete with motion */ + case 'd': { + timeout(-1); + /* read count prefix for motion */ + int mc = 0; + int c2 = getch(); + while (c2 >= '0' && c2 <= '9') { + mc = mc * 10 + (c2 - '0'); + c2 = getch(); + } + if (mc == 0) mc = 1; + + int from = e->cy, to = e->cy; + + if (c2 == 'd') { + /* dd: delete n lines */ + to = from + n - 1; + } else if (c2 == 'G') { + /* dG or d3G: delete from here to line mc (or end) */ + if (mc > 0 && !(c2 == 'G' && mc == 1 && e->cy == 0)) + to = mc - 1; /* d3G = delete to line 3 */ + else + to = e->buf.numlines - 1; + } else if (c2 == 'g') { + int c3 = getch(); + if (c3 == 'g') { + /* dgg: delete from here to top */ + to = 0; + } else break; + } else if (c2 == 'j') { + to = from + mc; + } else if (c2 == 'k') { + to = from; from = from - mc; + } else if (c2 == '$' || c2 == '0' || c2 == 'w' || c2 == 'b') { + /* single-line delete: just delete chars, not whole lines */ + /* for now treat as delete to end/start of line */ + if (c2 == '$') { + Line *l = &e->buf.lines[e->cy]; + l->len = e->cx; + if (l->chars) l->chars[e->cx] = '\0'; + e->buf.dirty = 1; + } else if (c2 == '0') { + Line *l = &e->buf.lines[e->cy]; + if (e->cx > 0 && l->chars) { + memmove(l->chars, l->chars + e->cx, l->len - e->cx); + l->len -= e->cx; + l->chars[l->len] = '\0'; + e->cx = 0; + e->buf.dirty = 1; + } + } else if (c2 == 'w') { + /* delete word forward */ + Line *l = &e->buf.lines[e->cy]; + int end = e->cx; + for (int i = 0; i < mc && end < l->len; i++) { + while (end < l->len && l->chars[end] != ' ') end++; + while (end < l->len && l->chars[end] == ' ') end++; + } + if (end > e->cx && l->chars) { + memmove(l->chars + e->cx, l->chars + end, l->len - end); + l->len -= (end - e->cx); + l->chars[l->len] = '\0'; + e->buf.dirty = 1; + } + } + break; /* don't fall through to line deletion */ + } else { + break; /* unknown motion */ + } + + /* normalize range */ + if (from > to) { int tmp = from; from = to; to = tmp; } + if (from < 0) from = 0; + if (to >= e->buf.numlines) to = e->buf.numlines - 1; + + /* delete line range */ + for (int i = to; i >= from; i--) { + if (e->buf.numlines > 1) { + buf_delete_line(&e->buf, i); + } else { + Line *l = &e->buf.lines[0]; + l->len = 0; + if (l->chars) l->chars[0] = '\0'; + e->buf.dirty = 1; + } + } + e->cy = from; + e->cx = 0; + break; + } + + /* command mode */ + case ':': + e->mode = MODE_COMMAND; + editor_set_status(e, ":"); + break; + + /* ZZ — save and quit */ + case 'Z': { + timeout(-1); /* block for next key */ + int c2 = getch(); + if (c2 == 'Z') { + buf_save(&e->buf); + buf_free(&e->buf); + term_end(); + _exit(0); + } + break; + } + } + + clamp_cursor(e); +} diff --git a/ved/src/normal.o b/ved/src/normal.o new file mode 100644 index 0000000000000000000000000000000000000000..3d5854221dcee2a62edd96e95da1f191066b9f47 GIT binary patch literal 9664 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 bEyM@vk^ko7$7MQ|{%MMTdtOsjxc +#include +#include +#include +#include + +#define OLLAMA_DEFAULT_HOST "http://localhost:11434" +#define OLLAMA_MODEL "qwen2.5-coder:3b" +#define CONTEXT_LINES 20 +#define LOGFILE "/tmp/ved_ollama.log" + +static char ollama_url[512]; +static FILE *logfp; + +/* async state */ +static pthread_t worker_tid; +static volatile int worker_running; +static char worker_result[512]; +static volatile int worker_done; + +static void dbglog(const char *fmt, ...) +{ + if (!logfp) logfp = fopen(LOGFILE, "a"); + if (!logfp) return; + va_list ap; + va_start(ap, fmt); + vfprintf(logfp, fmt, ap); + va_end(ap); + fflush(logfp); +} + +static void load_config(void) +{ + char path[256]; + snprintf(path, sizeof(path), "%s/.ahvibe.conf", getenv("HOME")); + FILE *f = fopen(path, "r"); + char host[256] = ""; + if (f) { + char line[512]; + while (fgets(line, sizeof(line), f)) { + if (strncmp(line, "ollama_host=", 12) == 0) { + char *v = line + 12; + int len = (int)strlen(v); + while (len > 0 && (v[len-1] == '\n' || v[len-1] == '\r')) + len--; + if (len > 0) { memcpy(host, v, len); host[len] = '\0'; } + } + } + fclose(f); + } + if (host[0]) + snprintf(ollama_url, sizeof(ollama_url), "%s/api/generate", host); + else + snprintf(ollama_url, sizeof(ollama_url), "%s/api/generate", OLLAMA_DEFAULT_HOST); + dbglog("ollama url: %s\n", ollama_url); +} + +typedef struct { char *data; size_t len; } RespBuf; + +static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *ud) +{ + RespBuf *rb = ud; + size_t total = size * nmemb; + char *tmp = realloc(rb->data, rb->len + total + 1); + if (!tmp) return 0; + rb->data = tmp; + memcpy(rb->data + rb->len, ptr, total); + rb->len += total; + rb->data[rb->len] = '\0'; + return total; +} + +static int hex2int(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static int extract_response(const char *json, char *out, int outsize) +{ + int pos = 0; + const char *p = json; + while ((p = strstr(p, "\"response\":\"")) != NULL) { + p += 12; + while (*p && *p != '"' && pos < outsize - 1) { + if (*p == '\\' && *(p+1)) { + p++; + switch (*p) { + case 'n': out[pos++] = '\n'; break; + case 't': out[pos++] = '\t'; break; + case '\\': out[pos++] = '\\'; break; + case '"': out[pos++] = '"'; break; + case '/': out[pos++] = '/'; break; + case 'u': + if (p[1] && p[2] && p[3] && p[4] && + hex2int(p[1]) >= 0 && hex2int(p[2]) >= 0 && + hex2int(p[3]) >= 0 && hex2int(p[4]) >= 0) { + int cp = (hex2int(p[1]) << 12) | (hex2int(p[2]) << 8) | + (hex2int(p[3]) << 4) | hex2int(p[4]); + p += 4; + if (cp < 0x80) { + out[pos++] = (char)cp; + } else if (cp < 0x800 && pos + 1 < outsize) { + out[pos++] = (char)(0xC0 | (cp >> 6)); + out[pos++] = (char)(0x80 | (cp & 0x3F)); + } else if (pos + 2 < outsize) { + out[pos++] = (char)(0xE0 | (cp >> 12)); + out[pos++] = (char)(0x80 | ((cp >> 6) & 0x3F)); + out[pos++] = (char)(0x80 | (cp & 0x3F)); + } + } else { + out[pos++] = 'u'; + } + break; + default: out[pos++] = *p; break; + } + } else { + out[pos++] = *p; + } + p++; + } + if (*p == '"') p++; + } + out[pos] = '\0'; + return pos; +} + +static char *json_escape(const char *s) +{ + size_t len = strlen(s); + char *out = malloc(len * 6 + 1); + if (!out) return NULL; + int j = 0; + for (size_t i = 0; i < len; i++) { + unsigned char c = (unsigned char)s[i]; + switch (c) { + case '"': out[j++] = '\\'; out[j++] = '"'; break; + case '\\': out[j++] = '\\'; out[j++] = '\\'; break; + case '\n': out[j++] = '\\'; out[j++] = 'n'; break; + case '\r': out[j++] = '\\'; out[j++] = 'r'; break; + case '\t': out[j++] = '\\'; out[j++] = 't'; break; + default: + if (c < 0x20) { + j += sprintf(out + j, "\\u%04x", c); + } else { + out[j++] = c; + } + break; + } + } + out[j] = '\0'; + return out; +} + +static void clean_response(char *full, char *ghost, int ghostsize) +{ + char *p = full; + dbglog("raw response: [%s]\n", full); + + /* skip markdown fences */ + if (strncmp(p, "```", 3) == 0) { + p += 3; + while (*p && *p != '\n') p++; + if (*p == '\n') p++; + } + /* remove trailing ``` */ + char *bt = strstr(p, "```"); + if (bt) *bt = '\0'; + /* remove FIM/special tokens */ + for (char *t = p; *t; t++) { + if (*t == '<' && (strncmp(t, "<|fim", 5) == 0 || + strncmp(t, "<|end", 5) == 0 || + strncmp(t, "<|im", 4) == 0)) { + *t = '\0'; break; + } + } + /* first line only */ + char *nl = strchr(p, '\n'); + if (nl) *nl = '\0'; + + /* trim trailing whitespace */ + int plen = (int)strlen(p); + while (plen > 0 && (p[plen-1] == ' ' || p[plen-1] == '\t')) + p[--plen] = '\0'; + + dbglog("cleaned: [%s]\n", p); + strncpy(ghost, p, ghostsize - 1); + ghost[ghostsize - 1] = '\0'; +} + +/* thread argument: the POST body */ +typedef struct { char *body; } WorkerArg; + +static void *worker_fn(void *arg) +{ + WorkerArg *wa = arg; + worker_result[0] = '\0'; + + CURL *curl = curl_easy_init(); + if (!curl) { free(wa->body); free(wa); worker_done = 1; return NULL; } + + RespBuf rb = {NULL, 0}; + struct curl_slist *hdrs = curl_slist_append(NULL, "Content-Type: application/json"); + + curl_easy_setopt(curl, CURLOPT_URL, ollama_url); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, wa->body); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rb); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3L); + + dbglog("POST %s\nbody: %.200s...\n", ollama_url, wa->body); + CURLcode res = curl_easy_perform(curl); + dbglog("curl result: %d\n", res); + + curl_slist_free_all(hdrs); + curl_easy_cleanup(curl); + + if (res == CURLE_OK && rb.data) { + dbglog("response: %.300s\n", rb.data); + char full[512]; + extract_response(rb.data, full, sizeof(full)); + clean_response(full, worker_result, sizeof(worker_result)); + } else { + dbglog("curl error: %s\n", curl_easy_strerror(res)); + } + + free(rb.data); + free(wa->body); + free(wa); + worker_done = 1; + return NULL; +} + +int ollama_request(Editor *e) +{ + static int loaded = 0; + if (!loaded) { load_config(); loaded = 1; } + + /* don't start if already running */ + if (worker_running) return -1; + + e->autocomplete_row = -1; /* ghost text mode */ + + /* build prompt */ + char *prompt = NULL; + size_t plen = 0; + FILE *f = open_memstream(&prompt, &plen); + if (!f) return -1; + + fprintf(f, "<|fim_prefix|>"); + + /* inject project description as a comment for context */ + if (e->desc[0]) { + const char *cs = (e->ft == FT_ASM) ? "; " : + (e->ft == FT_PYTHON || e->ft == FT_BASH) ? "# " : "// "; + fprintf(f, "%sProject: %s\n", cs, e->desc); + } + + int start = e->cy - CONTEXT_LINES; + if (start < 0) start = 0; + for (int i = start; i < e->cy; i++) { + Line *l = &e->buf.lines[i]; + if (l->chars && l->len > 0) fwrite(l->chars, 1, l->len, f); + fputc('\n', f); + } + Line *cur = &e->buf.lines[e->cy]; + if (cur->chars && e->cx > 0) fwrite(cur->chars, 1, e->cx, f); + fprintf(f, "<|fim_suffix|>"); + if (cur->chars && e->cx < cur->len) + fwrite(cur->chars + e->cx, 1, cur->len - e->cx, f); + fputc('\n', f); + int end = e->cy + 1 + CONTEXT_LINES; + if (end > e->buf.numlines) end = e->buf.numlines; + for (int i = e->cy + 1; i < end; i++) { + Line *l = &e->buf.lines[i]; + if (l->chars && l->len > 0) fwrite(l->chars, 1, l->len, f); + fputc('\n', f); + } + fprintf(f, "<|fim_middle|>"); + fclose(f); + + char *escaped = json_escape(prompt); + free(prompt); + if (!escaped) return -1; + + char *body = NULL; + if (asprintf(&body, + "{\"model\":\"%s\",\"prompt\":\"%s\"," + "\"raw\":true,\"stream\":false," + "\"options\":{\"num_predict\":64,\"temperature\":0.2," + "\"stop\":[\"\\n\\n\",\"<|fim\",\"<|end\",\"<|im_end\"]}}", + OLLAMA_MODEL, escaped) < 0) { + free(escaped); + return -1; + } + free(escaped); + + WorkerArg *wa = malloc(sizeof(*wa)); + wa->body = body; + + worker_done = 0; + worker_running = 1; + pthread_create(&worker_tid, NULL, worker_fn, wa); + pthread_detach(worker_tid); + return 0; +} + +int ollama_poll(Editor *e) +{ + if (!worker_running) return -1; + if (!worker_done) return 0; /* still working */ + + worker_running = 0; + if (worker_result[0] == '\0') return -1; + + /* autocomplete mode: insert directly into the target line */ + if (e->autocomplete_row >= 0 && e->autocomplete_row < e->buf.numlines) { + int row = e->autocomplete_row; + int col = e->autocomplete_col; + for (int i = 0; worker_result[i]; i++) + buf_insert_char(&e->buf, row, col++, worker_result[i]); + e->autocomplete_row = -1; + return 1; + } + + /* ghost text mode */ + strncpy(e->ghost, worker_result, sizeof(e->ghost) - 1); + e->ghost[sizeof(e->ghost) - 1] = '\0'; + return 1; +} + +int ollama_request_line(Editor *e, int row) +{ + static int loaded = 0; + if (!loaded) { load_config(); loaded = 1; } + if (worker_running) return -1; + + e->autocomplete_row = row; + e->autocomplete_col = buf_line_len(&e->buf, row); + + /* build prompt with cursor at end of target line */ + char *prompt = NULL; + size_t plen = 0; + FILE *f = open_memstream(&prompt, &plen); + if (!f) return -1; + + fprintf(f, "<|fim_prefix|>"); + if (e->desc[0]) { + const char *cs = (e->ft == FT_ASM) ? "; " : + (e->ft == FT_PYTHON || e->ft == FT_BASH) ? "# " : "// "; + fprintf(f, "%sProject: %s\n", cs, e->desc); + } + + int start = row - CONTEXT_LINES; + if (start < 0) start = 0; + for (int i = start; i <= row; i++) { + Line *l = &e->buf.lines[i]; + if (l->chars && l->len > 0) fwrite(l->chars, 1, l->len, f); + if (i < row) fputc('\n', f); + } + fprintf(f, "<|fim_suffix|>\n"); + + /* lines after for context */ + int end = row + 1 + CONTEXT_LINES; + if (end > e->buf.numlines) end = e->buf.numlines; + for (int i = row + 1; i < end; i++) { + Line *l = &e->buf.lines[i]; + if (l->chars && l->len > 0) fwrite(l->chars, 1, l->len, f); + fputc('\n', f); + } + fprintf(f, "<|fim_middle|>"); + fclose(f); + + char *escaped = json_escape(prompt); + free(prompt); + if (!escaped) return -1; + + char *body = NULL; + if (asprintf(&body, + "{\"model\":\"%s\",\"prompt\":\"%s\"," + "\"raw\":true,\"stream\":false," + "\"options\":{\"num_predict\":64,\"temperature\":0.2," + "\"stop\":[\"\\n\",\"<|fim\",\"<|end\",\"<|im_end\"]}}", + OLLAMA_MODEL, escaped) < 0) { + free(escaped); + return -1; + } + free(escaped); + + WorkerArg *wa = malloc(sizeof(*wa)); + wa->body = body; + + worker_done = 0; + worker_running = 1; + pthread_create(&worker_tid, NULL, worker_fn, wa); + pthread_detach(worker_tid); + return 0; +} diff --git a/ved/src/ollama.o b/ved/src/ollama.o new file mode 100644 index 0000000000000000000000000000000000000000..e6bb695efccd0da708c4acee057a6adb7a3daa64 GIT binary patch literal 18360 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 +#include + +#define CP_KEYWORD 1 +#define CP_TYPE 2 +#define CP_STRING 3 +#define CP_COMMENT 4 +#define CP_NUMBER 5 +#define CP_PREPROC 6 + +static const char *c_keywords[] = { + "auto","break","case","continue","default","do","else","enum", + "extern","for","goto","if","inline","register","restrict", + "return","sizeof","static","struct","switch","typedef","union", + "volatile","while","_Alignas","_Alignof","_Atomic","_Bool", + "_Complex","_Generic","_Imaginary","_Noreturn","_Static_assert", + "_Thread_local",NULL +}; + +static const char *c_types[] = { + "char","const","double","float","int","long","short","signed", + "unsigned","void","int8_t","int16_t","int32_t","int64_t", + "uint8_t","uint16_t","uint32_t","uint64_t","size_t","ssize_t", + "bool","true","false","NULL","FILE",NULL +}; + +static const char *asm_keywords[] = { + "section","global","extern","bits","org","align","equ","db","dw", + "dd","dq","resb","resw","resd","resq","times", + "mov","push","pop","call","ret","jmp","je","jne","jz","jnz", + "jg","jge","jl","jle","ja","jae","jb","jbe","cmp","test", + "add","sub","mul","imul","div","idiv","inc","dec","neg","not", + "and","or","xor","shl","shr","sal","sar","rol","ror", + "lea","nop","int","syscall","sysenter","hlt","rep","movsb", + "stosb","lodsb","cmpsb","scasb",NULL +}; + +static const char *py_keywords[] = { + "and","as","assert","async","await","break","class","continue", + "def","del","elif","else","except","finally","for","from", + "global","if","import","in","is","lambda","nonlocal","not", + "or","pass","raise","return","try","while","with","yield",NULL +}; + +static const char *py_types[] = { + "True","False","None","self","cls", + "int","float","str","bool","list","dict","tuple","set", + "bytes","bytearray","complex","range","type","object", + "print","len","open","super","isinstance","hasattr", + "getattr","setattr","enumerate","zip","map","filter",NULL +}; + +static const char *bash_keywords[] = { + "if","then","else","elif","fi","for","while","until","do","done", + "case","esac","in","function","select","time","coproc", + "return","exit","break","continue","shift","export","unset", + "local","readonly","declare","typeset","source","eval","exec", + "trap","set","shopt",NULL +}; + +static const char *bash_builtins[] = { + "echo","printf","read","cd","pwd","pushd","popd","dirs", + "test","true","false","let","expr", + "grep","sed","awk","cut","sort","uniq","wc","tr","find", + "cat","head","tail","tee","xargs","mkdir","rm","cp","mv", + "chmod","chown","ln","touch","basename","dirname",NULL +}; + +static int is_word_char(int c) +{ + return isalnum(c) || c == '_'; +} + +static int word_match(const char *s, int len, const char **list) +{ + for (int i = 0; list[i]; i++) + if ((int)strlen(list[i]) == len && memcmp(s, list[i], len) == 0) + return 1; + return 0; +} + +void syntax_init(void) +{ + if (!has_colors()) return; + start_color(); + use_default_colors(); + init_pair(CP_KEYWORD, COLOR_YELLOW, -1); + init_pair(CP_TYPE, COLOR_GREEN, -1); + init_pair(CP_STRING, COLOR_MAGENTA,-1); + init_pair(CP_COMMENT, COLOR_CYAN, -1); + init_pair(CP_NUMBER, COLOR_RED, -1); + init_pair(CP_PREPROC, COLOR_BLUE, -1); +} + +enum filetype syntax_detect(const char *filename) +{ + if (!filename) return FT_NONE; + const char *dot = strrchr(filename, '.'); + if (!dot) return FT_NONE; + if (strcmp(dot, ".c") == 0 || strcmp(dot, ".h") == 0) + return FT_C; + if (strcmp(dot, ".s") == 0 || strcmp(dot, ".S") == 0 || + strcmp(dot, ".asm") == 0 || strcmp(dot, ".nasm") == 0) + return FT_ASM; + if (strcmp(dot, ".py") == 0) + return FT_PYTHON; + if (strcmp(dot, ".sh") == 0 || strcmp(dot, ".bash") == 0) + return FT_BASH; + return FT_NONE; +} + +void syntax_draw_line(const char *s, int len, int y, int x, + int maxcols, enum filetype ft, int in_block_comment) +{ + int i = 0; + while (i < len && (i + x) < maxcols) { + /* C block comment continuation */ + if (in_block_comment) { + int start = i; + while (i < len) { + if (i + 1 < len && s[i] == '*' && s[i+1] == '/') { + i += 2; break; + } + i++; + } + attron(COLOR_PAIR(CP_COMMENT)); + mvaddnstr(y, x + start, s + start, i - start); + attroff(COLOR_PAIR(CP_COMMENT)); + in_block_comment = (i >= len || !(i >= 2 && s[i-2] == '*' && s[i-1] == '/')); + continue; + } + + /* line comments */ + if (ft == FT_C && i + 1 < len && s[i] == '/' && s[i+1] == '/') { + attron(COLOR_PAIR(CP_COMMENT)); + mvaddnstr(y, x + i, s + i, len - i); + attroff(COLOR_PAIR(CP_COMMENT)); + return; + } + if (ft == FT_ASM && s[i] == ';') { + attron(COLOR_PAIR(CP_COMMENT)); + mvaddnstr(y, x + i, s + i, len - i); + attroff(COLOR_PAIR(CP_COMMENT)); + return; + } + if (ft == FT_PYTHON && s[i] == '#') { + attron(COLOR_PAIR(CP_COMMENT)); + mvaddnstr(y, x + i, s + i, len - i); + attroff(COLOR_PAIR(CP_COMMENT)); + return; + } + if (ft == FT_BASH && s[i] == '#') { + attron(COLOR_PAIR(CP_COMMENT)); + mvaddnstr(y, x + i, s + i, len - i); + attroff(COLOR_PAIR(CP_COMMENT)); + return; + } + + /* C block comment start */ + if (ft == FT_C && i + 1 < len && s[i] == '/' && s[i+1] == '*') { + int start = i; + i += 2; + while (i + 1 < len && !(s[i] == '*' && s[i+1] == '/')) i++; + if (i + 1 < len) i += 2; + attron(COLOR_PAIR(CP_COMMENT)); + mvaddnstr(y, x + start, s + start, i - start); + attroff(COLOR_PAIR(CP_COMMENT)); + continue; + } + + /* C preprocessor */ + if (ft == FT_C && s[i] == '#') { + attron(COLOR_PAIR(CP_PREPROC)); + mvaddnstr(y, x + i, s + i, len - i); + attroff(COLOR_PAIR(CP_PREPROC)); + return; + } + + /* Python decorator */ + if (ft == FT_PYTHON && s[i] == '@') { + int start = i++; + while (i < len && is_word_char((unsigned char)s[i])) i++; + attron(COLOR_PAIR(CP_PREPROC)); + mvaddnstr(y, x + start, s + start, i - start); + attroff(COLOR_PAIR(CP_PREPROC)); + continue; + } + + /* triple-quoted strings (Python) */ + if (ft == FT_PYTHON && i + 2 < len && + ((s[i] == '"' && s[i+1] == '"' && s[i+2] == '"') || + (s[i] == '\'' && s[i+1] == '\'' && s[i+2] == '\''))) { + char q = s[i]; + int start = i; + i += 3; + while (i + 2 < len && !(s[i] == q && s[i+1] == q && s[i+2] == q)) + i++; + if (i + 2 < len) i += 3; + attron(COLOR_PAIR(CP_STRING)); + mvaddnstr(y, x + start, s + start, i - start); + attroff(COLOR_PAIR(CP_STRING)); + continue; + } + + /* f-string prefix (Python) */ + if (ft == FT_PYTHON && (s[i] == 'f' || s[i] == 'r' || s[i] == 'b') && + i + 1 < len && (s[i+1] == '"' || s[i+1] == '\'')) { + int start = i++; + char q = s[i++]; + while (i < len && s[i] != q) { + if (s[i] == '\\' && i + 1 < len) i++; + i++; + } + if (i < len) i++; + attron(COLOR_PAIR(CP_STRING)); + mvaddnstr(y, x + start, s + start, i - start); + attroff(COLOR_PAIR(CP_STRING)); + continue; + } + + /* string / char literal */ + if (s[i] == '"' || s[i] == '\'') { + char q = s[i]; + int start = i++; + while (i < len && s[i] != q) { + if (s[i] == '\\' && i + 1 < len) i++; + i++; + } + if (i < len) i++; + attron(COLOR_PAIR(CP_STRING)); + mvaddnstr(y, x + start, s + start, i - start); + attroff(COLOR_PAIR(CP_STRING)); + continue; + } + + /* number */ + if (isdigit((unsigned char)s[i]) || + (s[i] == '0' && i + 1 < len && (s[i+1] == 'x' || s[i+1] == 'b' || s[i+1] == 'o'))) { + int start = i; + if (s[i] == '0' && i + 1 < len && (s[i+1] == 'x' || s[i+1] == 'b' || s[i+1] == 'o')) + i += 2; + while (i < len && (isxdigit((unsigned char)s[i]) || s[i] == '.' || s[i] == '_')) + i++; + attron(COLOR_PAIR(CP_NUMBER)); + mvaddnstr(y, x + start, s + start, i - start); + attroff(COLOR_PAIR(CP_NUMBER)); + continue; + } + + /* word: keyword or type */ + if (is_word_char((unsigned char)s[i])) { + int start = i; + while (i < len && is_word_char((unsigned char)s[i])) i++; + int wlen = i - start; + + const char **kw = NULL, **tp = NULL; + if (ft == FT_C) { kw = c_keywords; tp = c_types; } + else if (ft == FT_ASM) { kw = asm_keywords; tp = NULL; } + else if (ft == FT_PYTHON) { kw = py_keywords; tp = py_types; } + else if (ft == FT_BASH) { kw = bash_keywords; tp = bash_builtins; } + + if (kw && word_match(s + start, wlen, kw)) { + attron(COLOR_PAIR(CP_KEYWORD) | A_BOLD); + mvaddnstr(y, x + start, s + start, wlen); + attroff(COLOR_PAIR(CP_KEYWORD) | A_BOLD); + } else if (tp && word_match(s + start, wlen, tp)) { + attron(COLOR_PAIR(CP_TYPE)); + mvaddnstr(y, x + start, s + start, wlen); + attroff(COLOR_PAIR(CP_TYPE)); + } else { + mvaddnstr(y, x + start, s + start, wlen); + } + continue; + } + + /* ASM register */ + if (ft == FT_ASM && s[i] == '%') { + int start = i++; + while (i < len && is_word_char((unsigned char)s[i])) i++; + attron(COLOR_PAIR(CP_TYPE)); + mvaddnstr(y, x + start, s + start, i - start); + attroff(COLOR_PAIR(CP_TYPE)); + continue; + } + + /* bash $variable, ${var}, $(...) */ + if (ft == FT_BASH && s[i] == '$') { + int start = i++; + if (i < len && (s[i] == '{' || s[i] == '(')) { + char close = (s[i] == '{') ? '}' : ')'; + i++; + while (i < len && s[i] != close) i++; + if (i < len) i++; + } else { + while (i < len && is_word_char((unsigned char)s[i])) i++; + } + attron(COLOR_PAIR(CP_TYPE)); + mvaddnstr(y, x + start, s + start, i - start); + attroff(COLOR_PAIR(CP_TYPE)); + continue; + } + + mvaddnstr(y, x + i, s + i, 1); + i++; + } +} + +int syntax_line_in_block_comment(Buffer *b, int row, enum filetype ft) +{ + if (ft != FT_C) return 0; + int in = 0; + for (int r = 0; r < row; r++) { + char *s = b->lines[r].chars; + int len = b->lines[r].len; + if (!s) continue; + for (int i = 0; i < len; i++) { + if (!in && i + 1 < len && s[i] == '/' && s[i+1] == '*') { + in = 1; i++; + } else if (in && i + 1 < len && s[i] == '*' && s[i+1] == '/') { + in = 0; i++; + } + } + } + return in; +} diff --git a/ved/src/syntax.o b/ved/src/syntax.o new file mode 100644 index 0000000000000000000000000000000000000000..277d4d21b12c42c18e3eb659318c3ce0ec3a79f1 GIT binary patch literal 22384 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 literal 0 HcmV?d00001 diff --git a/ved/src/terminal.c b/ved/src/terminal.c new file mode 100644 index 0000000..7ae09bf --- /dev/null +++ b/ved/src/terminal.c @@ -0,0 +1,16 @@ +#include "editor.h" + +void term_init(void) +{ + initscr(); + raw(); + noecho(); + keypad(stdscr, TRUE); + set_escdelay(25); + syntax_init(); +} + +void term_end(void) +{ + endwin(); +} diff --git a/ved/src/terminal.o b/ved/src/terminal.o new file mode 100644 index 0000000000000000000000000000000000000000..4f8084dae431a70207cf419ccc8745270481c569 GIT binary patch literal 1944 zcmbu9&ubGw6vtnZ+D7}sT11q32neELmP9MYA~kKu4v|RS)!0qK<=L=eH<*G-C&Z0Cc@#+yJhKq^ixta6;!06U(R38%Dl;+MJ>Vl_*wa$r zdE7H#c8+cF$V{(uJ%+Eu@Inl~7sD$t{6P$V7Q?rhV_&i#_wJbWV&;>w`rVFg>}sYP z>K(hINUzo#8|!twQK>vGZ|a-t8}+hI7;?-$^^HBUdzRVmk)v85JJ#rh(IUrEy5*QH zYu6Z&GqRO2^y*;|-LhN6w%4)wS>#4=!>8E(9(qCyfzPrDKMlwN$DV}azMv%AJe%-K zfGo^kU=yANWPzim@Ed?EaNNJ}H9!{l5}WW0#}L+qiHd$8T<(E1XVg{37JJqAd|RSE zpO>brp(3qiD1)>u$05z^b-R|$%olH~L#;u&6fcqRooqrmT zCHunp<0~ooa{f_kBQNbg=nvoH{X}j!A9cfL&xn5d8+wUOKz_L~Im1y@e!+kKr{kGk H5YGPx&s4wb literal 0 HcmV?d00001 diff --git a/vedit/vedit.py b/vedit/vedit.py index 474b4ae..e21b63b 100755 --- a/vedit/vedit.py +++ b/vedit/vedit.py @@ -1,4 +1,7 @@ #!/usr/bin/env python3 + +# vedit, a test client to vibe code. - anders@holck.se March 2026 + """Vibe - A simple vibe coding tool bridging Ollama LLM and bash shell.""" import curses