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:
parent
6510b0e14f
commit
3ada8f326c
|
@ -6,6 +6,7 @@
|
|||
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
|
||||
|
||||
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_mode_switch_mask(xcb_connection_t *conn);
|
||||
int connect_ipc(char *socket_path);
|
||||
|
|
|
@ -46,6 +46,9 @@ static char *glyphs_utf8[512];
|
|||
static int input_position;
|
||||
static int font_height;
|
||||
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
|
||||
|
@ -88,13 +91,23 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
|
|||
/* restore font color */
|
||||
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
|
||||
uint8_t *con = concat_strings(glyphs_ucs, input_position);
|
||||
xcb_image_text_16(conn, input_position, pixmap, pixmap_gc, 4 /* X */,
|
||||
font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)con);
|
||||
char *full_text = (char*)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 */
|
||||
xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8);
|
||||
xcb_flush(conn);
|
||||
free(con);
|
||||
if (prompt != NULL)
|
||||
free(full_text);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -115,6 +128,25 @@ static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_rel
|
|||
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
|
||||
* 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;
|
||||
}
|
||||
|
||||
if (sym == XK_Return) {
|
||||
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);
|
||||
}
|
||||
if (sym == XK_Return)
|
||||
finish_input();
|
||||
|
||||
if (sym == XK_BackSpace) {
|
||||
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);
|
||||
input_position++;
|
||||
|
||||
if (input_position == limit)
|
||||
finish_input();
|
||||
|
||||
handle_expose(NULL, conn, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
@ -220,30 +239,43 @@ int main(int argc, char *argv[]) {
|
|||
static struct option long_options[] = {
|
||||
{"socket", required_argument, 0, 's'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"limit", required_argument, 0, 'l'},
|
||||
{"prompt", required_argument, 0, 'P'},
|
||||
{"prefix", required_argument, 0, 'p'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{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) {
|
||||
if (o == 's') {
|
||||
switch (o) {
|
||||
case 's':
|
||||
socket_path = strdup(optarg);
|
||||
} else if (o == 'v') {
|
||||
break;
|
||||
case 'v':
|
||||
printf("i3-input " I3_VERSION);
|
||||
return 0;
|
||||
} else if (o == 'p') {
|
||||
case 'p':
|
||||
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 [-s <socket>] [-p <prefix>]\n");
|
||||
printf("i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-v]\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sockfd = connect_ipc(socket_path);
|
||||
|
||||
prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
|
||||
|
||||
int screens;
|
||||
xcb_connection_t *conn = xcb_connect(NULL, &screens);
|
||||
if (xcb_connection_has_error(conn))
|
||||
|
|
|
@ -10,10 +10,12 @@
|
|||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <err.h>
|
||||
#include <iconv.h>
|
||||
|
||||
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
|
||||
|
@ -53,3 +55,50 @@ char *convert_ucs_to_utf8(char *input) {
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -97,6 +97,13 @@ void client_unmap(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 client’s information into the logfile.
|
||||
*
|
||||
|
|
|
@ -376,6 +376,9 @@ struct Client {
|
|||
/** Holds the WM_CLASS, useful for matching the client in commands */
|
||||
char *window_class;
|
||||
|
||||
/** Holds the client’s mark, for vim-like jumping */
|
||||
char *mark;
|
||||
|
||||
/** Holds the xcb_window_t (just an ID) for the leader window (logical
|
||||
* parent for toolwindows and similar floating windows) */
|
||||
xcb_window_t leader;
|
||||
|
|
28
src/client.c
28
src/client.c
|
@ -24,6 +24,7 @@
|
|||
#include "queue.h"
|
||||
#include "layout.h"
|
||||
#include "client.h"
|
||||
#include "table.h"
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
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) {
|
||||
LOG("focusing direction %d\n", direction);
|
||||
|
||||
|
@ -817,6 +833,38 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
|||
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>? */
|
||||
if (STARTS_WITH(command, "exit")) {
|
||||
LOG("User issued exit-command, exiting without error.\n");
|
||||
|
|
Loading…
Reference in New Issue