static void build_start(Ted *ted) {
	chdir(ted->cwd);
#if __unix__
	char *program = "/bin/sh";
	char *argv[] = {
		program, "-c", "make", NULL
	};
#else
	#error "TODO"
#endif

	process_exec(&ted->build_process, program, argv);
	ted->building = true;
	ted->build_shown = true;
	buffer_new_file(&ted->build_buffer, NULL);
	ted->build_buffer.store_undo_events = false;
	ted->build_buffer.view_only = true;
}

static void build_stop(Ted *ted) {
	if (ted->building)
		process_kill(&ted->build_process);
	arr_foreach_ptr(ted->build_errors, BuildError, err) {
		free(err->filename);
	}
	ted->building = false;
	ted->build_shown = false;
}

static void build_frame(Ted *ted, float x1, float y1, float x2, float y2) {
	TextBuffer *buffer = &ted->build_buffer;
	Process *process = &ted->build_process;
	assert(ted->build_shown);
	char buf[256];
	if (ted->building) {
		buffer->view_only = false; // disable view only temporarily so we can edit it
		bool any_text_inserted = false;
		while (1) {
			char incomplete[4];
			memcpy(ted->build_incomplete_codepoint, incomplete, sizeof incomplete);
			*ted->build_incomplete_codepoint = 0;

			i64 bytes_read = (i64)process_read(process, buf + 3, sizeof buf - 3);
			if (bytes_read == -2) {
				ted_seterr(ted, "Error reading command output: %s.", process_geterr(process));
				build_stop(ted);
				break;
			} else if (bytes_read == -1) {
				// no data right now.
				break;
			} else if (bytes_read == 0) {
				// end of file
				break;
			} else {
				any_text_inserted = true;
				// got some data.
				char *p = buf + 3 - strlen(incomplete);
				char *end = buf + 3 + bytes_read;
				// start off data with incomplete code point from last time
				memcpy(p, incomplete, strlen(incomplete));
				while (p != end) {
					char32_t c = 0;
					size_t ret = unicode_utf8_to_utf32(&c, p, (size_t)(end - p));
					if (ret == (size_t)-1) {
						// invalid UTF-8. skip this byte.
						++p;
					} else if (ret == (size_t)-2) {
						// incomplete UTF-8
						size_t leftovers = (size_t)(end - p);
						assert(leftovers < 4);
						memcpy(ted->build_incomplete_codepoint, p, leftovers);
						ted->build_incomplete_codepoint[leftovers] = '\0';
						p = end;
					} else {
						if (ret == 0) ret = 1;
						// got a code point
						buffer_insert_char_at_pos(buffer, buffer_end_of_file(buffer), c);
						p += ret;
					}
				}
			}
		}

		if (any_text_inserted) {
			buffer->cursor_pos = buffer_end_of_file(buffer);
			buffer_scroll_to_cursor(buffer);
		}

		char message[64];
		int status = process_check_status(process, message, sizeof message);
		if (status == 0) {
			// hasn't exited yet
		} else {
			buffer_insert_utf8_at_cursor(buffer, message);
			ted->building = false;

			// check for errors
			for (u32 line_idx = 0; line_idx < buffer->nlines; ++line_idx) {
				Line *line = &buffer->lines[line_idx];
				if (line->len < 3) {
					continue;
				}
				bool is_error = true;
				u32 i = 0;
				char32_t *str = line->str; u32 len = line->len;
				
				// we have something like main.c:5
				
				// get file name
				while (i < len) {
					if (str[i] == ':') break;
					if (!is32_alnum(str[i]) && str[i] != '/' && str[i] != '.' && str[i] != '\\') {
						is_error = false;
						break;
					}
					++i;
				}
				if (i >= len) is_error = false;

				if (is_error) {
					u32 filename_len = i;
					u32 line_number_len = 0;
					++i;
					while (i < len) {
						if (str[i] == ':') break;
						if (str[i] < '0' || str[i] > '9') {
							is_error = false;
							break;
						}
						++i;
						++line_number_len;
					}
					if (i >= len) is_error = false;
					if (line_number_len == 0) is_error = false;

					if (is_error) {
						char *line_number_str = str32_to_utf8_cstr(str32(str + filename_len + 1, line_number_len));
						if (line_number_str) {
							int line_number = atoi(line_number_str);
							free(line_number_str);
							
							char *filename = str32_to_utf8_cstr(str32(str, filename_len));
							if (filename) {
								char full_path[TED_PATH_MAX];
								ted_full_path(ted, filename, full_path, sizeof full_path);
								printf("File %s line %d\n",full_path,line_number);
							}
						}
					}
				}
			}
		}
		buffer->view_only = true;
	}
	buffer_render(buffer, rect4(x1, y1, x2, y2));
}