xref: /openbsd-src/usr.bin/tmux/cmd-set-option.c (revision 1693b10bbdb876791a8ba7277dd26d2fd5e7aff8)
1 /* $OpenBSD: cmd-set-option.c,v 1.139 2021/08/20 19:50:17 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include "tmux.h"
25 
26 /*
27  * Set an option.
28  */
29 
30 static enum cmd_retval	cmd_set_option_exec(struct cmd *, struct cmdq_item *);
31 
32 const struct cmd_entry cmd_set_option_entry = {
33 	.name = "set-option",
34 	.alias = "set",
35 
36 	.args = { "aFgopqst:uUw", 1, 2 },
37 	.usage = "[-aFgopqsuUw] " CMD_TARGET_PANE_USAGE " option [value]",
38 
39 	.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
40 
41 	.flags = CMD_AFTERHOOK,
42 	.exec = cmd_set_option_exec
43 };
44 
45 const struct cmd_entry cmd_set_window_option_entry = {
46 	.name = "set-window-option",
47 	.alias = "setw",
48 
49 	.args = { "aFgoqt:u", 1, 2 },
50 	.usage = "[-aFgoqu] " CMD_TARGET_WINDOW_USAGE " option [value]",
51 
52 	.target = { 't', CMD_FIND_WINDOW, CMD_FIND_CANFAIL },
53 
54 	.flags = CMD_AFTERHOOK,
55 	.exec = cmd_set_option_exec
56 };
57 
58 const struct cmd_entry cmd_set_hook_entry = {
59 	.name = "set-hook",
60 	.alias = NULL,
61 
62 	.args = { "agpRt:uw", 1, 2 },
63 	.usage = "[-agpRuw] " CMD_TARGET_PANE_USAGE " hook [command]",
64 
65 	.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
66 
67 	.flags = CMD_AFTERHOOK,
68 	.exec = cmd_set_option_exec
69 };
70 
71 static enum cmd_retval
72 cmd_set_option_exec(struct cmd *self, struct cmdq_item *item)
73 {
74 	struct args			*args = cmd_get_args(self);
75 	int				 append = args_has(args, 'a');
76 	struct cmd_find_state		*target = cmdq_get_target(item);
77 	struct window_pane		*loop;
78 	struct options			*oo;
79 	struct options_entry		*parent, *o, *po;
80 	char				*name, *argument, *expanded = NULL;
81 	char				*cause;
82 	const char			*value;
83 	int				 window, idx, already, error, ambiguous;
84 	int				 scope;
85 
86 	window = (cmd_get_entry(self) == &cmd_set_window_option_entry);
87 
88 	/* Expand argument. */
89 	argument = format_single_from_target(item, args_string(args, 0));
90 
91 	/* If set-hook -R, fire the hook straight away. */
92 	if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'R')) {
93 		notify_hook(item, argument);
94 		free(argument);
95 		return (CMD_RETURN_NORMAL);
96 	}
97 
98 	/* Parse option name and index. */
99 	name = options_match(argument, &idx, &ambiguous);
100 	if (name == NULL) {
101 		if (args_has(args, 'q'))
102 			goto out;
103 		if (ambiguous)
104 			cmdq_error(item, "ambiguous option: %s", argument);
105 		else
106 			cmdq_error(item, "invalid option: %s", argument);
107 		goto fail;
108 	}
109 	if (args_count(args) < 2)
110 		value = NULL;
111 	else
112 		value = args_string(args, 1);
113 	if (value != NULL && args_has(args, 'F')) {
114 		expanded = format_single_from_target(item, value);
115 		value = expanded;
116 	}
117 
118 	/* Get the scope and table for the option .*/
119 	scope = options_scope_from_name(args, window, name, target, &oo,
120 	    &cause);
121 	if (scope == OPTIONS_TABLE_NONE) {
122 		if (args_has(args, 'q'))
123 			goto out;
124 		cmdq_error(item, "%s", cause);
125 		free(cause);
126 		goto fail;
127 	}
128 	o = options_get_only(oo, name);
129 	parent = options_get(oo, name);
130 
131 	/* Check that array options and indexes match up. */
132 	if (idx != -1 && (*name == '@' || !options_is_array(parent))) {
133 		cmdq_error(item, "not an array: %s", argument);
134 		goto fail;
135 	}
136 
137 	/* With -o, check this option is not already set. */
138 	if (!args_has(args, 'u') && args_has(args, 'o')) {
139 		if (idx == -1)
140 			already = (o != NULL);
141 		else {
142 			if (o == NULL)
143 				already = 0;
144 			else
145 				already = (options_array_get(o, idx) != NULL);
146 		}
147 		if (already) {
148 			if (args_has(args, 'q'))
149 				goto out;
150 			cmdq_error(item, "already set: %s", argument);
151 			goto fail;
152 		}
153 	}
154 
155 	/* Change the option. */
156 	if (args_has(args, 'U') && scope == OPTIONS_TABLE_WINDOW) {
157 		TAILQ_FOREACH(loop, &target->w->panes, entry) {
158 			po = options_get_only(loop->options, name);
159 			if (po == NULL)
160 				continue;
161 			if (options_remove_or_default(po, idx, &cause) != 0) {
162 				cmdq_error(item, "%s", cause);
163 				free(cause);
164 				goto fail;
165 			}
166 		}
167 	}
168 	if (args_has(args, 'u') || args_has(args, 'U')) {
169 		if (o == NULL)
170 			goto out;
171 		if (options_remove_or_default(o, idx, &cause) != 0) {
172 			cmdq_error(item, "%s", cause);
173 			free(cause);
174 			goto fail;
175 		}
176 	} else if (*name == '@') {
177 		if (value == NULL) {
178 			cmdq_error(item, "empty value");
179 			goto fail;
180 		}
181 		options_set_string(oo, name, append, "%s", value);
182 	} else if (idx == -1 && !options_is_array(parent)) {
183 		error = options_from_string(oo, options_table_entry(parent),
184 		    options_table_entry(parent)->name, value,
185 		    args_has(args, 'a'), &cause);
186 		if (error != 0) {
187 			cmdq_error(item, "%s", cause);
188 			free(cause);
189 			goto fail;
190 		}
191 	} else {
192 		if (value == NULL) {
193 			cmdq_error(item, "empty value");
194 			goto fail;
195 		}
196 		if (o == NULL)
197 			o = options_empty(oo, options_table_entry(parent));
198 		if (idx == -1) {
199 			if (!append)
200 				options_array_clear(o);
201 			if (options_array_assign(o, value, &cause) != 0) {
202 				cmdq_error(item, "%s", cause);
203 				free(cause);
204 				goto fail;
205 			}
206 		} else if (options_array_set(o, idx, value, append,
207 		    &cause) != 0) {
208 			cmdq_error(item, "%s", cause);
209 			free(cause);
210 			goto fail;
211 		}
212 	}
213 
214 	options_push_changes(name);
215 
216 out:
217 	free(argument);
218 	free(expanded);
219 	free(name);
220 	return (CMD_RETURN_NORMAL);
221 
222 fail:
223 	free(argument);
224 	free(expanded);
225 	free(name);
226 	return (CMD_RETURN_ERROR);
227 }
228