From a1646d84127a199fdacb6c5500d96e8829ebe8c1 Mon Sep 17 00:00:00 2001
From: Leo Tenenbaum <pommicket@gmail.com>
Date: Wed, 3 Mar 2021 15:09:49 -0500
Subject: :shell, bugfixes

---
 README.md |  7 +++++--
 buffer.c  | 11 +++++++++++
 build.c   | 55 +++++++++++++++++++++++++++----------------------------
 command.c |  6 ++++++
 command.h |  2 ++
 find.c    |  4 +---
 main.c    |  6 ++++--
 menu.c    | 37 +++++++++++++++++++++++++++++++++++++
 session.c |  2 ++
 ted.cfg   |  1 +
 ted.h     |  3 ++-
 util.c    |  8 ++------
 12 files changed, 100 insertions(+), 42 deletions(-)

diff --git a/README.md b/README.md
index 4b0fabb..a2094f4 100644
--- a/README.md
+++ b/README.md
@@ -18,13 +18,16 @@ a simple editor that starts up practically instantaneously, and performs well on
 
 ## Supported features (more coming soon)
 
+All the keybindings listed below are customizable!
+
 - Multiple tabs, each with a different file
 - Split screen (Ctrl+/, Ctrl+Shift+/)
 - Auto-indent
 - Customization of (pretty much) all colours and keyboard commands.
 - Syntax highlighting for C, C++, Rust, and Python.
 - Find and replace (with regular expressions!)
-- Run build command (default keybinding F4), go to errors
+- Run build command (F4), go to errors
+- Run any shell command (Ctrl+!)
 - Go to definition (Ctrl+click)
 - Go to line (Ctrl+G)
 
@@ -72,7 +75,7 @@ Then run `make.bat`.
 <tr><td>0.5</td> <td>Go to definition</td> <td>2021 Feb 22</td></tr>
 <tr><td>0.5a</td> <td>Several bugfixes, go to line</td> <td>2021 Feb 23</td></tr>
 <tr><td>0.6</td> <td>Split-screen</td> <td>2021 Feb 28</td></tr>
-<tr><td>0.7</td> <td>Restore session, command selector, big bug fixes</td> <td>2021 Mar 3</td></tr>
+<tr><td>0.7</td> <td>Restore session, command selector, :shell, big bug fixes</td> <td>2021 Mar 3</td></tr>
 </table>
 
 ## License
