From 5c6f3d7266ab0c63015715f12c8e15eb144311a2 Mon Sep 17 00:00:00 2001
From: Brian Ashworth <bosrsf04@gmail.com>
Date: Tue, 27 Nov 2018 21:42:09 -0500
Subject: [PATCH] Change execute_command to return a list of results

This matches i3's behavior of returning a list of results that contain
the result of each command that was executed. Additionally, the
`parse_error` attribute has been added to the IPC JSON reply.
---
 include/sway/commands.h |  6 ++--
 sway/commands.c         | 67 +++++++++++++++++------------------------
 sway/commands/bind.c    | 23 +++++++++-----
 sway/ipc-server.c       | 11 +++++--
 sway/main.c             | 13 +++++---
 sway/tree/view.c        |  9 ++++--
 6 files changed, 70 insertions(+), 59 deletions(-)

diff --git a/include/sway/commands.h b/include/sway/commands.h
index eb446eae..c3913c79 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -56,7 +56,7 @@ struct cmd_handler *find_handler(char *line, struct cmd_handler *cmd_handlers,
  * all matching containers. Otherwise, it'll run on the `con` container. If
  * `con` is NULL then it'll run on the currently focused container.
  */
-struct cmd_results *execute_command(char *command,  struct sway_seat *seat,
+list_t *execute_command(char *command,  struct sway_seat *seat,
 		struct sway_container *con);
 /**
  * Parse and handles a command during config file loading.
@@ -82,11 +82,11 @@ struct cmd_results *cmd_results_new(enum cmd_status status, const char* input, c
  */
 void free_cmd_results(struct cmd_results *results);
 /**
- * Serializes cmd_results to a JSON string.
+ * Serializes a list of cmd_results to a JSON string.
  *
  * Free the JSON string later on.
  */
-char *cmd_results_to_json(struct cmd_results *results);
+char *cmd_results_to_json(list_t *res_list);
 
 struct cmd_results *add_color(const char *name,
 		char *buffer, const char *color);
diff --git a/sway/commands.c b/sway/commands.c
index bffc18f6..34e66ce9 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -215,12 +215,9 @@ static void set_config_node(struct sway_node *node) {
 	}
 }
 
-struct cmd_results *execute_command(char *_exec, struct sway_seat *seat,
+list_t *execute_command(char *_exec, struct sway_seat *seat,
 		struct sway_container *con) {
-	// Even though this function will process multiple commands we will only
-	// return the last error, if any (for now). (Since we have access to an
-	// error string we could e.g. concatenate all errors there.)
-	struct cmd_results *results = NULL;
+	list_t *res_list = create_list();
 	char *exec = strdup(_exec);
 	char *head = exec;
 	char *cmdlist;
@@ -254,8 +251,8 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat,
 			char *error = NULL;
 			struct criteria *criteria = criteria_parse(head, &error);
 			if (!criteria) {
-				results = cmd_results_new(CMD_INVALID, head,
-					"%s", error);
+				list_add(res_list, cmd_results_new(CMD_INVALID, head,
+					"%s", error));
 				free(error);
 				goto cleanup;
 			}
@@ -291,10 +288,8 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat,
 			}
 			struct cmd_handler *handler = find_handler(argv[0], NULL, 0);
 			if (!handler) {
-				if (results) {
-					free_cmd_results(results);
-				}
-				results = cmd_results_new(CMD_INVALID, cmd, "Unknown/invalid command");
+				list_add(res_list, cmd_results_new(CMD_INVALID, cmd,
+							"Unknown/invalid command"));
 				free_argv(argc, argv);
 				goto cleanup;
 			}
@@ -308,29 +303,21 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat,
 			if (!config->handler_context.using_criteria) {
 				set_config_node(node);
 				struct cmd_results *res = handler->handle(argc-1, argv+1);
-				if (res->status != CMD_SUCCESS) {
+				list_add(res_list, res);
+				if (res->status == CMD_INVALID) {
 					free_argv(argc, argv);
-					if (results) {
-						free_cmd_results(results);
-					}
-					results = res;
 					goto cleanup;
 				}
-				free_cmd_results(res);
 			} else {
 				for (int i = 0; i < views->length; ++i) {
 					struct sway_view *view = views->items[i];
 					set_config_node(&view->container->node);
 					struct cmd_results *res = handler->handle(argc-1, argv+1);
-					if (res->status != CMD_SUCCESS) {
+					list_add(res_list, res);
+					if (res->status == CMD_INVALID) {
 						free_argv(argc, argv);
-						if (results) {
-							free_cmd_results(results);
-						}
-						results = res;
 						goto cleanup;
 					}
-					free_cmd_results(res);
 				}
 			}
 			free_argv(argc, argv);
@@ -339,10 +326,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat,
 cleanup:
 	free(exec);
 	list_free(views);
-	if (!results) {
-		results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
-	}
-	return results;
+	return res_list;
 }
 
 // this is like execute_command above, except:
@@ -574,20 +558,25 @@ void free_cmd_results(struct cmd_results *results) {
 	free(results);
 }
 
-char *cmd_results_to_json(struct cmd_results *results) {
+char *cmd_results_to_json(list_t *res_list) {
 	json_object *result_array = json_object_new_array();
-	json_object *root = json_object_new_object();
-	json_object_object_add(root, "success",
-			json_object_new_boolean(results->status == CMD_SUCCESS));
-	if (results->input) {
-		json_object_object_add(
-				root, "input", json_object_new_string(results->input));
+	for (int i = 0; i < res_list->length; ++i) {
+		struct cmd_results *results = res_list->items[i];
+		json_object *root = json_object_new_object();
+		json_object_object_add(root, "success",
+				json_object_new_boolean(results->status == CMD_SUCCESS));
+		if (results->error) {
+			json_object_object_add(root, "parse_error",
+					json_object_new_boolean(results->status == CMD_INVALID));
+			json_object_object_add(
+					root, "error", json_object_new_string(results->error));
+		}
+		if (results->input) {
+			json_object_object_add(
+					root, "input", json_object_new_string(results->input));
+		}
+		json_object_array_add(result_array, root);
 	}
-	if (results->error) {
-		json_object_object_add(
-				root, "error", json_object_new_string(results->error));
-	}
-	json_object_array_add(result_array, root);
 	const char *json = json_object_to_json_string(result_array);
 	char *res = strdup(json);
 	json_object_put(result_array);
diff --git a/sway/commands/bind.c b/sway/commands/bind.c
index 08acbe7a..34881b0f 100644
--- a/sway/commands/bind.c
+++ b/sway/commands/bind.c
@@ -289,13 +289,20 @@ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding)
 	wlr_log(WLR_DEBUG, "running command for binding: %s", binding->command);
 
 	config->handler_context.seat = seat;
-	struct cmd_results *results = execute_command(binding->command, NULL, NULL);
-	if (results->status == CMD_SUCCESS) {
-		ipc_event_binding(binding);
-	} else {
-		wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)",
-			binding->command, results->error);
+	list_t *res_list = execute_command(binding->command, NULL, NULL);
+	bool success = true;
+	while (res_list->length) {
+		struct cmd_results *results = res_list->items[0];
+		if (results->status != CMD_SUCCESS) {
+			wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)",
+				binding->command, results->error);
+			success = false;
+		}
+		free_cmd_results(results);
+		list_del(res_list, 0);
+	}
+	list_free(res_list);
+	if (success) {
+		ipc_event_binding(binding);
 	}
-
-	free_cmd_results(results);
 }
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
index aa0f0fad..95433d97 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -597,13 +597,18 @@ void ipc_client_handle_command(struct ipc_client *client) {
 	switch (client->current_command) {
 	case IPC_COMMAND:
 	{
-		struct cmd_results *results = execute_command(buf, NULL, NULL);
+		list_t *res_list = execute_command(buf, NULL, NULL);
 		transaction_commit_dirty();
-		char *json = cmd_results_to_json(results);
+		char *json = cmd_results_to_json(res_list);
 		int length = strlen(json);
 		client_valid = ipc_send_reply(client, json, (uint32_t)length);
 		free(json);
-		free_cmd_results(results);
+		while (res_list->length) {
+			struct cmd_results *results = res_list->items[0];
+			free_cmd_results(results);
+			list_del(res_list, 0);
+		}
+		list_free(res_list);
 		goto exit_cleanup;
 	}
 
diff --git a/sway/main.c b/sway/main.c
index a21970e2..a74183fe 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -392,11 +392,16 @@ int main(int argc, char **argv) {
 	wlr_log(WLR_DEBUG, "Running deferred commands");
 	while (config->cmd_queue->length) {
 		char *line = config->cmd_queue->items[0];
-		struct cmd_results *res = execute_command(line, NULL, NULL);
-		if (res->status != CMD_SUCCESS) {
-			wlr_log(WLR_ERROR, "Error on line '%s': %s", line, res->error);
+		list_t *res_list = execute_command(line, NULL, NULL);
+		while (res_list->length) {
+			struct cmd_results *res = res_list->items[0];
+			if (res->status != CMD_SUCCESS) {
+				wlr_log(WLR_ERROR, "Error on line '%s': %s", line, res->error);
+			}
+			free_cmd_results(res);
+			list_del(res_list, 0);
 		}
-		free_cmd_results(res);
+		list_free(res_list);
 		free(line);
 		list_del(config->cmd_queue, 0);
 	}
diff --git a/sway/tree/view.c b/sway/tree/view.c
index febba3b9..511c2ecc 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -437,9 +437,14 @@ void view_execute_criteria(struct sway_view *view) {
 		wlr_log(WLR_DEBUG, "for_window '%s' matches view %p, cmd: '%s'",
 				criteria->raw, view, criteria->cmdlist);
 		list_add(view->executed_criteria, criteria);
-		struct cmd_results *res = execute_command(
+		list_t *res_list = execute_command(
 				criteria->cmdlist, NULL, view->container);
-		free_cmd_results(res);
+		while (res_list->length) {
+			struct cmd_results *res = res_list->items[0];
+			free_cmd_results(res);
+			list_del(res_list, 0);
+		}
+		list_free(res_list);
 	}
 	list_free(criterias);
 }