--- Makefile.in +++ Makefile.in @@ -49,7 +49,8 @@ help.${O} ifile.${O} input.${O} jump.${O} line.${O} linenum.${O} \ lsystem.${O} mark.${O} optfunc.${O} option.${O} opttbl.${O} os.${O} \ output.${O} position.${O} prompt.${O} search.${O} signal.${O} \ - tags.${O} ttyin.${O} version.${O} @REGEX_O@ + tags.${O} ttyin.${O} version.${O} @REGEX_O@ \ + selection.${O} all: less lesskey lessecho --- cmd.h +++ cmd.h @@ -66,6 +66,10 @@ #define A_NEXT_TAG 53 #define A_PREV_TAG 54 +#define A_MOUSE 60 +#define A_MOUSE_END_SEL_IN 61 +#define A_MOUSE_END_SEL_OUT 62 + #define A_INVALID 100 #define A_NOACTION 101 #define A_UINVALID 102 @@ -129,3 +133,7 @@ #define SK_BACKTAB 15 #define SK_CTL_BACKSPACE 16 #define SK_CONTROL_K 40 + +/* less input command mode */ +#define M_NORMAL 1 +#define M_MULTICHAR_COMMAND 2 --- command.c +++ command.c @@ -57,7 +57,7 @@ extern int forw_prompt; static char ungot[UNGOT_SIZE]; -static char *ungotp = NULL; +public char *ungotp = NULL; #if SHELL_ESCAPE static char *shellcmd = NULL; /* For holding last shell command for "!!" */ #endif @@ -73,6 +73,10 @@ #if PIPEC static char pipec; #endif +int mouse_x1, mouse_y1; /* mouse coordinates on left mouse button press */ +extern char *selection; /* buffer with selection */ +extern int selection_size; /* current selection size */ +extern int xselection; /* is xselection available */ static void multi_search(); @@ -858,13 +862,218 @@ } /* + * process escape sequence with mouse button action + * Note: action on mouse wheel depends on current less input command mode + */ + static void +process_mouse_button(mode) + int mode; /* normal mode or multicharacter command mode */ +{ + int button, mouse_x, mouse_y; + int counter; + + button = getcc() - 32; + mouse_x = getcc() - 32; + mouse_y = getcc() - 32; + + if (button & 64) + { + /* mouse wheel used */ + if (button & 1) + { + /* mouse wheel down */ + switch (mode) + { + case M_NORMAL: + /* forward N (default 3) line. */ + if (number <= 0) + number = 3; + cmd_exec(); + if (show_attn == OPT_ONPLUS && number > 1) + set_attnpos(bottompos); + forward(number, 0, 0); + break; + case M_MULTICHAR_COMMAND: + /* the same behaviour as for down arrow */ + ungetcc('k'); + ungetcc('\033'); + break; + default: + error("Warnning: Internal error, bad mode for process_mouse_button()", NULL_PARG); + break; + } + } else + { + /* mouse wheel up */ + switch (mode) + { + case M_NORMAL: + /* backward N (default 3) line. */ + if (number <= 0) + number = 3; + cmd_exec(); + backward(number, 0, 0); + break; + case M_MULTICHAR_COMMAND: + /* the same behaviour as for up arrow */ + ungetcc('j'); + ungetcc('\033'); + break; + default: + error("Warnning: Internal error, bad mode for process_mouse_button()", NULL_PARG); + break; + } + } + } else + { + /* 1-3 mouse button used */ + switch (button & 3) + { + case 0: + /* + * left button pressed + * xterm is waiting for mouse tracking information + */ + printf("\033[1;%d;%d;1;%dT", + mouse_x, mouse_y, get_swindow() + 1); + fflush(stdout); + /* save coordinates of selection */ + mouse_x1 = mouse_x; + mouse_y1 = mouse_y; + break; + case 1: + /* + * middle button pressed + * paste selection + */ + if (xselection) + read_xselection(); + for (counter = selection_size; --counter >= 0;) + ungetcc(selection[counter]); + break; + case 3: + /* any button released */ + break; + } + } + return; +} + +/* + * process escape sequence with mouse action (end of selection) + * left mouse button was released inside of given area + */ + static void +process_mouse_end_selection_in() +{ + int button, mouse_x, mouse_y; + + mouse_x = getcc() - 32; + mouse_y = getcc() - 32; + create_selection(mouse_x1 - 1, mouse_y1 - 1, + mouse_x - 1, mouse_y - 1); + if (xselection) + write_xselection(); + return; +} + +/* + * process escape sequence with mouse action (end of selection) + * left mouse button was released outside of given area + */ + static void +process_mouse_end_selection_out() +{ + int mouse_x, mouse_y; + int mouse_x2, mouse_y2; + + mouse_x1 = getcc() - 32; /* startx */ + mouse_y1 = getcc() - 32; /* starty */ + mouse_x2 = getcc() - 32; /* endx - inside of given area */ + mouse_y2 = getcc() - 32; /* endy - inside of given area */ + mouse_x = getcc() - 32; /* current mouse x coordinate */ + mouse_y = getcc() - 32; /* current mouse y coordinate */ + create_selection(mouse_x1 - 1, mouse_y1 - 1, + mouse_x2 - 1, mouse_y2 - 1); + if (xselection) + write_xselection(); + return; +} + +/* + * process possible escape sequention with mouse action + * in multicharacter command mode + */ + static int +save_mouse_action(int *c) +{ + unsigned char mouse_cbuf[CMDBUF_SIZE + 1]; /* buffer for current input */ + char *mouse_extra = NULL; + int mouse_cbuf_pos; /* current position in mouse_cbuf */ + int mouse_action; /* what action is in current input ? */ + + mouse_cbuf[0] = *c; + mouse_cbuf[1] = '\0'; + mouse_cbuf_pos = 1; + + while (((mouse_action = mcmd_decode(mouse_cbuf, &mouse_extra)) == A_PREFIX) && + (mouse_cbuf_pos < CMDBUF_SIZE)) + { + /* + * current input contains prefix for an action + * read next char + */ + mouse_cbuf[mouse_cbuf_pos++] = getcc(); + mouse_cbuf[mouse_cbuf_pos] = '\0'; + mouse_extra = NULL; + } + + switch (mouse_action) + { + case A_MOUSE: + /* + * a mouse button pressed or released + */ + process_mouse_button(M_MULTICHAR_COMMAND); + *c = getcc(); + return 1; + case A_MOUSE_END_SEL_IN: + /* + * end of mouse selection + * left mouse button was released inside of given area + */ + process_mouse_end_selection_in(); + *c = getcc(); + return 1; + case A_MOUSE_END_SEL_OUT: + /* + * end of mouse selection + * left mouse button was released outside of given area + */ + process_mouse_end_selection_out(); + *c = getcc(); + return 1; + default: + /* + * no mouse action + * unget all characters + */ + while (--mouse_cbuf_pos > 0) + ungetcc(mouse_cbuf[mouse_cbuf_pos]); + *c = mouse_cbuf[0]; + return 0; + } + return 0; +} + +/* * Main command processor. * Accept and execute commands until a quit command. */ public void commands() { - register int c; + int c; register int action; register char *cbuf; int newaction; @@ -929,6 +1138,7 @@ * action to be performed. */ if (mca) + while (save_mouse_action(&c)); /* process all escape sequences with mouse actions */ switch (mca_char(c)) { case MCA_MORE: @@ -999,6 +1209,29 @@ switch (action) { + case A_MOUSE: + /* + * a mouse button pressed or released + */ + process_mouse_button(M_NORMAL); + break; + + case A_MOUSE_END_SEL_IN: + /* + * end of mouse selection + * left mouse button was released inside of given area + */ + process_mouse_end_selection_in(); + break; + + case A_MOUSE_END_SEL_OUT: + /* + * end of mouse selection + * left mouse button was released outside of given area + */ + process_mouse_end_selection_out(); + break; + case A_DIGIT: /* * First digit of a number. --- decode.c +++ decode.c @@ -165,6 +165,13 @@ 'Z','Z',0, A_QUIT }; +static unsigned char mousetable[] = +{ + ESC,'[','M',0, A_MOUSE, + ESC,'[','t',0, A_MOUSE_END_SEL_IN, + ESC,'[','T',0, A_MOUSE_END_SEL_OUT +}; + static unsigned char edittable[] = { '\t',0, EC_F_COMPLETE, /* TAB */ @@ -217,6 +224,7 @@ * List of command tables and list of line-edit tables. */ static struct tablelist *list_fcmd_tables = NULL; +static struct tablelist *list_mcmd_tables = NULL; static struct tablelist *list_ecmd_tables = NULL; static struct tablelist *list_var_tables = NULL; static struct tablelist *list_sysvar_tables = NULL; @@ -292,8 +300,10 @@ /* * Add the default command tables. */ + add_fcmd_table((char*)mousetable, sizeof(mousetable)); add_fcmd_table((char*)cmdtable, sizeof(cmdtable)); add_ecmd_table((char*)edittable, sizeof(edittable)); + add_mcmd_table((char*)mousetable, sizeof(mousetable)); #if USERFILE /* * For backwards compatibility, @@ -368,6 +378,18 @@ } /* + * Add an mouse command table. + */ + public void +add_mcmd_table(buf, len) + char *buf; + int len; +{ + if (add_cmd_table(&list_mcmd_tables, buf, len) < 0) + error("Warning: some mouse commands disabled", NULL_PARG); +} + +/* * Add an environment variable table. */ static void @@ -522,6 +544,17 @@ } /* + * Decode a command from the mousetables list. + */ + public int +mcmd_decode(cmd, sp) + char *cmd; + char **sp; +{ + return (cmd_decode(list_mcmd_tables, cmd, sp)); +} + +/* * Get the value of an environment variable. * Looks first in the lesskey file, then in the real environment. */ --- defines.h.in +++ defines.h.in @@ -183,7 +183,7 @@ * Sizes of various buffers. */ #define CMDBUF_SIZE 512 /* Buffer for multichar commands */ -#define UNGOT_SIZE 100 /* Max chars to unget() */ +#define UNGOT_SIZE 10000 /* Max chars to unget() */ #define LINEBUF_SIZE 1024 /* Max size of line in input file */ #define OUTBUF_SIZE 1024 /* Output buffer */ #define PROMPT_SIZE 200 /* Max size of prompt string */ --- funcs.h +++ funcs.h @@ -83,8 +83,10 @@ public void init_cmds (); public void add_fcmd_table (); public void add_ecmd_table (); + public void add_mcmd_table (); public int fcmd_decode (); public int ecmd_decode (); + public int mcmd_decode (); public char * lgetenv (); public int lesskey (); public void add_hometable (); @@ -181,6 +183,7 @@ public void gomark (); public POSITION markpos (); public void unmark (); + public void opt_A (); public void opt_o (); public void opt__O (); public void opt_l (); @@ -266,3 +269,10 @@ public void open_getchr (); public void close_getchr (); public int getchr (); + public void clear_selection(); + public int create_selection(int, int, int, int); + public int write_xselection(); + public int read_xselection(); + public int is_xselection_available(); + public void init_mouse_support(); + public void deinit_mouse_support(); --- less.hlp +++ less.hlp @@ -111,6 +111,8 @@ Display help (from command line). -a ........ --search-skip-screen Forward search skips current screen. + -A ........ --mouse-support + Use less mouse support (works only in xterm) -b [_N] .... --buffers=[_N] Number of buffers. -B ........ --auto-buffers --- less.nro +++ less.nro @@ -10,7 +10,7 @@ .br .B "less \-\-version" .br -.B "less [\-[+]aBcCdeEfFgGiIJKLmMnNqQrRsSuUVwWX~]" +.B "less [\-[+]aABcCdeEfFgGiIJKLmMnNqQrRsSuUVwWX~]" .br .B " [\-b \fIspace\fP] [\-h \fIlines\fP] [\-j \fIline\fP] [\-k \fIkeyfile\fP]" .br @@ -453,6 +453,22 @@ thus skipping all lines displayed on the screen. By default, searches start at the second line on the screen (or after the last found line; see the \-j option). +.IP "-A or --mouse-support" +Causes less to process mouse actions itself. Currently +it works only in xterm. The inspiration comes from vim-6.0. +.sp +Cut&Paste function works only internally by default. To access +X selection you must hold SHIFT key to process the mouse actions +by xterm. Or you can install +.I xselection +utility by which less +is able to access X selection itself. +.sp +Also scrolling by mouse wheel button is supported. You must map +wheel mouse action on the 4th and 5th mouse button. This is done +in XF86Config in Section "InputDevice" by option: +.sp + Option "ZAxisMapping" "4 5" .IP "\-b\fIn\fP or \-\-buffers=\fIn\fP" Specifies the amount of buffer space .I less --- main.c +++ main.c @@ -32,6 +32,7 @@ public int quitting; public int secure; public int dohelp; +public int xselection = 0; /* is xselection utility available ? */ public int less_is_more; #if LOGFILE --- optfunc.c +++ optfunc.c @@ -64,7 +64,31 @@ extern int so_fg_color, so_bg_color; extern int bl_fg_color, bl_bg_color; #endif +extern int opt_mouse_support; +/* + * Handler for the -A option. + */ + /*ARGSUSED*/ + public void +opt_A(type, s) + int type; + char *s; +{ + switch (type) + { + case TOGGLE: + if (opt_mouse_support) + init_mouse_support(); + else + deinit_mouse_support(); + break; + case QUERY: + case INIT: + break; + } + return; +} #if LOGFILE /* --- opttbl.c +++ opttbl.c @@ -54,6 +54,7 @@ #if HILITE_SEARCH public int hilite_search; /* Highlight matched search patterns? */ #endif +public int opt_mouse_support; /* Less mouse support (works only with xterm) */ public int less_is_more = 0; /* Make compatible with POSIX more */ @@ -61,6 +62,7 @@ * Long option names. */ static struct optname a_optname = { "search-skip-screen", NULL }; +static struct optname A_optname = { "mouse-support", NULL }; static struct optname b_optname = { "buffers", NULL }; static struct optname B__optname = { "auto-buffers", NULL }; static struct optname c_optname = { "clear-screen", NULL }; @@ -137,7 +139,14 @@ NULL } }, - + { 'A', &A_optname, + BOOL, OPT_OFF, &opt_mouse_support, opt_A, + { + "Do not use less mouse support", + "Use less mouse support (works only in xterm)", + NULL + } + }, { 'b', &b_optname, NUMBER|INIT_HANDLER, 64, &bufspace, opt_b, { --- output.c +++ output.c @@ -40,6 +40,8 @@ extern int bl_fg_color, bl_bg_color; #endif +extern char *ungotp; + /* * Display the line which is in the line buffer. */ @@ -505,7 +507,27 @@ #else c = getchr(); if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR) + { + /* + * on the standard input can be a sequence (not only one char) + * we have read first char of the potential sequence and + * we want to unget it to internal less buffer but there + * can be older ungotten chracters + * + * we must not to put old ungotten chars inside this sequence + * + * 1. solution is to put this char at the begin of internal + * less buffer (LIFO) to use this character after all older + * ungotten chars. But the user can be mixed that the current + * command is done later + * + * 2. soluton is to discard all old ungotten chars + * + * I prefer the 2. solution + */ + ungotp = NULL; ungetcc(c); + } #endif } --- screen.c +++ screen.c @@ -241,6 +241,9 @@ extern char *tgetstr(); extern char *tgoto(); +extern int xselection; /* is xselection available */ +extern int opt_mouse_support; /* is less mouse support enabled (option -A ) */ +int mouse_support = 0; /* is less mouse support initialized ? */ /* * Change terminal to "raw mode", or restore to "normal" mode. @@ -1527,6 +1530,42 @@ #endif +public void +init_mouse_support() +{ + if (mouse_support) + /* nothing to do (less mouse support is already inicialized */ + return; + + /* save old highlight mouse tracking */ + printf("\033[?1001s"); + /* enable mouse tracking */ + printf("\033[?1001h"); + fflush(stdout); + mouse_support = 1; + + /* check if the xselection utility is available and is usable */ + xselection = is_xselection_available(); + + return; +} + +public void +deinit_mouse_support() +{ + if (!mouse_support) + /* nothing to do (mouse support was not inicialized) */ + return; + + /* disable mouse tracking */ + printf("\033[?1001l"); + /* restore old highlight mouse tracking */ + printf("\033[?1001r"); + fflush(stdout); + mouse_support = 0; + return; +} + /* * Initialize terminal */ @@ -1538,6 +1577,8 @@ tputs(sc_init, sc_height, putchr); if (!no_keypad) tputs(sc_s_keypad, sc_height, putchr); + if (opt_mouse_support) + init_mouse_support(); if (top_scroll) { int i; @@ -1575,6 +1616,7 @@ tputs(sc_e_keypad, sc_height, putchr); if (!no_init) tputs(sc_deinit, sc_height, putchr); + deinit_mouse_support(); #else /* Restore system colors. */ SETCOLORS(sy_fg_color, sy_bg_color); --- selection.c +++ selection.c @@ -0,0 +1,271 @@ +/* + * Copyright (C) 1984-2000 Mark Nudelman + * + * You may distribute under the terms of either the GNU General Public + * License or the Less License, as specified in the README file. + * + * For more information about less, or for information on how to + * contact the author, see the README file. + */ +/* + * Copyright (c) 1997-2000 Kazushi (Jam) Marukawa + * All rights of japanized routines are reserved. + * + * You may distribute under the terms of the Less License. + */ + + +/* + * User-level command processor. + */ + +#include +#include "less.h" +#include "position.h" +#include "option.h" +#include "cmd.h" + +#define IS_CONT(c) (((c) & 0xC0) == 0x80) +#define SELECTION_SIZE_STEP 1000 + +char *selection=NULL; /* buffer for less internal selection */ +int max_selection_size=0; /* current size of the buffer for less */ + /* internal selection */ +int selection_size=0; /* current size of less internal selection */ + +extern int utf_mode; + +/* + * clear less internal selection + */ + public void +clear_selection() +{ + if (selection) + { + free(selection); + selection=NULL; + max_selection_size=0; + selection_size=0; + } + return; +} + +/* + * expand the size of less internal selection buffer + */ + static int +expand_selection_buffer(size) + int size; /* new size will be: max_selection_size + size */ +{ + char *new_selection; + + if (!selection) + { + /* buffer for selection isn't allocated */ + selection = (char*)malloc((size + 1) * sizeof(char)); + if (!selection) + { + error("WARNING: Not enough memory for selection", NULL_PARG); + clear_selection(); + return 0; + } + max_selection_size = size; + selection_size = 0; + *selection = '\0'; + return 1; + } + + if (size > 0) + { + new_selection = (char*)malloc((max_selection_size + size + 1) * sizeof(char)); + if (!new_selection) + { + error("WARNING: Not enough memory for selection", NULL_PARG); + clear_selection(); + return 0; + } + memcpy(new_selection, selection, max_selection_size + 1); + free(selection); + selection = new_selection; + max_selection_size += size; + return 1; + } + return 0; +} + + +/* + * add a char into less internal selection + */ + static int +add_char_to_selection(c) + int c; +{ + if (selection_size >= max_selection_size) + if (!(expand_selection_buffer(SELECTION_SIZE_STEP))) + return 0; + + /* finally add new char */ + selection[selection_size++] = c; + selection[selection_size] = '\0'; + return 1; +} + +/* + * add more chars into less internal selection + */ + static int +add_buf_to_selection(buf, size) + char *buf; + int size; +{ + if ((selection_size + size) > max_selection_size) + if (!(expand_selection_buffer(size))) + return 0; + + /* finally add buf to selection */ + memcpy(selection + selection_size, buf, size); + selection_size += size; + selection[selection_size] = '\0'; + return 1; +} + + +/* + * create less internal selection + * x1, y1, x2, y2 - are coordinates of begin and end of selection + */ + public int +create_selection(x1, y1, x2, y2) + int x1; + int y1; + int x2; + int y2; +{ + int x, y; + int c, csp, ap; + POSITION pos; + + if ((x1 == x2) && (y1 == y2)) + /* no new selection */ + return 1; + + if (((y1 == y2) && (x1 > x2)) || (y1 > y2)) + { + /* we want to have x1,y1 as begin of selection */ + x = x1; y = y1; + x1 = x2; y1 = y2; + x2 = x; y2 = y; + } + + clear_selection(); + for (y = y1; y <= y2; y++) + { + pos = position(y); + forw_line(pos); + for (x = (y == y1) ? x1 : 0; + (((c = gline(x, &csp, &ap)) != '\0') && !((y == y2) && (x >= x2))); + x++) + { + if (!add_char_to_selection(c)) + /* something wrong */ + return 0; + if ((utf_mode) && (IS_CONT(c)) && (y == y2)) + /* it is multichar */ + ++x2; + } + } + return 2; +} + +/* + * write less internal selection to xselection + * it use the xselection utility because less isn't linked against Xlib + */ + public int +write_xselection() +{ + FILE *xsel; + + if ((xsel = popen("xselection PRIMARY -", "w")) == NULL) + /* can't open xselection */ + return 0; + + if (selection) + if (fwrite(selection, 1, selection_size, xsel) != selection_size) + { + error("WARNING: Can not write the whole selection to the xselection", NULL_PARG); + pclose(xsel); + return 0; + } + + if (pclose(xsel) != 0) + /* xselection didn't work correctly */ + return 0; + + /* everything OK */ + return 1; +} + + +/* + * read xselection to less internal selection + * it use the xselection utility because less isn't linked against Xlib + */ + public int +read_xselection() +{ + FILE *xsel; + char buf[SELECTION_SIZE_STEP]; + int read; + + if ((xsel = popen("xselection PRIMARY", "r")) == NULL) + /* can't open xselection */ + return 0; + + clear_selection(); + while ((read = fread(buf, 1, SELECTION_SIZE_STEP, xsel)) != 0) + { + if (!add_buf_to_selection(buf, read)) + { + /* something wrong */ + pclose(xsel); + return 0; + } + } + + if (pclose(xsel) != 0) + { + /* xselection didn't work correctly */ + clear_selection(); + return 0; + } + + /* everything OK */ + return 1; +} + +/* + * test if xselection utility is available + */ + public int +is_xselection_available() +{ + FILE *xsel; + char buf[SELECTION_SIZE_STEP]; + int read; + + if ((xsel = popen("xselection -help 2>/dev/null", "r")) == NULL) + /* can't open xselection */ + return 0; + + while ((read = fread(buf, 1, SELECTION_SIZE_STEP, xsel)) != 0); + + if (pclose(xsel) != 0) + /* xselection doesn't work correctly */ + return 0; + + /* everything OK */ + return 1; +}