diff --git a/buffer.c b/buffer.c
index 7489d04..21922ee 100644
--- a/buffer.c
+++ b/buffer.c
@@ -2387,6 +2387,7 @@ void buffer_render(TextBuffer *buffer, Rect r) {
 		rgba_u32_to_floats(colors[COLOR_TEXT], text_state.color);
 
 	buffer->first_line_on_screen = start_line;
+	buffer->last_line_on_screen = 0;
 	for (u32 line_idx = start_line; line_idx < nlines; ++line_idx) {
 		Line *line = &lines[line_idx];
 		if (arr_len(char_types) < line->len) {
@@ -2429,6 +2430,7 @@ void buffer_render(TextBuffer *buffer, Rect r) {
 		text_state.y += text_font_char_height(font);
 		column = 0;
 	}
+	if (buffer->last_line_on_screen == 0) buffer->last_line_on_screen = nlines - 1;
 	
 	arr_free(char_types);
 
@@ -2544,3 +2546,12 @@ void buffer_dedent_selection(TextBuffer *buffer) {
 	buffer_dedent_lines(buffer, l1, l2);
 }
 
+void buffer_indent_cursor_line(TextBuffer *buffer) {
+	u32 line = buffer->cursor_pos.line;
+	buffer_indent_lines(buffer, line, line);
+}
+void buffer_dedent_cursor_line(TextBuffer *buffer) {
+	u32 line = buffer->cursor_pos.line;
+	buffer_dedent_lines(buffer, line, line);
+}
+
diff --git a/build.c b/build.c
index 7554c50..4a65857 100644
--- a/build.c
+++ b/build.c
@@ -11,25 +11,41 @@ static void build_stop(Ted *ted) {
 		process_kill(&ted->build_process);
 	ted->building = false;
 	ted->build_shown = false;
+	*ted->build_dir = '\0';
 	build_clear(ted);
 }
 
-
-static void build_start(Ted *ted) {
+// make sure you set ted->build_dir before running this!
+static void build_start_with_command(Ted *ted, char const *command) {
+	assert(*ted->build_dir);
+	change_directory(ted->build_dir);
 	if (ted->building) {
 		build_stop(ted);
 	}
-	Settings *settings = &ted->settings;
-	
 	ted_save_all(ted);
 
-	// get rid of any old build errors
-	build_clear(ted);
-	
+	if (process_run(&ted->build_process, command)) {
+		ted->building = true;
+		ted->build_shown = true;
+		TextBuffer *build_buffer = &ted->build_buffer;
+		// new empty build output buffer
+		buffer_new_file(build_buffer, NULL);
+		build_buffer->store_undo_events = false; // don't need undo events for build output buffer
+		char32_t text[] = {'$', ' '};
+		buffer_insert_text_at_cursor(build_buffer, str32(text, 2));
+		buffer_insert_utf8_at_cursor(build_buffer, command);
+		buffer_insert_char_at_cursor(build_buffer, '\n');
+		build_buffer->view_only = true;
+	} else {
+		ted_seterr(ted, "Couldn't start build: %s", process_geterr(&ted->build_process));
+	}
+}
+
+static void build_start(Ted *ted) {
 	bool cargo = false, make = false;
 	
-	change_directory(ted->cwd);
-	strcpy(ted->build_dir, ted->cwd);
+	strbuf_cpy(ted->build_dir, ted->cwd);
+	Settings *settings = &ted->settings;
 	
 	char *command = settings->build_default_command;
 	
@@ -42,11 +58,9 @@ static void build_start(Ted *ted) {
 	if (fs_file_exists("Cargo.toml")) {
 		cargo = true;
 	} else if (fs_file_exists(".." PATH_SEPARATOR_STR "Cargo.toml")) {
-		change_directory("..");
 		ted_full_path(ted, "..", ted->build_dir, sizeof ted->build_dir);
 		cargo = true;
 	} else if (fs_file_exists(".." PATH_SEPARATOR_STR ".." PATH_SEPARATOR_STR "Cargo.toml")) {
-		change_directory(".." PATH_SEPARATOR_STR "..");
 		ted_full_path(ted, "../..", ted->build_dir, sizeof ted->build_dir);
 		cargo = true;
 	} else 
@@ -54,7 +68,6 @@ static void build_start(Ted *ted) {
 	if (fs_file_exists("Makefile")) {
 		make = true;
 	} else if (fs_file_exists(".." PATH_SEPARATOR_STR "Makefile")) {
-		change_directory("..");
 		ted_full_path(ted, "..", ted->build_dir, sizeof ted->build_dir);
 		make = true;
 	}
@@ -66,22 +79,8 @@ static void build_start(Ted *ted) {
 	} else if (make) {
 		command = "make";
 	}
-
-	if (process_run(&ted->build_process, command)) {
-		ted->building = true;
-		ted->build_shown = true;
-		TextBuffer *build_buffer = &ted->build_buffer;
-		// new empty build output buffer
-		buffer_new_file(build_buffer, NULL);
-		build_buffer->store_undo_events = false; // don't need undo events for build output buffer
-		char32_t text[] = {'$', ' '};
-		buffer_insert_text_at_cursor(build_buffer, str32(text, 2));
-		buffer_insert_utf8_at_cursor(build_buffer, command);
-		buffer_insert_char_at_cursor(build_buffer, '\n');
-		build_buffer->view_only = true;
-	} else {
-		ted_seterr(ted, "Couldn't start build: %s", process_geterr(&ted->build_process));
-	}
+	
+	build_start_with_command(ted, command);
 }
 
 static void build_go_to_error(Ted *ted) {
diff --git a/command.c b/command.c
index c7276ac..727b2c6 100644
--- a/command.c
+++ b/command.c
@@ -115,6 +115,8 @@ void command_execute(Ted *ted, Command c, i64 argument) {
 		} else if (buffer) {
 			if (buffer->selection)
 				buffer_dedent_selection(buffer);
+			else
+				buffer_dedent_cursor_line(buffer);
 		}
 		break;
 	case CMD_NEWLINE:
@@ -319,6 +321,10 @@ void command_execute(Ted *ted, Command c, i64 argument) {
 	case CMD_BUILD_PREV_ERROR:
 		build_prev_error(ted);
 		break;
+	case CMD_SHELL:
+		menu_open(ted, MENU_SHELL);
+		break;
+	
 	case CMD_GOTO_DEFINITION:
 		menu_open(ted, MENU_GOTO_DEFINITION);
 		break;
diff --git a/command.h b/command.h
index d5d8062..41bab31 100644
--- a/command.h
+++ b/command.h
@@ -72,6 +72,7 @@ ENUM_U16 {
 	CMD_BUILD,
 	CMD_BUILD_PREV_ERROR,
 	CMD_BUILD_NEXT_ERROR,
+	CMD_SHELL,
 
 	CMD_GOTO_DEFINITION, // "go to definition of..."
 	CMD_GOTO_LINE, // open "goto line..." menu
@@ -151,6 +152,7 @@ static CommandName const command_names[] = {
 	{"build", CMD_BUILD},
 	{"build-prev-error", CMD_BUILD_PREV_ERROR},
 	{"build-next-error", CMD_BUILD_NEXT_ERROR},
+	{"shell", CMD_SHELL},
 	{"goto-definition", CMD_GOTO_DEFINITION},
 	{"goto-line", CMD_GOTO_LINE},
 	{"split-horizontal", CMD_SPLIT_HORIZONTAL},
diff --git a/find.c b/find.c
index 58bff8c..1e0b409 100644
--- a/find.c
+++ b/find.c
@@ -451,9 +451,7 @@ static void find_open(Ted *ted, bool replace) {
 		ted->find = true;
 		buffer_select_all(ted->active_buffer);
 	}
-	if (!ted->replace && replace) {
-		ted->replace = true;
-	}
+	ted->replace = replace;
 	find_update(ted, true);
 }
 
diff --git a/main.c b/main.c
index a829736..6a9e46a 100644
--- a/main.c
+++ b/main.c
@@ -1,5 +1,4 @@
 // @TODO:
-// - run shell command (i.e. not just `make`)
 // - completion
 
 // - more instructions (basic stuff + how to open config)
@@ -73,8 +72,8 @@ bool tag_goto(Ted *ted, char const *tag);
 #include "find.c"
 #include "node.c"
 #include "tags.c"
-#include "menu.c"
 #include "build.c"
+#include "menu.c"
 #include "command.c"
 #include "config.c"
 #include "session.c"
@@ -644,6 +643,9 @@ int main(int argc, char **argv) {
 									}
 								}
 							}
+							if (ted->build_shown)
+								if (buffer_handle_click(ted, &ted->build_buffer, pos, times)) // handle build buffer clicks
+									add = false;
 						}
 						if (add) {
 							ted->mouse_clicks[button][ted->nmouse_clicks[button]] = pos;
diff --git a/menu.c b/menu.c
index 868a4a4..24a9af8 100644
--- a/menu.c
+++ b/menu.c
@@ -32,6 +32,9 @@ static void menu_close(Ted *ted) {
 		buffer_clear(&ted->argument_buffer);
 		free(selector->entries); selector->entries = NULL; selector->n_entries = 0;
 	} break;
+	case MENU_SHELL:
+		buffer_clear(&ted->line_buffer);
+		break;
 	}
 	ted->menu = MENU_NONE;
 	ted->selector_open = NULL;
@@ -79,6 +82,9 @@ static void menu_open(Ted *ted, Menu menu) {
 		selector->enable_cursor = true;
 		selector->cursor = 0;
 	} break;
+	case MENU_SHELL:
+		ted_switch_to_buffer(ted, &ted->line_buffer);
+		break;
 	}
 }
 
@@ -288,6 +294,15 @@ static void menu_update(Ted *ted) {
 		}
 		free(search_term);
 	} break;
+	case MENU_SHELL:
+		if (line_buffer->line_buffer_submitted) {
+			char *command = str32_to_utf8_cstr(buffer_get_line(line_buffer, 0));
+			menu_close(ted);
+			strbuf_cpy(ted->build_dir, ted->cwd);
+			build_start_with_command(ted, command);
+			free(command);
+		}
+		break;
 	}
 }
 
@@ -405,5 +420,27 @@ static void menu_render(Ted *ted) {
 
 		text_render(font_bold);
 	} break;
+	case MENU_SHELL: {
+		float line_buffer_height = char_height;
+		
+		bounds.size.y = line_buffer_height + 2 * padding;
+		rect_coords(bounds, &x1, &y1, &x2, &y2);
+		
+		gl_geometry_rect(bounds, colors[COLOR_MENU_BG]);
+		gl_geometry_rect_border(bounds, settings->border_thickness, colors[COLOR_BORDER]);
+		gl_geometry_draw();
+		
+		x1 += padding;
+		y1 += padding;
+		x2 -= padding;
+		y2 -= padding;
+		
+		char const *text = "Run";
+		text_utf8(font_bold, text, x1, y1, colors[COLOR_TEXT]);
+		x1 += text_get_size_v2(font_bold, text).x + padding;
+		text_render(font_bold);
+		
+		buffer_render(&ted->line_buffer, rect4(x1, y1, x2, y2));
+	} break;
 	}
 }
