Implement vim-like marks

Commands are 'mark' and 'goto'. Both can be used either directly,
like 'mark a' and 'goto a', or interactively (just 'mark'). For
interactive mode, i3-input must be installed and in your PATH.
This commit is contained in:
Michael Stapelberg 2009-09-20 16:54:29 +02:00
parent 6510b0e14f
commit 3ada8f326c
7 changed files with 200 additions and 32 deletions

View File

@ -6,6 +6,7 @@
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__); #define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
char *convert_ucs_to_utf8(char *input); char *convert_ucs_to_utf8(char *input);
char *convert_utf8_to_ucs2(char *input, int *real_strlen);
uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); uint32_t get_colorpixel(xcb_connection_t *conn, char *hex);
uint32_t get_mode_switch_mask(xcb_connection_t *conn); uint32_t get_mode_switch_mask(xcb_connection_t *conn);
int connect_ipc(char *socket_path); int connect_ipc(char *socket_path);

View File

@ -46,6 +46,9 @@ static char *glyphs_utf8[512];
static int input_position; static int input_position;
static int font_height; static int font_height;
static char *command_prefix; static char *command_prefix;
static char *prompt;
static int prompt_len;
static int limit;
/* /*
* Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
@ -88,13 +91,23 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
/* restore font color */ /* restore font color */
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF")); xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
uint8_t *con = concat_strings(glyphs_ucs, input_position); uint8_t *con = concat_strings(glyphs_ucs, input_position);
xcb_image_text_16(conn, input_position, pixmap, pixmap_gc, 4 /* X */, char *full_text = (char*)con;
font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)con); if (prompt != NULL) {
full_text = malloc((prompt_len + input_position) * 2 + 1);
if (full_text == NULL)
err(EXIT_FAILURE, "malloc() failed\n");
memcpy(full_text, prompt, prompt_len * 2);
memcpy(full_text + (prompt_len * 2), con, input_position * 2);
}
xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */,
font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text);
/* Copy the contents of the pixmap to the real window */ /* Copy the contents of the pixmap to the real window */
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8); xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8);
xcb_flush(conn); xcb_flush(conn);
free(con); free(con);
if (prompt != NULL)
free(full_text);
return 1; return 1;
} }
@ -115,6 +128,25 @@ static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_rel
return 1; return 1;
} }
static void finish_input() {
uint8_t *command = concat_strings(glyphs_utf8, input_position);
char *full_command = (char*)command;
/* prefix the command if a prefix was specified on commandline */
if (command_prefix != NULL) {
if (asprintf(&full_command, "%s%s", command_prefix, command) == -1)
err(EXIT_FAILURE, "asprintf() failed\n");
}
printf("command = %s\n", full_command);
ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command);
#if 0
free(command);
return 1;
#endif
exit(0);
}
/* /*
* Handles keypresses by converting the keycodes to keysymbols, then the * Handles keypresses by converting the keycodes to keysymbols, then the
* keysymbols to UCS-2. If the conversion succeeded, the glyph is saved in the * keysymbols to UCS-2. If the conversion succeeded, the glyph is saved in the
@ -138,24 +170,8 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
return 1; return 1;
} }
if (sym == XK_Return) { if (sym == XK_Return)
uint8_t *command = concat_strings(glyphs_utf8, input_position); finish_input();
char *full_command = (char*)command;
/* prefix the command if a prefix was specified on commandline */
if (command_prefix != NULL) {
if (asprintf(&full_command, "%s%s", command_prefix, command) == -1)
err(EXIT_FAILURE, "asprintf() failed\n");
}
printf("command = %s\n", full_command);
ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command);
#if 0
free(command);
return 1;
#endif
exit(0);
}
if (sym == XK_BackSpace) { if (sym == XK_BackSpace) {
if (input_position == 0) if (input_position == 0)
@ -208,6 +224,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
glyphs_utf8[input_position] = strdup(out); glyphs_utf8[input_position] = strdup(out);
input_position++; input_position++;
if (input_position == limit)
finish_input();
handle_expose(NULL, conn, NULL); handle_expose(NULL, conn, NULL);
return 1; return 1;
} }
@ -220,30 +239,43 @@ int main(int argc, char *argv[]) {
static struct option long_options[] = { static struct option long_options[] = {
{"socket", required_argument, 0, 's'}, {"socket", required_argument, 0, 's'},
{"version", no_argument, 0, 'v'}, {"version", no_argument, 0, 'v'},
{"limit", required_argument, 0, 'l'},
{"prompt", required_argument, 0, 'P'},
{"prefix", required_argument, 0, 'p'}, {"prefix", required_argument, 0, 'p'},
{"help", no_argument, 0, 'h'}, {"help", no_argument, 0, 'h'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
char *options_string = "s:p:vh"; char *options_string = "s:p:P:l:vh";
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) { while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
if (o == 's') { switch (o) {
case 's':
socket_path = strdup(optarg); socket_path = strdup(optarg);
} else if (o == 'v') { break;
case 'v':
printf("i3-input " I3_VERSION); printf("i3-input " I3_VERSION);
return 0; return 0;
} else if (o == 'p') { case 'p':
command_prefix = strdup(optarg); command_prefix = strdup(optarg);
} else if (o == 'h') { break;
case 'l':
limit = atoi(optarg);
break;
case 'P':
prompt = strdup(optarg);
break;
case 'h':
printf("i3-input " I3_VERSION); printf("i3-input " I3_VERSION);
printf("i3-input [-s <socket>] [-p <prefix>]\n"); printf("i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-v]\n");
return 0; return 0;
} }
} }
sockfd = connect_ipc(socket_path); sockfd = connect_ipc(socket_path);
prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
int screens; int screens;
xcb_connection_t *conn = xcb_connect(NULL, &screens); xcb_connection_t *conn = xcb_connect(NULL, &screens);
if (xcb_connection_has_error(conn)) if (xcb_connection_has_error(conn))

View File

@ -10,10 +10,12 @@
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <err.h> #include <err.h>
#include <iconv.h> #include <iconv.h>
static iconv_t conversion_descriptor = 0; static iconv_t conversion_descriptor = 0;
static iconv_t conversion_descriptor2 = 0;
/* /*
* Returns the input string, but converted from UCS-2 to UTF-8. Memory will be * Returns the input string, but converted from UCS-2 to UTF-8. Memory will be
@ -53,3 +55,50 @@ char *convert_ucs_to_utf8(char *input) {
return buffer; return buffer;
} }
/*
* Converts the given string to UCS-2 big endian for use with
* xcb_image_text_16(). The amount of real glyphs is stored in real_strlen,
* a buffer containing the UCS-2 encoded string (16 bit per glyph) is
* returned. It has to be freed when done.
*
*/
char *convert_utf8_to_ucs2(char *input, int *real_strlen) {
size_t input_size = strlen(input) + 1;
/* UCS-2 consumes exactly two bytes for each glyph */
int buffer_size = input_size * 2;
char *buffer = malloc(buffer_size);
if (buffer == NULL)
err(EXIT_FAILURE, "malloc() failed\n");
size_t output_size = buffer_size;
/* We need to use an additional pointer, because iconv() modifies it */
char *output = buffer;
/* We convert the input into UCS-2 big endian */
if (conversion_descriptor2 == 0) {
conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8");
if (conversion_descriptor2 == 0) {
fprintf(stderr, "error opening the conversion context\n");
exit(1);
}
}
/* Get the conversion descriptor back to original state */
iconv(conversion_descriptor2, NULL, NULL, NULL, NULL);
/* Convert our text */
int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size);
if (rc == (size_t)-1) {
perror("Converting to UCS-2 failed");
if (real_strlen != NULL)
*real_strlen = 0;
return NULL;
}
if (real_strlen != NULL)
*real_strlen = ((buffer_size - output_size) / 2) - 1;
return buffer;
}

View File

@ -97,6 +97,13 @@ void client_unmap(xcb_connection_t *conn, Client *client);
*/ */
void client_map(xcb_connection_t *conn, Client *client); void client_map(xcb_connection_t *conn, Client *client);
/**
* Set the given mark for this client. Used for jumping to the client
* afterwards (like m<mark> and '<mark> in vim).
*
*/
void client_mark(xcb_connection_t *conn, Client *client, const char *mark);
/** /**
* Pretty-prints the clients information into the logfile. * Pretty-prints the clients information into the logfile.
* *

View File

@ -376,6 +376,9 @@ struct Client {
/** Holds the WM_CLASS, useful for matching the client in commands */ /** Holds the WM_CLASS, useful for matching the client in commands */
char *window_class; char *window_class;
/** Holds the clients mark, for vim-like jumping */
char *mark;
/** Holds the xcb_window_t (just an ID) for the leader window (logical /** Holds the xcb_window_t (just an ID) for the leader window (logical
* parent for toolwindows and similar floating windows) */ * parent for toolwindows and similar floating windows) */
xcb_window_t leader; xcb_window_t leader;

