acme: shift button 3 for reverse search

An experiment. Let's see if it's any good.
Also document the Mac conventions in devdraw(3).
This commit is contained in:
Russ Cox 2024-06-15 10:55:21 -04:00
parent 60ca2be037
commit 0c79c32675
14 changed files with 270 additions and 100 deletions

View file

@ -99,6 +99,11 @@ The argument is one of
\fL'\fIxmin ymin xmax ymax\fL'\fR,
\fRor
\fIxmin\fL,\fIymin\fL,\fIxmax\fL,\fIymax\fR.
See
.MR devdraw (1)
and
.MR keyboard (7)
for details about typing and clicking in graphical applications.
.PP
The
.MR plumber (4)

View file

@ -588,6 +588,9 @@ not just
or
.BR 127 .
(There is an easier way to locate literal text; see below.)
If shift is held down during the selection or click,
any leading regular expression search defaults to
searching backward in the text instead of forward.
.PP
If the text is a file name followed by a colon and an address,
.I acme
@ -608,6 +611,8 @@ moved there. Thus, to search for occurrences of a word in a file,
just click button 3 on the word. Because of the rule of using the
selection as the button 3 action, subsequent clicks will find subsequent
occurrences without moving the mouse.
If shift is held down during the selection or click,
the search looks backward in the file.
.PP
In all these actions, the mouse motion is not done if the text is a null string
within a non-null selected string in the tag, so that (for example) complex regular expressions

View file