diff --git a/session.c b/session.c
index 4895f34..ac76511 100644
--- a/session.c
+++ b/session.c
@@ -37,6 +37,8 @@ static void session_read_node(Ted *ted, FILE *fp) {
 	} else {
 		node->active_tab = read_u16(fp);
 		u16 ntabs = clamp_u16(read_u16(fp), 0, TED_MAX_TABS);
+		if (node->active_tab >= ntabs)
+			node->active_tab = 0;
 		for (u16 i = 0; i < ntabs; ++i) {
 			u16 buf_idx = read_u16(fp);
 			if (buf_idx >= TED_MAX_BUFFERS) continue;
diff --git a/ted.cfg b/ted.cfg
index b38307e..d824b92 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -120,6 +120,7 @@ Ctrl+Alt+Shift+v = :view-only
 F4 = :build
 Ctrl+[ = :build-prev-error
 Ctrl+] = :build-next-error
+Ctrl+! = :shell
 
 Ctrl+d = :goto-definition
 Ctrl+g = :goto-line
diff --git a/ted.h b/ted.h
index b9edc0b..ea1afdd 100644
--- a/ted.h
+++ b/ted.h
@@ -170,7 +170,8 @@ ENUM_U16 {
 	MENU_ASK_RELOAD, // prompt about whether to reload file which has ben changed by another program
 	MENU_GOTO_DEFINITION,
 	MENU_GOTO_LINE,
-	MENU_COMMAND_SELECTOR
+	MENU_COMMAND_SELECTOR,
+	MENU_SHELL, // run a shell command
 } ENUM_U16_END(Menu);
 
 typedef struct {
diff --git a/util.c b/util.c
index f14f640..7d0064d 100644
--- a/util.c
+++ b/util.c
@@ -22,13 +22,9 @@ static u8 util_popcount(u64 x) {
 
 static u8 util_count_leading_zeroes32(u32 x) {
 	if (x == 0) return 32; // GCC's __builtin_clz is undefined for x = 0
-#if __GNUC__
-#if UINT_MAX == 4294967295
+#if __GNUC__ && UINT_MAX == 4294967295
 	return (u8)__builtin_clz(x);
-#else
-	#error "unsigned int isn't 32 bits. this function needs fixing to work on systems like yours."
-#endif
-#elif _WIN32
+#elif _WIN32 && UINT_MAX == 4294967295
 	return (u8)__lzcnt(x);
 #else
 	u8 count = 0;
-- 
cgit v1.2.3