View File

@ -24,6 +24,7 @@
#include "queue.h" #include "queue.h"
#include "layout.h" #include "layout.h"
#include "client.h" #include "client.h"
#include "table.h"
/* /*
* Removes the given client from the container, either because it will be inserted into another * Removes the given client from the container, either because it will be inserted into another
@ -316,3 +317,30 @@ void client_map(xcb_connection_t *conn, Client *client) {
xcb_map_window(conn, client->frame); xcb_map_window(conn, client->frame);
} }
/*
* Set the given mark for this client. Used for jumping to the client
* afterwards (like m<mark> and '<mark> in vim).
*
*/
void client_mark(xcb_connection_t *conn, Client *client, const char *mark) {
if (client->mark != NULL)
free(client->mark);
client->mark = sstrdup(mark);
/* Make sure no other client has this mark set */
Client *current;
for (int c = 0; c < 10; c++)
SLIST_FOREACH(current, &(workspaces[c].focus_stack), focus_clients) {
if (current == client ||
current->mark == NULL ||
strcmp(current->mark, mark) != 0)
continue;
free(current->mark);
current->mark = NULL;
/* We can break here since there can only be one other
* client with this mark. */
break;
}
}

View File

@ -58,6 +58,22 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir
typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t; typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t;
static void jump_to_mark(xcb_connection_t *conn, const char *mark) {
Client *current;
LOG("Jumping to \"%s\"\n", mark);
for (int c = 0; c < 10; c++)
SLIST_FOREACH(current, &(workspaces[c].focus_stack), focus_clients) {
if (current->mark == NULL || strcmp(current->mark, mark) != 0)
continue;
set_focus(conn, current, true);
return;
}
LOG("No window with this mark found\n");
}
static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) { static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) {
LOG("focusing direction %d\n", direction); LOG("focusing direction %d\n", direction);
@ -817,6 +833,38 @@ void parse_command(xcb_connection_t *conn, const char *command) {
return; return;
} }
if (STARTS_WITH(command, "mark")) {
if (last_focused == NULL) {
LOG("There is no window to mark\n");
return;
}
const char *rest = command + strlen("mark");
while (*rest == ' ')
rest++;
if (*rest == '\0') {
LOG("interactive mark starting\n");
start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '");
} else {
LOG("mark with \"%s\"\n", rest);
client_mark(conn, last_focused, rest);
}
return;
}
if (STARTS_WITH(command, "goto")) {
const char *rest = command + strlen("goto");
while (*rest == ' ')
rest++;
if (*rest == '\0') {
LOG("interactive go to mark starting\n");
start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '");
} else {
LOG("go to \"%s\"\n", rest);
jump_to_mark(conn, rest);
}
return;
}
/* Is it an <exit>? */ /* Is it an <exit>? */
if (STARTS_WITH(command, "exit")) { if (STARTS_WITH(command, "exit")) {
LOG("User issued exit-command, exiting without error.\n"); LOG("User issued exit-command, exiting without error.\n");