ref: 4eb183a5f487d9647216767fcf93c794c4c13157
author: phil9 <telephil9@gmail.com>
date: Mon Dec 27 15:40:17 EST 2021
initial import
--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 phil9 <telephil9@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+++ b/README.md
@@ -1,0 +1,22 @@
+# vexed
+A visual hex editor for plan9.
+
+Move using the arrow keys or select using the mouse.
+Scroll with page up/down, the mouse wheel or using the scrollbar.
+Home and End keys go to the beginning and end of the file respectively.
+Del or q exit the program.
+
+**THIS IS WORK IN PROGRESS. USE AT YOUR OWN RISK**
+
+## Usage:
+```sh
+% mk install
+% vexed <file>
+```
+
+## License:
+MIT
+
+## Bugs:
+Nope, only features!
+
--- /dev/null
+++ b/a.h
@@ -1,0 +1,26 @@
+typedef struct Buffer Buffer;
+
+struct Buffer
+{
+ uchar *data;
+ usize count;
+ usize size;
+};
+
+int readfile(Buffer*, char*);
+int writefile(Buffer*, char*);
+
+/* COLORS */
+enum
+{
+ BACK,
+ ADDR,
+ HEX,
+ ASCII,
+ SCROLL,
+ NCOLS,
+};
+
+Image* cols[NCOLS];
+
+void initcols(int);
--- /dev/null
+++ b/buf.c
@@ -1,0 +1,44 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "a.h"
+
+int
+readfile(Buffer *buf, char *filename)
+{
+ int fd;
+ long r;
+
+ buf->count = 0;
+ buf->size = 8192;
+ buf->data = malloc(buf->size);
+ if(buf->data == nil)
+ return -1;
+ fd = open(filename, OREAD);
+ if(fd < 0)
+ return -1;
+ for(;;){
+ r = read(fd, buf->data + buf->count, buf->size - buf->count);
+ if(r < 0)
+ return -1;
+ if(r == 0)
+ break;
+ buf->count += r;
+ if(buf->count == buf->size){
+ buf->size *= 1.5;
+ buf->data = realloc(buf->data, buf->size);
+ if(buf->data == nil)
+ return -1;
+ }
+ }
+ buf->data[buf->count] = 0;
+ close(fd);
+ return 0;
+}
+
+int writefile(Buffer *buf, char *filename)
+{
+ USED(buf);
+ USED(filename);
+ return -1;
+}
--- /dev/null
+++ b/cols.c
@@ -1,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "a.h"
+
+void
+initcols(int reverse)
+{
+ if(reverse){
+ cols[BACK] = display->black;
+ cols[ADDR] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
+ cols[HEX] = display->white;
+ cols[ASCII] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
+ cols[SCROLL] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x999999FF^reverse);
+ }else{
+ cols[BACK] = display->white;
+ cols[ADDR] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DGreygreen);
+ cols[HEX] = display->black;
+ cols[ASCII] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DGreygreen);
+ cols[SCROLL] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x999999FF);
+ }
+/*
+ bg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x282828ff);
+ fg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xebdbb2ff);
+ ofg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x83a598ff);
+ scrlbg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x504945ff);
+*/
+}
+
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,9 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin
+TARG=vexed
+OFILES=vexed.$O buf.$O cols.$O
+HFILES=a.h
+
+</sys/src/cmd/mkone
+
--- /dev/null
+++ b/vexed.c
@@ -1,0 +1,333 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <ctype.h>
+#include "a.h"
+
+enum
+{
+ Emouse,
+ Eresize,
+ Ekeyboard,
+};
+
+enum
+{
+ Padding = 4,
+ Spacing = 8,
+ Scrollwidth = 12,
+};
+
+const char *filename;
+Buffer buf;
+int blines;
+Mousectl *mctl;
+Keyboardctl *kctl;
+int sw;
+int scrollsize;
+Rectangle scrollr;
+Rectangle viewr;
+Rectangle statusr;
+int nlines;
+int offset;
+int sel = 0;
+
+int
+selvisible(void)
+{
+ int l;
+
+ l = sel/16;
+ return offset <= l && l < offset+nlines;
+}
+
+int
+ensureselvisible(void)
+{
+ if(selvisible())
+ return 0;
+ offset = sel/16;
+ return 1;
+}
+
+void
+drawline(int y, int base)
+{
+ int index, i, n;
+ char b[8] = {0}, *s;
+ Point p;
+ Point p2;
+
+ index = base + offset*16;
+ if(index >= buf.count)
+ return;
+ p = Pt(viewr.min.x, y);
+ if(index/16 == sel/16){
+ n = snprint(b, sizeof b, "%06X", sel);
+ p = stringnbg(screen, p, cols[BACK], ZP, font, b, n, cols[ADDR], ZP);
+ }else{
+ n = snprint(b, sizeof b, "%06X", index);
+ p = stringn(screen, p, cols[ADDR], ZP, font, b, n);
+ }
+ p.x += 2*Spacing;
+ p2 = addpt(p, Pt(16*3*sw - sw + 2*Spacing, 0));
+ for(i = 0; i < 16; i++) {
+ if(index + i >= buf.count)
+ break;
+ n = snprint(b, sizeof b, "%02X", buf.data[index + i]);
+ s = isprint(buf.data[index + i]) ? (char*)&buf.data[index + i] : ".";
+ if(index + i == sel){
+ p = stringnbg(screen, p, cols[BACK], ZP, font, b, n, cols[HEX], ZP);
+ p2 = stringnbg(screen, p2, cols[BACK], ZP, font, s, 1, cols[ASCII], ZP);
+ }else{
+ p = stringn(screen, p, cols[HEX], ZP, font, b, n);
+ p2 = stringn(screen, p2, cols[ASCII], ZP, font, s, 1);
+ }
+ p = stringn(screen, p, cols[BACK], ZP, font, " ", 1);
+ }
+}
+
+void
+redraw(void)
+{
+ int i, h, y, ye;
+ Rectangle scrposr;
+ char b[16] = {0};
+
+ draw(screen, screen->r, cols[BACK], nil, ZP);
+ draw(screen, scrollr, cols[SCROLL], nil, ZP);
+ border(screen, scrollr, 0, cols[HEX], ZP);
+ if(blines > 0){
+ h = ((double)nlines / blines) * Dy(scrollr);
+ y = ((double)offset / blines) * Dy(scrollr);
+ ye = scrollr.min.y + y + h - 1;
+ if(ye >= scrollr.max.y)
+ ye = scrollr.max.y - 1;
+ scrposr = Rect(scrollr.min.x + 1, scrollr.min.y + y + 1, scrollr.max.x - 1, ye);
+ }else
+ scrposr = insetrect(scrollr, -1);
+ draw(screen, scrposr, cols[BACK], nil, ZP);
+ for(i = 0; i < nlines; i++)
+ drawline(viewr.min.y + i * font->height, i*16);
+ string(screen, Pt(statusr.min.x + Padding, statusr.min.y), cols[HEX], ZP, font, filename);
+ snprint(b, sizeof b, "%d%%", (int)((100.0 * sel) / buf.count + 0.5));
+ y = statusr.max.x - stringwidth(font, b) - Padding;
+ string(screen, Pt(y, statusr.min.y), cols[HEX], ZP, font, b);
+ flushimage(display, 1);
+}
+
+int
+scroll(int lines)
+{
+ if(blines <= nlines)
+ return 0;
+ if(lines < 0 && offset == 0)
+ return 0;
+ if(lines > 0 && offset + nlines >= blines){
+ return 0;
+ }
+ offset += lines;
+ if(offset < 0)
+ offset = 0;
+ if(offset + blines%nlines >= blines)
+ offset = blines - blines%nlines;
+ sel += lines * 16;
+ if(sel < 0)
+ sel = 0;
+ else if(sel >= buf.count)
+ sel = buf.count - 1;
+ redraw();
+ return 1;
+}
+
+int
+indexat(Point p)
+{
+ int row, col, index;
+
+ row = (p.y - viewr.min.y) / font->height;
+ col = ((p.x - viewr.min.x) / sw - 8);
+ if(col < 0 || col > 16*3*sw)
+ return -1;
+ index = col/3 + row*16 + offset*16;
+ if(index >= buf.count)
+ return -1;
+ return index;
+}
+
+void
+emouse(Mouse *m)
+{
+ int n;
+
+ if(ptinrect(m->xy, scrollr)){
+ if(m->buttons == 1){
+ n = (m->xy.y - scrollr.min.y) / font->height;
+ scroll(-n);
+ }else if(m->buttons == 2){
+ n = (m->xy.y - scrollr.min.y) * blines / Dy(scrollr);
+ offset = n;
+ sel = offset*16;
+ redraw();
+ }else if(m->buttons == 4){
+ n = (m->xy.y - scrollr.min.y) / font->height;
+ scroll(n);
+ }
+ } else if(ptinrect(m->xy, viewr)){
+ if(m->buttons == 1){
+ n = indexat(m->xy);
+ if(n >= 0){
+ sel = n;
+ redraw();
+ }
+ }else if(m->buttons == 8){
+ scroll(-scrollsize);
+ }else if(m->buttons == 16){
+ scroll(scrollsize);
+ }
+ }
+}
+
+void
+eresize(void)
+{
+ int w, x;
+
+ statusr = Rect(screen->r.min.x + Padding, screen->r.max.y - Padding - font->height, screen->r.max.x - Padding, screen->r.max.y - Padding);
+ scrollr = Rect(screen->r.min.x + Padding, screen->r.min.y + Padding, screen->r.min.x + Padding + Scrollwidth, statusr.min.y - Padding);
+ sw = stringwidth(font, " ");
+ w = Padding + 6*sw + 2*Spacing + 16*3*sw-sw + 2*Spacing + 16*sw + Padding;
+ x = scrollr.max.x + Padding;
+ viewr = Rect(x, screen->r.min.y + Padding, x + w, statusr.min.y - Padding);
+ nlines = Dy(viewr) / font->height;
+ scrollsize = mousescrollsize(nlines);
+ redraw();
+ flushimage(display, 1);
+}
+
+void
+ekeyboard(Rune k)
+{
+ switch(k){
+ case 'q':
+ case Kdel:
+ threadexitsall(nil);
+ break;
+ case Kpgup:
+ scroll(-nlines);
+ break;
+ case Kpgdown:
+ scroll(nlines);
+ break;
+ case Kleft:
+ if(sel > 0){
+ sel--;
+ ensureselvisible();
+ redraw();
+ }
+ break;
+ case Kright:
+ if(sel < (buf.count - 1)){
+ sel++;
+ ensureselvisible();
+ redraw();
+ }
+ break;
+ case Kup:
+ if(sel >= 16){
+ sel -= 16;
+ ensureselvisible();
+ redraw();
+ }
+ break;
+ case Kdown:
+ if(sel < (buf.count - 16)){
+ sel += 16;
+ ensureselvisible();
+ redraw();
+ }
+ break;
+ case Khome:
+ if(sel != 0){
+ sel = 0;
+ ensureselvisible();
+ redraw();
+ }
+ break;
+ case Kend:
+ if(sel != buf.count - 1){
+ sel = buf.count - 1;
+ if(!selvisible())
+ offset = blines - blines%nlines;
+ redraw();
+ }
+ break;
+ }
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-b] <filename>\n", argv0);
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ Mouse m;
+ Rune k;
+ Alt alts[] = {
+ { nil, &m, CHANRCV },
+ { nil, nil, CHANRCV },
+ { nil, &k, CHANRCV },
+ { nil, nil, CHANEND },
+ };
+ int reverse;
+
+ reverse = 0;
+ ARGBEGIN{
+ case 'b':
+ reverse = 1;
+ break;
+ default:
+ usage();
+ }ARGEND;
+ if(*argv == nil)
+ usage();
+ filename = *argv;
+ if(readfile(&buf, filename) < 0)
+ sysfatal("readfile: %r");
+ blines = buf.count/16;
+ if(initdraw(nil, nil, argv0) < 0)
+ sysfatal("initdraw: %r");
+ display->locking = 0;
+ if((mctl = initmouse(nil, screen)) == nil)
+ sysfatal("initmouse: %r");
+ if((kctl = initkeyboard(nil)) == nil)
+ sysfatal("initkeyboard: %r");
+ initcols(reverse);
+ alts[Emouse].c = mctl->c;
+ alts[Eresize].c = mctl->resizec;
+ alts[Ekeyboard].c = kctl->c;
+ eresize();
+ for(;;){
+ switch(alt(alts)){
+ case Emouse:
+ emouse(&m);
+ break;
+ case Eresize:
+ if(getwindow(display, Refnone) < 0)
+ sysfatal("getwindow: %r");
+ eresize();
+ break;
+ case Ekeyboard:
+ ekeyboard(k);
+ break;
+ }
+ }
+}
+