diff --git a/docs/userguide b/docs/userguide index 667cdde9..41452793 100644 --- a/docs/userguide +++ b/docs/userguide @@ -1,7 +1,7 @@ i3 User’s Guide =============== Michael Stapelberg -January 2012 +April 2012 This document contains all the information you need to configure and use the i3 window manager. If it does not, please contact us on IRC (preferred) or post your @@ -1356,6 +1356,24 @@ to switch to the workspace which begins with number 1, regardless of which name it has. This is useful in case you are changing the workspace’s name dynamically. +=== Renaming workspaces + +You can rename workspaces. This might be useful to start with the default +numbered workspaces, do your work, and rename the workspaces afterwards to +reflect what’s actually on them. + +*Syntax*: +---------------------------------------------------- +rename workspace to +---------------------------------------------------- + +*Examples*: +------------------------------------------------ +i3-msg 'rename workspace 5 to 6' +i3-msg 'rename workspace 1 to "1: www"' +i3-msg 'rename workspace "1: www" to "10: www"' +------------------------------------------------ + [[resizingconfig]] === Resizing containers/windows diff --git a/include/commands.h b/include/commands.h index 94200741..85057d19 100644 --- a/include/commands.h +++ b/include/commands.h @@ -259,4 +259,10 @@ void cmd_move_scratchpad(I3_CMD); */ void cmd_scratchpad_show(I3_CMD); +/** + * Implementation of 'rename workspace to ' + * + */ +void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name); + #endif diff --git a/parser-specs/commands.spec b/parser-specs/commands.spec index aab5529d..684fd23e 100644 --- a/parser-specs/commands.spec +++ b/parser-specs/commands.spec @@ -31,6 +31,7 @@ state INITIAL: 'floating' -> FLOATING 'mark' -> MARK 'resize' -> RESIZE + 'rename' -> RENAME 'nop' -> NOP 'scratchpad' -> SCRATCHPAD 'mode' -> MODE @@ -173,6 +174,21 @@ state RESIZE_TILING_OR: end -> call cmd_resize($way, $direction, $resize_px, $resize_ppt) +# rename workspace to +state RENAME: + 'workspace' + -> RENAME_WORKSPACE + +state RENAME_WORKSPACE: + old_name = word + -> RENAME_WORKSPACE_TO + +state RENAME_WORKSPACE_TO: + 'to' + -> + new_name = string + -> call cmd_rename_workspace($old_name, $new_name) + # move [ [px]] # move [window|container] [to] workspace # move [window|container] [to] output diff --git a/src/commands.c b/src/commands.c index 1eff7390..e8fb7f90 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1556,3 +1556,63 @@ void cmd_scratchpad_show(I3_CMD) { // XXX: default reply for now, make this a better reply cmd_output->json_output = sstrdup("{\"success\": true}"); } + +/* + * Implementation of 'rename workspace to ' + * + */ +void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) { + LOG("Renaming workspace \"%s\" to \"%s\"\n", old_name, new_name); + + Con *output, *workspace = NULL; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(workspace, output_get_content(output), + !strcasecmp(child->name, old_name)); + + if (!workspace) { + // TODO: we should include the old workspace name here and use yajl for + // generating the reply. + cmd_output->json_output = sstrdup("{\"success\": false, " + "\"error\":\"Old workspace not found\"}"); + return; + } + + Con *check_dest = NULL; + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) + GREP_FIRST(check_dest, output_get_content(output), + !strcasecmp(child->name, new_name)); + + if (check_dest != NULL) { + // TODO: we should include the new workspace name here and use yajl for + // generating the reply. + cmd_output->json_output = sstrdup("{\"success\": false, " + "\"error\":\"New workspace already exists\"}"); + return; + } + + /* Change the name and try to parse it as a number. */ + FREE(workspace->name); + workspace->name = sstrdup(new_name); + char *endptr = NULL; + long parsed_num = strtol(new_name, &endptr, 10); + if (parsed_num == LONG_MIN || + parsed_num == LONG_MAX || + parsed_num < 0 || + endptr == new_name) + workspace->num = -1; + else workspace->num = parsed_num; + LOG("num = %d\n", workspace->num); + + /* By re-attaching, the sort order will be correct afterwards. */ + Con *previously_focused = focused; + Con *parent = workspace->parent; + con_detach(workspace); + con_attach(workspace, parent, false); + /* Restore the previous focus since con_attach messes with the focus. */ + con_focus(previously_focused); + + cmd_output->needs_tree_render = true; + cmd_output->json_output = sstrdup("{\"success\": true}"); + + ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}"); +} diff --git a/src/workspace.c b/src/workspace.c index 75b8a7b3..87e26049 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -52,8 +52,8 @@ Con *workspace_get(const char *num, bool *created) { workspace->type = CT_WORKSPACE; FREE(workspace->name); workspace->name = sstrdup(num); - /* We set ->num to the number if this workspace’s name consists only of - * a positive number. Otherwise it’s a named ws and num will be -1. */ + /* We set ->num to the number if this workspace’s name begins with a + * positive number. Otherwise it’s a named ws and num will be -1. */ char *endptr = NULL; long parsed_num = strtol(num, &endptr, 10); if (parsed_num == LONG_MIN || diff --git a/testcases/t/117-workspace.t b/testcases/t/117-workspace.t index 9f4d270c..6c9fc6e1 100644 --- a/testcases/t/117-workspace.t +++ b/testcases/t/117-workspace.t @@ -134,4 +134,83 @@ cmd 'workspace number 4'; is(focused_ws(), '4: foo', 'now on workspace 4: foo'); ok(!workspace_exists('4'), 'workspace 4 still does not exist'); +################################################################################ +# Verify that renaming workspaces works. +################################################################################ + +sub workspace_numbers_sorted { + my ($name) = @_; + my $i3 = i3(get_socket_path()); + my $tree = $i3->get_tree->recv; + + my @outputs = @{$tree->{nodes}}; + my @workspaces; + for my $output (@outputs) { + # get the first CT_CON of each output + my $content = first { $_->{type} == 2 } @{$output->{nodes}}; + @workspaces = (@workspaces, @{$content->{nodes}}); + } + + my @numbers = grep { $_ != -1 } map { $_->{num} } @workspaces; + is_deeply( + [ sort { $a <=> $b } @numbers ], + \@numbers, + 'workspace numbers sorted'); +} + +# 1: numbered workspace +cmd 'workspace 10'; +cmd 'open'; +cmd 'workspace 13'; +cmd 'open'; + +workspace_numbers_sorted(); + +cmd 'workspace 9'; +is(focused_ws(), '9', 'now on workspace 9'); + +ok(!workspace_exists('12'), 'workspace 12 does not exist yet'); +cmd 'rename workspace 9 to 12'; +ok(!workspace_exists('9'), 'workspace 9 does not exist anymore'); +is(focused_ws(), '12', 'now on workspace 12'); +$ws = get_ws('12'); +is($ws->{num}, 12, 'number correctly changed'); + +workspace_numbers_sorted(); + +# 2: numbered + named workspace +cmd 'workspace 9: foo'; +is(focused_ws(), '9: foo', 'now on workspace 9: foo'); + +ok(!workspace_exists('11: bar'), 'workspace 11: bar does not exist yet'); +cmd 'rename workspace "9: foo" to "11: bar"'; +ok(!workspace_exists('9: foo'), 'workspace 9 does not exist anymore'); +is(focused_ws(), '11: bar', 'now on workspace 10'); +$ws = get_ws('11: bar'); +is($ws->{num}, 11, 'number correctly changed'); +workspace_numbers_sorted(); +# keep that one open, we need it later +cmd 'open'; + +# 3: named workspace +cmd 'workspace bleh'; +is(focused_ws(), 'bleh', 'now on workspace bleh'); + +ok(!workspace_exists('qux'), 'workspace qux does not exist yet'); +cmd 'rename workspace bleh to qux'; +ok(!workspace_exists('bleh'), 'workspace 9 does not exist anymore'); +is(focused_ws(), 'qux', 'now on workspace qux'); +$ws = get_ws('qux'); +is($ws->{num}, -1, 'number correctly changed'); +workspace_numbers_sorted(); + +# 5: already existing workspace +my $result = cmd 'rename workspace qux to 11: bar'; +ok(!$result->[0]->{success}, 'renaming workspace to an already existing one failed'); + +# 6: non-existing old workspace (verify command result) +$result = cmd 'rename workspace notexistant to bleh'; +ok(!$result->[0]->{success}, 'renaming workspace which does not exist failed'); + + done_testing; diff --git a/testcases/t/187-commands-parser.t b/testcases/t/187-commands-parser.t index 7e6f97bd..8b57a0a1 100644 --- a/testcases/t/187-commands-parser.t +++ b/testcases/t/187-commands-parser.t @@ -127,7 +127,7 @@ is(parser_calls("\nworkspace test"), ################################################################################ is(parser_calls('unknown_literal'), - "Expected one of these tokens: , '[', 'move', 'exec', 'exit', 'restart', 'reload', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'resize', 'nop', 'scratchpad', 'mode'\n" . + "Expected one of these tokens: , '[', 'move', 'exec', 'exit', 'restart', 'reload', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'resize', 'rename', 'nop', 'scratchpad', 'mode'\n" . "Your command: unknown_literal\n" . " ^^^^^^^^^^^^^^^", 'error for unknown literal ok');