@ -10,19 +10,71 @@ invoked via
.I Devdraw
serves a custom graphics protocol and is the only program
that talks directly to X window servers.
On Macintosh, setting
.BI devdrawretina
to
.BI 1
will cause
.I devdraw
to use all available physical pixels on a retina display.
.PP
.SS "Apple macOS
.PP
On macOS, because a laptop trackpad click only has one button (the trackpad itself)
Option-click is button 2, and Command-click is button 3.
While the main mouse button is held down,
Control, Option, and Command serve as simulated buttons 1, 2, 3 for chording in
.MR acme (4) .
For example, the 1-3 pasting chord in acme can be executed by
highlighting text while holding down the trackpad button
and then, while still holding down the button, pressing the Command key.
.PP
As usual, buttons 4 and 5 represent a scroll wheel.
Two-finger scrolling on the trackpad sends those button events.
.PP
Holding down shift while clicking adds 5 to the button number.
For example, Command-Click is button 3, so Command-Shift-Click is button 8.
Most programs do not respond to those buttons; one notable exception is
.MR acme (1) ,
which interprets button 8 (shifted button 3) as a reverse search.
.PP
Typing Command-F toggles full screen mode.
.PP
.I Devdraw
automatically detects high-resolution (retina) displays.
For debugging, typing Command-R toggles retina mode.
.PP
Other than the special cases mentioned above,
holding down Command while typing a character
.B Kcmd
(0xF100)
plus that character.
Some programs (notably
.IR acme (1))
recognize standard keyboard shortcuts such as
Command-Z (undo), Command-Shift-Z (redo),
Command-X (cut), and Command-V (paste).
.SS "X Windows
.PP
On Unix systems, Control-click is mouse button 2,
and Alt-click is mouse button 3.
While the main mouse button is held down,
Control and Alt serve as simulated buttons 2, 3 for chording in
.MR acme (4) .
For example, the 1-3 pasting chord in acme can be executed by
highlighting text while holding down the trackpad button
and then, while still holding down the button, pressing the Alt key.
.PP
Because the Control and Alt keys have other meanings
(see
.MR keyboard (7)
for the Alt key's meaning)
and there is no third modifier key like on the Mac,
there is no way to type
.B Kcmd
variants,
so there is no access to keyboard shortcuts for
undo, redo, cut, and paste.
.SH SOURCE
.B \*9/src/cmd/devdraw
.SH "SEE ALSO
.MR draw (3) ,
.MR drawfcall (3) ,
.MR graphics (3)
.MR graphics (3) ,
.MR keyboard (7)
.SH BUGS
.I Devdraw
should probably present a standard 9P server

View file

@ -361,6 +361,10 @@ for text inserted to the tag,
for a button 3 action in the body,
.B l
for a button 3 action in the tag,
.B R
for a shifted button 3 action in the body,
.B r
for a shifted button 3 action in the tag,
.B X
for a button 2 action in the body, and
.B x

View file

@ -62,16 +62,11 @@ Any rune can be typed using a compose key followed by several
other keys.
The compose key is also generally near the lower right of the main key area:
the
.B NUM PAD
key on the Gnot, the
.B Alternate
key on the Next, the
.B Compose
key on the SLC, the
.B Option
key on the Magnum, and either
key on the Mac
and the
.B Alt
key on the PC.
key on Unix systems.
To type a single rune with the value specified by
a given four-digit hexadecimal number,
type the compose key,

View file

@ -1,4 +1,5 @@
#include <u.h>
#include <pwd.h>
#include <signal.h>
#include <libc.h>
#include <ctype.h>
@ -56,7 +57,7 @@ threadmaybackground(void)
void
threadmain(int argc, char *argv[])
{
char *p;
char *p, *env;
rfork(RFNOTEG);
font = nil;
@ -64,6 +65,25 @@ threadmain(int argc, char *argv[])
mainpid = getpid();
messagesize = 8192;
threadmaybackground();
env = getenv("__CFBundleIdentifier");
if(env != nil && strcmp(env, "com.swtch.9term") == 0) {
// Being invoked as $PLAN9/mac/9term.app.
// Set $SHELL and daemonize to let parent exit.
// This makes sure that each click on 9term
// brings up a new window.
extern void _threaddaemonize(void);
struct passwd *pw;
unsetenv("__CFBundleIdentifier");
pw = getpwuid(getuid());
if(pw != nil && pw->pw_shell != nil)
setenv("SHELL", pw->pw_shell, 1);
loginshell = TRUE;
//_threaddaemonize();
}
ARGBEGIN{
default:
usage();

View file

@ -518,6 +518,7 @@ mousethread(void *v)
Mouse m;
char *act;
enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
enum { Shift = 5 };
static Alt alts[NMALT+1];
USED(v);
@ -661,9 +662,9 @@ mousethread(void *v)
}else if(m.buttons & 2){
if(textselect2(t, &q0, &q1, &argt))
execute(t, q0, q1, FALSE, argt);
}else if(m.buttons & 4){
}else if(m.buttons & (4|(4<<Shift))){
if(textselect3(t, &q0, &q1))
look3(t, q0, q1, FALSE);
look3(t, q0, q1, FALSE, (m.buttons&(4<<Shift))!=0);
}
if(w)
winunlock(w);
@ -770,7 +771,7 @@ waitthread(void *v)
pids = p;
}
}else{
if(search(t, c->name, c->nname)){
if(search(t, c->name, c->nname, FALSE)){
textdelete(t, t->q0, t->q1, TRUE);
textsetselect(t, 0, 0);
}

View file

@ -172,7 +172,7 @@ regexp(uint showerr, Text *t, Range lim, Range r, Rune *pat, int dir, int *found
}
Range
address(uint showerr, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, int (*getc)(void*, uint), int *evalp, uint *qp)
address(uint showerr, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, int (*getc)(void*, uint), int *evalp, uint *qp, int reverse)
{
int dir, size, npat;
int prevc, c, nc, n;
@ -183,6 +183,8 @@ address(uint showerr, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, i
r = ar;
q = q0;
dir = None;
if(reverse)
dir = Back;
size = Line;
c = 0;
while(q < q1){
@ -201,7 +203,7 @@ address(uint showerr, Text *t, Range lim, Range ar, void *a, uint q0, uint q1, i
if(q>=q1 && t!=nil && t->file!=nil) /* rhs defaults to $ */
r.q1 = t->file->b.nc;
else{
nr = address(showerr, t, lim, ar, a, q, q1, getc, evalp, &q);
nr = address(showerr, t, lim, ar, a, q, q1, getc, evalp, &q, FALSE);
r.q1 = nr.q1;
}
*qp = q;

View file

@ -454,6 +454,7 @@ struct Expand
int nname;
char *bname;
int jump;
int reverse;
union{
Text *at;
Rune *ar;

View file

@ -280,13 +280,14 @@ getarg(Text *argt, int doaddr, int dofile, Rune **rp, int *nrp)
Expand e;
char *a;
memset(&e, 0, sizeof e);
*rp = nil;
*nrp = 0;
if(argt == nil)
return nil;
a = nil;
textcommit(argt, TRUE);
if(expand(argt, argt->q0, argt->q1, &e)){
if(expand(argt, argt->q0, argt->q1, &e, FALSE)){
free(e.bname);
if(e.nname && dofile){
e.name = runerealloc(e.name, e.nname+1);
@ -1083,7 +1084,7 @@ look(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg)
if(et && et->w){
t = &et->w->body;
if(narg > 0){
search(t, arg, narg);
search(t, arg, narg, FALSE);
return;
}
getarg(argt, FALSE, FALSE, &r, &n);
@ -1092,7 +1093,7 @@ look(Text *et, Text *t, Text *argt, int _0, int _1, Rune *arg, int narg)
r = runemalloc(n);
bufread(&t->file->b, t->q0, r, n);
}
search(t, r, n);
search(t, r, n, FALSE);
free(r);
}
}

View file

@ -63,8 +63,8 @@ void fontx(Text*, Text*, Text*, int, int, Rune*, int);
#define isalnum acmeisalnum
int isalnum(Rune);
void execute(Text*, uint, uint, int, Text*);
int search(Text*, Rune*, uint);
void look3(Text*, uint, uint, int);
int search(Text*, Rune*, uint, int);
void look3(Text*, uint, uint, int, int);
void editcmd(Text*, Rune*, uint);
uint min(uint, uint);
uint max(uint, uint);
@ -85,11 +85,11 @@ int isregexc(int);
void *emalloc(uint);
void *erealloc(void*, uint);
char *estrdup(char*);
Range address(uint, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*);
Range address(uint, Text*, Range, Range, void*, uint, uint, int (*)(void*, uint), int*, uint*, int);
int rxexecute(Text*, Rune*, uint, uint, Rangeset*);
int rxbexecute(Text*, uint, Rangeset*);
Window* makenewwindow(Text *t);
int expand(Text*, uint, uint, Expand*);
int expand(Text*, uint, uint, Expand*, int);
Rune* skipbl(Rune*, int, int*);
Rune* findbl(Rune*, int, int*);
char* edittext(Window*, int, Rune*, int);

View file

@ -80,7 +80,7 @@ startplumbing(void)
void
look3(Text *t, uint q0, uint q1, int external)
look3(Text *t, uint q0, uint q1, int external, int reverse)
{
int n, c, f, expanded;
Text *ct;
@ -94,7 +94,7 @@ look3(Text *t, uint q0, uint q1, int external)
ct = seltext;
if(ct == nil)
seltext = t;
expanded = expand(t, q0, q1, &e);
expanded = expand(t, q0, q1, &e, reverse);
if(!external && t->w!=nil && t->w->nopen[QWevent]>0){
/* send alphanumeric expansion to external client */
if(expanded == FALSE)
@ -109,6 +109,8 @@ look3(Text *t, uint q0, uint q1, int external)
c = 'l';
if(t->what == Body)
c = 'L';
if(reverse)
c += 'R' - 'L';
n = q1-q0;
if(n <= EVENTSIZE){
r = runemalloc(n);
@ -203,12 +205,17 @@ look3(Text *t, uint q0, uint q1, int external)
ct = &t->w->body;
if(t->w != ct->w)
winlock(ct->w, 'M');
if(t == ct)
textsetselect(ct, e.q1, e.q1);
if(t == ct) {
uint q;
q = e.q1;
if(reverse)
q = e.q0;
textsetselect(ct, q, q);
}
n = e.q1 - e.q0;
r = runemalloc(n);
bufread(&t->file->b, e.q0, r, n);
if(search(ct, r, n) && e.jump)
if(search(ct, r, n, reverse) && e.jump)
moveto(mousectl, addpt(frptofchar(&ct->fr, ct->fr.p0), Pt(4, ct->fr.font->height-4)));
if(t->w != ct->w)
winunlock(ct->w);
@ -241,6 +248,7 @@ plumblook(Plumbmsg *m)
warning(nil, "insanely long file name (%d bytes) in plumb message (%.32s...)\n", m->ndata, m->data);
return;
}
memset(&e, 0, sizeof e);
e.q0 = 0;
e.q1 = 0;
if(m->data[0] == '\0')
@ -303,11 +311,11 @@ plumbshow(Plumbmsg *m)
}
int
search(Text *ct, Rune *r, uint n)
search(Text *ct, Rune *r, uint n, int reverse)
{
uint q, nb, maxn;
uint nb, maxn;
int around;
Rune *s, *b, *c;
Rune *s, *b;
if(n==0 || n>ct->file->b.nc)
return FALSE;
@ -321,55 +329,111 @@ search(Text *ct, Rune *r, uint n)
nb = 0;
b[nb] = 0;
around = 0;
q = ct->q1;
for(;;){
if(q >= ct->file->b.nc){
q = 0;
around = 1;
nb = 0;
b[nb] = 0;
}
if(nb > 0){
c = runestrchr(b, r[0]);
if(c == nil){
q += nb;
if(reverse){
uint q1;
q1 = ct->q0; // q1 is (past) end of text being searched.
for(;;){
if(q1 <= 0){
q1 = ct->file->b.nc;
around = 1;
nb = 0;
b[nb] = 0;
if(around && q>=ct->q1)
break;
continue;
}
q += (c-b);
nb -= (c-b);
b = c;
}
/* reload if buffer covers neither string nor rest of file */
if(nb<n && nb!=ct->file->b.nc-q){
nb = ct->file->b.nc-q;
if(nb >= maxn)
nb = maxn-1;
bufread(&ct->file->b, q, s, nb);
b = s;
b[nb] = '\0';
}
/* this runeeq is fishy but the null at b[nb] makes it safe */
if(runeeq(b, n, r, n)==TRUE){
if(ct->w){
textshow(ct, q, q+n, 1);
winsettag(ct->w);
}else{
ct->q0 = q;
ct->q1 = q+n;
if(nb > 0){
Rune *c;
for(c=b+nb; c>b; c--)
if(c[-1] == r[n-1])
break;
if(c == b) {
q1 -= nb;
nb = 0;
b[nb] = 0;
if(around && q1 <= 0)
break;
continue;
}
q1 -= nb - (c - b);
nb = c - b;
}
seltext = ct;
fbuffree(s);
return TRUE;
/* reload if buffer covers neither string nor beginning of file */
if(nb<n && nb!=q1){
nb = q1;
if(nb >= maxn)
nb = maxn-1;
bufread(&ct->file->b, q1-nb, s, nb);
b = s;
b[nb] = '\0';
}
if(runeeq(b+nb-n, n, r, n)==TRUE){
if(ct->w){
textshow(ct, q1-n, q1, 1);
winsettag(ct->w);
}else{
ct->q0 = q1-n;
ct->q1 = q1;
}
seltext = ct;
fbuffree(s);
return TRUE;
}
q1--;
nb--;
if(around && q1 <= 0)
break;
}
}else{
uint q;
q = ct->q1;
for(;;){
if(q >= ct->file->b.nc){
q = 0;
around = 1;
nb = 0;
b[nb] = 0;
}
if(nb > 0){
Rune *c;
c = runestrchr(b, r[0]);
if(c == nil){
q += nb;
nb = 0;
b[nb] = 0;
if(around && q>=ct->q1)
break;
continue;
}
q += (c-b);
nb -= (c-b);
b = c;
}
/* reload if buffer covers neither string nor rest of file */
if(nb<n && nb!=ct->file->b.nc-q){
nb = ct->file->b.nc-q;
if(nb >= maxn)
nb = maxn-1;
bufread(&ct->file->b, q, s, nb);
b = s;
b[nb] = '\0';
}
/* this runeeq is fishy but the null at b[nb] makes it safe */
if(runeeq(b, n, r, n)==TRUE){
if(ct->w){
textshow(ct, q, q+n, 1);
winsettag(ct->w);
}else{
ct->q0 = q;
ct->q1 = q+n;
}
seltext = ct;
fbuffree(s);
return TRUE;
}
--nb;
b++;
q++;
if(around && q>=ct->q1)
break;
}
--nb;
b++;
q++;
if(around && q>=ct->q1)
break;
}
fbuffree(s);
return FALSE;
@ -526,7 +590,7 @@ texthas(Text *t, uint q0, Rune *r)
}
int
expandfile(Text *t, uint q0, uint q1, Expand *e)
expandfile(Text *t, uint q0, uint q1, Expand *e, int reverse)
{
int i, n, nname, colon, eval;
uint amin, amax;
@ -570,6 +634,11 @@ expandfile(Text *t, uint q0, uint q1, Expand *e)
break;
}else
amax = t->file->b.nc;
if(colon != q0)
reverse = FALSE;
}else if(reverse){
if(textreadc(t, q0) != ':')
reverse = FALSE;
}
amin = amax;
e->q0 = q0;
@ -643,12 +712,16 @@ expandfile(Text *t, uint q0, uint q1, Expand *e)
}
Isfile:
print("isfile reverse=%d colon=%d q0=%d\n", reverse, colon, q0);
e->name = r;
e->nname = nname;
e->u.at = t;
e->a0 = amin+1;
e->reverse = reverse;
eval = FALSE;
address(TRUE, nil, range(-1,-1), range(0,0), t, e->a0, amax, tgetc, &eval, (uint*)&e->a1);
// Note: address is repeated in openfile when
// expandfile returns to expand returns to look3.
address(TRUE, nil, range(-1,-1), range(0,0), t, e->a0, amax, tgetc, &eval, (uint*)&e->a1, e->reverse);
return TRUE;
Isntfile:
@ -657,7 +730,7 @@ expandfile(Text *t, uint q0, uint q1, Expand *e)
}
int
expand(Text *t, uint q0, uint q1, Expand *e)
expand(Text *t, uint q0, uint q1, Expand *e, int reverse)
{
memset(e, 0, sizeof *e);
e->agetc = tgetc;
@ -670,7 +743,7 @@ expand(Text *t, uint q0, uint q1, Expand *e)
e->jump = FALSE;
}
if(expandfile(t, q0, q1, e))
if(expandfile(t, q0, q1, e, reverse))
return TRUE;
if(q0 == q1){
@ -806,7 +879,7 @@ openfile(Text *t, Expand *e)
eval = FALSE;
else{
eval = TRUE;
r = address(TRUE, t, range(-1,-1), range(t->q0, t->q1), e->u.at, e->a0, e->a1, e->agetc, &eval, &dummy);
r = address(TRUE, t, range(-1,-1), range(t->q0, t->q1), e->u.at, e->a0, e->a1, e->agetc, &eval, &dummy, e->reverse);
if(r.q0 > r.q1) {
eval = FALSE;
warning(nil, "addresses out of order\n");

View file

@ -486,7 +486,7 @@ xfidwrite(Xfid *x)
t = &w->body;
wincommit(w, t);
eval = TRUE;
a = address(FALSE, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb);
a = address(FALSE, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb, FALSE);
free(r);
if(nb < nr){
respond(x, &fc, Ebadaddr);
@ -900,7 +900,11 @@ xfideventwrite(Xfid *x, Window *w)
break;
case 'l':
case 'L':
look3(t, q0, q1, TRUE);
look3(t, q0, q1, TRUE, FALSE);
break;
case 'r':
case 'R':
look3(t, q0, q1, TRUE, TRUE);
break;
default:
qunlock(&row.lk);

View file

@ -631,12 +631,17 @@ rpc_resizewindow(Client *c, Rectangle r)
b = [NSEvent pressedMouseButtons];
b = (b&~6) | (b&4)>>1 | (b&2)<<1;
if(b){
int x;
x = 0;
if(m & ~omod & NSEventModifierFlagControl)
b |= 1;
x = 1;
if(m & ~omod & NSEventModifierFlagOption)
b |= 2;
x = 2;
if(m & ~omod & NSEventModifierFlagCommand)
b |= 4;
x = 4;
if(m & NSEventModifierFlagShift)
x <<= 5;
b |= x;
[self sendmouse:b];
}else if(m & ~omod & NSEventModifierFlagOption)
gfx_keystroke(self.client, Kalt);
@ -701,6 +706,8 @@ rpc_resizewindow(Client *c, Rectangle r)
}else
if(m & NSEventModifierFlagCommand)
b = 4;
if(m & NSEventModifierFlagShift)
b <<= 5;
}
[self sendmouse:b];
}