Browse Source

bim: update to v1.5.1

K. Lange 2 years ago
parent
commit
94fe55e27b
1 changed files with 502 additions and 53 deletions
  1. 502 53
      apps/bim.c

+ 502 - 53
apps/bim.c

@@ -37,11 +37,12 @@
 #include <ctype.h>
 #include <dirent.h>
 #include <poll.h>
+#include <limits.h>
 #include <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 
-#define BIM_VERSION   "1.4.5"
+#define BIM_VERSION   "1.5.1"
 #define BIM_COPYRIGHT "Copyright 2012-2019 K. Lange <\033[3mklange@toaruos.org\033[23m>"
 
 #define BLOCK_SIZE 4096
@@ -165,6 +166,7 @@ typedef struct {
 	int actual;
 	int istate;
 	int is_current;
+	int rev_status;
 	char_t   text[];
 } line_t;
 
@@ -203,6 +205,8 @@ struct {
 	unsigned int go_to_line:1;
 	unsigned int hilight_current_line:1;
 	unsigned int shift_scrolling:1;
+	unsigned int check_git:1;
+	unsigned int color_gutter:1;
 
 	int cursor_padding;
 	int split_percent;
@@ -235,12 +239,17 @@ struct {
 	1, /* should go to line when opening file */
 	1, /* hilight the current line */
 	1, /* shift scrolling (shifts view rather than moving cursor) */
+	0, /* check git on open and on save */
+	1, /* color the gutter for modified lines */
 	4, /* cursor padding */
 	50, /* split percentage */
 	5, /* how many lines to scroll on mouse wheel */
 };
 
 void redraw_line(int j, int x);
+int git_examine(char * filename);
+void search_next(void);
+void set_preferred_column(void);
 
 /**
  * Special implementation of getch with a timeout
@@ -323,6 +332,7 @@ typedef struct _env {
 	unsigned short readonly:1;
 	unsigned short indent:1;
 	unsigned short highlighting_paren:1;
+	unsigned short checkgitstatusonwrite:1;
 
 	short  mode;
 	short  tabstop;
@@ -405,6 +415,119 @@ buffer_t * buffer_new(void) {
 	return buffers[buffers_len-1];
 }
 
+/**
+ * Open the biminfo file.
+ */
+FILE * open_biminfo(void) {
+	/* TODO This should probably be configurable line bimrc */
+	char * home = getenv("HOME");
+	if (!home) {
+		/* Since it's not, we need HOME */
+		return NULL;
+	}
+
+	/* biminfo lives at ~/.biminfo */
+	char biminfo_path[PATH_MAX+1] = {0};
+	sprintf(biminfo_path,"%s/.biminfo",home);
+
+	/* Try to open normally first... */
+	FILE * biminfo = fopen(biminfo_path,"r+");
+	if (!biminfo) {
+		/* Otherwise, try to create it. */
+		biminfo = fopen(biminfo_path,"w+");
+	}
+	return biminfo;
+}
+
+/**
+ * Fetch the cursor position from a biminfo file
+ */
+int fetch_from_biminfo(buffer_t * buf) {
+	/* Can't fetch if we don't have a filename */
+	if (!buf->file_name) return 1;
+
+	/* Get the absolute name of the file */
+	char tmp_path[PATH_MAX+2];
+	if (!realpath(buf->file_name, tmp_path)) {
+		return 1;
+	}
+	strcat(tmp_path," ");
+
+	FILE * biminfo = open_biminfo();
+	if (!biminfo) return 1;
+
+	/* Scan */
+	char line[PATH_MAX+64];
+
+	while (!feof(biminfo)) {
+		fpos_t start_of_line;
+		fgetpos(biminfo, &start_of_line);
+		fgets(line, PATH_MAX+63, biminfo);
+		if (line[0] != '>') {
+			continue;
+		}
+
+		if (!strncmp(&line[1],tmp_path, strlen(tmp_path))) {
+			/* Read */
+			sscanf(line+1+strlen(tmp_path)+1,"%d",&buf->line_no);
+			sscanf(line+1+strlen(tmp_path)+21,"%d",&buf->col_no);
+			return 0;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * Write a file containing the last cursor position of a buffer.
+ */
+int update_biminfo(buffer_t * buf) {
+	if (!buf->file_name) return 1;
+
+	/* Get the absolute name of the file */
+	char tmp_path[PATH_MAX+1];
+	if (!realpath(buf->file_name, tmp_path)) {
+		return 1;
+	}
+	strcat(tmp_path," ");
+
+	FILE * biminfo = open_biminfo();
+	if (!biminfo) return 1;
+
+	/* Scan */
+	char line[PATH_MAX+64];
+
+	while (!feof(biminfo)) {
+		fpos_t start_of_line;
+		fgetpos(biminfo, &start_of_line);
+		fgets(line, PATH_MAX+63, biminfo);
+		if (line[0] != '>') {
+			continue;
+		}
+
+		if (!strncmp(&line[1],tmp_path, strlen(tmp_path))) {
+			/* Update */
+			fsetpos(biminfo, &start_of_line);
+			fprintf(biminfo,">%s %20d %20d\n", tmp_path, buf->line_no, buf->col_no);
+			goto _done;
+		}
+	}
+
+	if (ftell(biminfo) == 0) {
+		/* New biminfo */
+		fprintf(biminfo, "# This is a biminfo file.\n");
+		fprintf(biminfo, "# It was generated by bim. Do not edit it by hand!\n");
+		fprintf(biminfo, "# Cursor positions and other state are stored here.\n");
+	}
+
+	/* Haven't found what we're looking for, should be at end of file */
+	fprintf(biminfo, ">%s %20d %20d\n", tmp_path, buf->line_no, buf->col_no);
+
+_done:
+	fclose(biminfo);
+	return 0;
+}
+
 /**
  * Close a buffer
  */
@@ -422,6 +545,8 @@ buffer_t * buffer_close(buffer_t * buf) {
 		return env; /* wtf */
 	}
 
+	update_biminfo(buf);
+
 	/* Clean up lines used by old buffer */
 	for (int i = 0; i < buf->line_count; ++i) {
 		free(buf->lines[i]);
@@ -785,6 +910,7 @@ static char * syn_c_keywords[] = {
 	"alignas","alignof","offsetof","asm","__asm__",
 	/* C++ stuff */
 	"public","private","class","using","namespace","virtual","override","protected",
+	"template","typename","static_cast","throw",
 	NULL
 };
 
@@ -794,6 +920,7 @@ static char * syn_c_types[] = {
 	"uint8_t","uint16_t","uint32_t","uint64_t",
 	"int8_t","int16_t","int32_t","int64_t","FILE",
 	"ssize_t","size_t","uintptr_t","intptr_t","__volatile__",
+	"constexpr",
 	NULL
 };
 
@@ -1042,7 +1169,7 @@ static int syn_c_calculate(struct syntax_state * state) {
 	return -1;
 }
 
-static char * c_ext[] = {".c",".h",".cpp",".hpp",".c++",".h++",NULL};
+static char * c_ext[] = {".c",".h",".cpp",".hpp",".c++",".h++",".cc",".hh",NULL};
 
 static char * syn_py_keywords[] = {
 	"class","def","return","del","if","else","elif","for","while","continue",
@@ -1478,7 +1605,7 @@ static char * rust_ext[] = {".rs",NULL};
 
 static char * syn_bimrc_keywords[] = {
 	"history","padding","hlparen","hlcurrent","splitpercent",
-	"shiftscrolling","scrollamount",
+	"shiftscrolling","scrollamount","git","colorgutter",
 	NULL
 };
 
@@ -2037,7 +2164,7 @@ static int paint_esh_string(struct syntax_state * state) {
 			paint(1, FLAG_STRING);
 		}
 	}
-	return 0;
+	return 2;
 }
 
 static int paint_esh_single_string(struct syntax_state * state) {
@@ -2051,7 +2178,7 @@ static int paint_esh_single_string(struct syntax_state * state) {
 			paint(1, FLAG_STRING);
 		}
 	}
-	return 0;
+	return 1;
 }
 
 static int esh_keyword_qualifier(int c) {
@@ -2066,6 +2193,11 @@ static char * esh_keywords[] = {
 };
 
 static int syn_esh_calculate(struct syntax_state * state) {
+	if (state->state == 1) {
+		return paint_esh_single_string(state);
+	} else if (state->state == 2) {
+		return paint_esh_string(state);
+	}
 	if (charat() == '#') {
 		while (charat() != -1) paint(1, FLAG_COMMENT);
 		return -1;
@@ -2075,12 +2207,10 @@ static int syn_esh_calculate(struct syntax_state * state) {
 		return 0;
 	} else if (charat() == '\'') {
 		paint(1, FLAG_STRING);
-		paint_esh_single_string(state);
-		return 0;
+		return paint_esh_single_string(state);
 	} else if (charat() == '"') {
 		paint(1, FLAG_STRING);
-		paint_esh_string(state);
-		return 0;
+		return paint_esh_string(state);
 	} else if (match_and_paint(state, "export", FLAG_KEYWORD, esh_keyword_qualifier)) {
 		while (charat() == ' ') skip();
 		while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE);
@@ -2273,6 +2403,7 @@ line_t * line_insert(line_t * line, char_t c, int offset, int lineno) {
 	line->actual += 1;
 
 	if (!env->loading) {
+		line->rev_status = 2; /* Modified */
 		recalculate_tabs(line);
 		recalculate_syntax(line, lineno);
 	}
@@ -2304,6 +2435,7 @@ void line_delete(line_t * line, int offset, int lineno) {
 
 	/* The line is one character shorter */
 	line->actual -= 1;
+	line->rev_status = 2;
 
 	recalculate_tabs(line);
 	recalculate_syntax(line, lineno);
@@ -2327,6 +2459,7 @@ void line_replace(line_t * line, char_t _c, int offset, int lineno) {
 	line->text[offset] = _c;
 
 	if (!env->loading) {
+		line->rev_status = 2; /* Modified */
 		recalculate_tabs(line);
 		recalculate_syntax(line, lineno);
 	}
@@ -2396,16 +2529,17 @@ line_t ** add_line(line_t ** lines, int offset) {
 	}
 
 	/* Allocate the new line */
-	lines[offset] = malloc(sizeof(line_t) + sizeof(char_t) * 32);
+	lines[offset] = calloc(sizeof(line_t) + sizeof(char_t) * 32, 1);
 	lines[offset]->available = 32;
-	lines[offset]->actual    = 0;
-	lines[offset]->istate    = 0;
-	lines[offset]->is_current = 0;
 
 	/* There is one new line */
 	env->line_count += 1;
 	env->lines = lines;
 
+	if (!env->loading) {
+		lines[offset]->rev_status = 2; /* Modified */
+	}
+
 	if (offset > 0 && !env->loading) {
 		recalculate_syntax(lines[offset-1],offset-1);
 	}
@@ -2436,6 +2570,7 @@ void replace_line(line_t ** lines, int offset, line_t * replacement) {
 	memcpy(&lines[offset]->text, &replacement->text, sizeof(char_t) * replacement->actual);
 
 	if (!env->loading) {
+		lines[offset]->rev_status = 2;
 		recalculate_syntax(lines[offset],offset);
 	}
 }
@@ -2476,6 +2611,7 @@ line_t ** merge_lines(line_t ** lines, int lineb) {
 	lines[linea]->actual = lines[linea]->actual + lines[lineb]->actual;
 
 	if (!env->loading) {
+		lines[linea]->rev_status = 2;
 		recalculate_tabs(lines[linea]);
 		recalculate_syntax(lines[linea], linea);
 	}
@@ -2536,17 +2672,17 @@ line_t ** split_line(line_t ** lines, int line, int split) {
 	v++;
 
 	/* Allocate space for the new line */
-	lines[line+1] = malloc(sizeof(line_t) + sizeof(char_t) * v);
+	lines[line+1] = calloc(sizeof(line_t) + sizeof(char_t) * v, 1);
 	lines[line+1]->available = v;
 	lines[line+1]->actual = remaining;
-	lines[line+1]->istate = 0;
-	lines[line+1]->is_current = 0;
 
 	/* Move the data from the old line into the new line */
 	memmove(lines[line+1]->text, &lines[line]->text[split], sizeof(char_t) * remaining);
 	lines[line]->actual = split;
 
 	if (!env->loading) {
+		lines[line]->rev_status = 2;
+		lines[line+1]->rev_status = 2;
 		recalculate_tabs(lines[line]);
 		recalculate_tabs(lines[line+1]);
 		recalculate_syntax(lines[line], line);
@@ -2560,30 +2696,98 @@ line_t ** split_line(line_t ** lines, int line, int split) {
 	return lines;
 }
 
+/**
+ * Understand spaces and comments and check if the previous line
+ * ended with a brace or a colon.
+ */
+int line_ends_with_brace(line_t * line) {
+	int i = line->actual-1;
+	while (i >= 0) {
+		if ((line->text[i].flags & 0xF) == FLAG_COMMENT || line->text[i].codepoint == ' ') {
+			i--;
+		} else {
+			break;
+		}
+	}
+	if (i < 0) return 0;
+	return (line->text[i].codepoint == '{' || line->text[i].codepoint == ':');
+}
+
+int line_is_comment(line_t * line) {
+	if (!env->syntax) return 0;
+
+	if (!strcmp(env->syntax->name,"c")) {
+		if (line->istate == 1) return 1;
+	} else if (!strcmp(env->syntax->name,"java")) {
+		if (line->istate == 1) return 1;
+	} else if (!strcmp(env->syntax->name,"rust")) {
+		if (line->istate > 0) return 1;
+	}
+
+	return 0;
+}
+
 /**
  * Add indentation from the previous (temporally) line
  */
 void add_indent(int new_line, int old_line, int ignore_brace) {
 	if (env->indent) {
 		int changed = 0;
-		for (int i = 0; i < env->lines[old_line]->actual; ++i) {
-			if (env->lines[old_line]->text[i].codepoint == ' ' ||
-				env->lines[old_line]->text[i].codepoint == '\t') {
-				env->lines[new_line] = line_insert(env->lines[new_line],env->lines[old_line]->text[i],i,new_line);
-				env->col_no++;
-				changed = 1;
-			} else {
-				break;
+		if (old_line < new_line && line_is_comment(env->lines[new_line])) {
+			for (int i = 0; i < env->lines[old_line]->actual; ++i) {
+				if (env->lines[old_line]->text[i].codepoint == '/') {
+					if (env->lines[old_line]->text[i+1].codepoint == '*') {
+						/* Insert ' * ' */
+						char_t space = {1,FLAG_COMMENT,' '};
+						char_t asterisk = {1,FLAG_COMMENT,'*'};
+						env->lines[new_line] = line_insert(env->lines[new_line],space,i,new_line);
+						env->lines[new_line] = line_insert(env->lines[new_line],asterisk,i+1,new_line);
+						env->lines[new_line] = line_insert(env->lines[new_line],space,i+2,new_line);
+						env->col_no += 3;
+					}
+					break;
+				} else if (env->lines[old_line]->text[i].codepoint == ' ' && env->lines[old_line]->text[i+1].codepoint == '*') {
+					/* Insert ' * ' */
+					char_t space = {1,FLAG_COMMENT,' '};
+					char_t asterisk = {1,FLAG_COMMENT,'*'};
+					env->lines[new_line] = line_insert(env->lines[new_line],space,i,new_line);
+					env->lines[new_line] = line_insert(env->lines[new_line],asterisk,i+1,new_line);
+					env->lines[new_line] = line_insert(env->lines[new_line],space,i+2,new_line);
+					env->col_no += 3;
+					break;
+				} else if (env->lines[old_line]->text[i].codepoint == ' ' ||
+					env->lines[old_line]->text[i].codepoint == '\t' ||
+					env->lines[old_line]->text[i].codepoint == '*') {
+					env->lines[new_line] = line_insert(env->lines[new_line],env->lines[old_line]->text[i],i,new_line);
+					env->col_no++;
+					changed = 1;
+				} else {
+					break;
+				}
+			}
+		} else {
+			for (int i = 0; i < env->lines[old_line]->actual; ++i) {
+				if (old_line < new_line && i == env->lines[old_line]->actual - 3 &&
+					env->lines[old_line]->text[i].codepoint == ' ' &&
+					env->lines[old_line]->text[i+1].codepoint == '*' &&
+					env->lines[old_line]->text[i+2].codepoint == '/') {
+					break;
+				} else if (env->lines[old_line]->text[i].codepoint == ' ' ||
+					env->lines[old_line]->text[i].codepoint == '\t') {
+					env->lines[new_line] = line_insert(env->lines[new_line],env->lines[old_line]->text[i],i,new_line);
+					env->col_no++;
+					changed = 1;
+				} else {
+					break;
+				}
 			}
 		}
-		if (old_line < new_line && !ignore_brace &&
-			(env->lines[old_line]->text[env->lines[old_line]->actual-1].codepoint == '{' ||
-			env->lines[old_line]->text[env->lines[old_line]->actual-1].codepoint == ':')) {
+		if (old_line < new_line && !ignore_brace && line_ends_with_brace(env->lines[old_line])) {
 			if (env->tabs) {
 				char_t c;
 				c.codepoint = '\t';
 				c.display_width = env->tabstop;
-				env->lines[new_line] = line_insert(env->lines[new_line], c, env->lines[new_line]->actual, new_line);
+				env->lines[new_line] = line_insert(env->lines[new_line], c, env->col_no-1, new_line);
 				env->col_no++;
 				changed = 1;
 			} else {
@@ -2592,7 +2796,7 @@ void add_indent(int new_line, int old_line, int ignore_brace) {
 					c.codepoint = ' ';
 					c.display_width = 1;
 					c.flags = FLAG_SELECT;
-					env->lines[new_line] = line_insert(env->lines[new_line], c, env->lines[new_line]->actual, new_line);
+					env->lines[new_line] = line_insert(env->lines[new_line], c, env->col_no-1, new_line);
 					env->col_no++;
 				}
 				changed = 1;
@@ -2648,11 +2852,8 @@ void setup_buffer(buffer_t * env) {
 	env->lines = malloc(sizeof(line_t *) * env->line_avail);
 
 	/* Initialize the first line */
-	env->lines[0] = malloc(sizeof(line_t) + sizeof(char_t) * 32);
+	env->lines[0] = calloc(sizeof(line_t) + sizeof(char_t) * 32, 1);
 	env->lines[0]->available = 32;
-	env->lines[0]->actual    = 0;
-	env->lines[0]->istate    = 0;
-	env->lines[0]->is_current = 0;
 }
 
 /**
@@ -2895,7 +3096,7 @@ void hide_cursor(void) {
 	fflush(stdout);
 }
 
-/*
+/**
  * Show the cursor
  */
 void show_cursor(void) {
@@ -3323,12 +3524,25 @@ void redraw_line(int j, int x) {
 	/* Move cursor to upper left most cell of this line */
 	place_cursor(1 + env->left,2 + j);
 
-	/* Draw a gutter on the left.
-	 * TODO: The gutter can be used to show single-character
-	 *       line annotations, such as collapse state, or
-	 *       whether a search result was found on this line.
-	 */
-	set_colors(COLOR_NUMBER_FG, COLOR_ALT_FG);
+	/* Draw a gutter on the left. */
+	switch (env->lines[x]->rev_status) {
+		case 1:
+			set_colors(COLOR_NUMBER_FG, COLOR_GREEN);
+			break;
+		case 2:
+			set_colors(COLOR_NUMBER_FG, global_config.color_gutter ? COLOR_SEARCH_BG : COLOR_ALT_FG);
+			break;
+		case 3:
+			set_colors(COLOR_NUMBER_FG, COLOR_KEYWORD);
+			break;
+		case 4:
+			set_colors(COLOR_NUMBER_FG, COLOR_RED);
+			break;
+		default:
+			set_colors(COLOR_NUMBER_FG, COLOR_ALT_FG);
+			break;
+	}
+
 	printf(" ");
 
 	draw_line_number(x);
@@ -4055,7 +4269,7 @@ void open_file(char * file) {
 	setup_buffer(env);
 
 	FILE * f;
-	int init_line = 1;
+	int init_line = -1;
 
 	if (!strcmp(file,"-")) {
 		/**
@@ -4101,7 +4315,7 @@ void open_file(char * file) {
 		}
 		env->loading = 0;
 		if (global_config.go_to_line) {
-			goto_line(init_line);
+			goto_line(1);
 		}
 		if (env->syntax && env->syntax->prefers_spaces) {
 			env->tabs = 0;
@@ -4153,12 +4367,26 @@ void open_file(char * file) {
 
 	env->loading = 0;
 
+	if (global_config.check_git) {
+		env->checkgitstatusonwrite = 1;
+		git_examine(file);
+	}
+
 	for (int i = 0; i < env->line_count; ++i) {
 		recalculate_tabs(env->lines[i]);
 	}
 
 	if (global_config.go_to_line) {
-		goto_line(init_line);
+		if (init_line != -1) {
+			goto_line(init_line);
+		} else {
+			env->line_no = 1;
+			env->col_no = 1;
+			fetch_from_biminfo(env);
+			redraw_all();
+			place_cursor_actual();
+			set_preferred_column();
+		}
 	}
 
 	fclose(f);
@@ -4193,6 +4421,10 @@ void try_quit(void) {
 			return;
 		}
 	}
+	/* Close all buffers */
+	while (buffers_len) {
+		buffer_close(buffers[0]);
+	}
 	quit();
 }
 
@@ -4240,6 +4472,90 @@ void next_tab(void) {
 	}
 }
 
+/**
+ * Check for modified lines in a file by examining `git diff` output.
+ * This can be enabled globally in bimrc or per environment with the 'git' option.
+ */
+int git_examine(char * filename) {
+	if (env->modified) return 1;
+#ifndef __toaru__
+	int fds[2];
+	pipe(fds);
+	int child = fork();
+	if (child == 0) {
+		FILE * dev_null = fopen("/dev/null","w");
+		close(fds[0]);
+		dup2(fds[1], STDOUT_FILENO);
+		dup2(fileno(dev_null), STDERR_FILENO);
+		char * args[] = {"git","--no-pager","diff","-U0","--no-color","--",filename,NULL};
+		exit(execvp("git",args));
+	} else if (child < 0) {
+		return 1;
+	}
+
+	close(fds[1]);
+	FILE * f = fdopen(fds[0],"r");
+
+	int line_offset = 0;
+	int line_count = 0;
+	int lines_to_pull = 0;
+	int line_no = 0;
+	int pre_count = 0;
+	while (!feof(f)) {
+		int c = fgetc(f);
+		if (c < 0) break;
+
+		if (lines_to_pull > 0 && line_offset == 0) {
+			if (c != '-') {
+				if (c == '+') {
+					if (pre_count == lines_to_pull) {
+						env->lines[line_no-1]->rev_status = 3;
+					} else {
+						env->lines[line_no-1]->rev_status = 1;
+					}
+				}
+				lines_to_pull--;
+				pre_count--;
+				line_no++;
+			}
+		} else if (c == '@' && line_offset == 0) {
+			/* Read line offset, count */
+			if (fgetc(f) == '@' && fgetc(f) == ' ' && fgetc(f) == '-') {
+				while (isdigit(c = fgetc(f)));
+				if (c != ',') {
+					fscanf(f,"%d",&line_no);
+					pre_count = 1;
+				} else {
+					while ((c = fgetc(f)) == ',');
+					ungetc(c,f);
+					fscanf(f,"%d",&pre_count);
+				}
+				while ((c = fgetc(f)) == ' ');
+				ungetc(c,f);
+				/* Read one integer */
+				fscanf(f,"%d",&line_no);
+				lines_to_pull = 1;
+				if (fgetc(f) == ',') {
+					fscanf(f,"%d",&lines_to_pull);
+				}
+			}
+		}
+
+		if (c == '\n') {
+			line_offset = 0;
+			line_count++;
+			continue;
+		}
+
+		line_offset++;
+	}
+
+	fclose(f);
+#endif
+	return 0;
+}
+
+
 /**
  * Write active buffer to file
  */
@@ -4260,6 +4576,7 @@ void write_file(char * file) {
 	int i, j;
 	for (i = 0; i < env->line_count; ++i) {
 		line_t * line = env->lines[i];
+		line->rev_status = 0;
 		for (j = 0; j < line->actual; j++) {
 			char_t c = line->text[j];
 			if (c.codepoint == 0) {
@@ -4285,6 +4602,10 @@ void write_file(char * file) {
 		memcpy(env->file_name, file, strlen(file) + 1);
 	}
 
+	if (env->checkgitstatusonwrite) {
+		git_examine(file);
+	}
+
 	update_title();
 	redraw_all();
 }
@@ -4624,12 +4945,41 @@ void perform_replacement(int line_no, uint32_t * needle, uint32_t * replacement,
 	*out_col = -1;
 }
 
+#define COMMAND_HISTORY_MAX 255
+char * command_history[COMMAND_HISTORY_MAX] = {NULL};
+
+void insert_command_history(char * cmd) {
+	/* See if this is already in the history. */
+	size_t amount_to_shift = COMMAND_HISTORY_MAX - 1;
+	for (int i = 0; i < COMMAND_HISTORY_MAX && command_history[i]; ++i) {
+		if (!strcmp(command_history[i], cmd)) {
+			free(command_history[i]);
+			amount_to_shift = i;
+			break;
+		}
+	}
+
+	/* Remove last entry that will roll off the stack */
+	if (amount_to_shift == COMMAND_HISTORY_MAX - 1) {
+		if (command_history[COMMAND_HISTORY_MAX-1]) free(command_history[COMMAND_HISTORY_MAX-1]);
+	}
+
+	/* Roll the history */
+	memmove(&command_history[1], &command_history[0], sizeof(char *) * (amount_to_shift));
+
+	command_history[0] = strdup(cmd);
+}
+
 /**
  * Process a user command.
  */
 void process_command(char * cmd) {
 	/* Special case ! to run shell commands without parsing tokens */
 	int c;
+
+	/* Add command to history */
+	insert_command_history(cmd);
+
 	if (*cmd == '!') {
 		/* Reset and draw some line feeds */
 		reset();
@@ -4838,6 +5188,19 @@ void process_command(char * cmd) {
 		} else {
 			write_file(env->file_name);
 		}
+	} else if (!strcmp(argv[0], "history")) {
+		render_commandline_message(""); /* To clear command line */
+		for (int i = COMMAND_HISTORY_MAX; i > 1; --i) {
+			if (command_history[i-1]) render_commandline_message("%d:%s\n", i-1, command_history[i-1]);
+		}
+		render_commandline_message("\n");
+		redraw_tabbar();
+		redraw_commandline();
+		fflush(stdout);
+		int c;
+		while ((c = bim_getch())== -1);
+		bim_unget(c);
+		redraw_all();
 	} else if (!strcmp(argv[0], "wq")) {
 		/* wq: write file and close buffer; if there's no file to write to, may do weird things */
 		write_file(env->file_name);
@@ -4861,6 +5224,9 @@ void process_command(char * cmd) {
 		try_quit();
 	} else if (!strcmp(argv[0], "qa!")) {
 		/* Forcefully exit editor */
+		while (buffers_len) {
+			buffer_close(buffers[0]);
+		}
 		quit();
 	} else if (!strcmp(argv[0], "tabp")) {
 		/* Next tab */
@@ -4870,6 +5236,23 @@ void process_command(char * cmd) {
 		/* Previous tab */
 		next_tab();
 		update_title();
+	} else if (!strcmp(argv[0], "git")) {
+		if (argc < 2) {
+			render_status_message("git=%d", env->checkgitstatusonwrite);
+		} else {
+			env->checkgitstatusonwrite = !!atoi(argv[1]);
+			if (env->checkgitstatusonwrite && !env->modified && env->file_name) {
+				git_examine(env->file_name);
+				redraw_text();
+			}
+		}
+	} else if (!strcmp(argv[0], "colorgutter")) {
+		if (argc < 2) {
+			render_status_message("colorgutter=%d", global_config.color_gutter);
+		} else {
+			global_config.color_gutter = !!atoi(argv[1]);
+			redraw_text();
+		}
 	} else if (!strcmp(argv[0], "indent")) {
 		env->indent = 1;
 		redraw_statusbar();
@@ -5162,6 +5545,8 @@ void command_tab_complete(char * buffer) {
 		add_candidate("split");
 		add_candidate("splitpercent");
 		add_candidate("unsplit");
+		add_candidate("git");
+		add_candidate("colorgutter");
 		goto _accept_candidate;
 	}
 
@@ -5423,6 +5808,8 @@ void command_mode(void) {
 	printf(":");
 	show_cursor();
 
+	int history_point = -1;
+
 	while ((c = bim_getch())) {
 		if (c == -1) {
 			if (timeout && this_buf[timeout-1] == '\033') {
@@ -5511,10 +5898,22 @@ void command_mode(void) {
 						bim_unget(c);
 						return;
 					case 'A':
-						render_status_message("history up");
+						/* Load from history */
+						if (command_history[history_point+1]) {
+							memcpy(buffer, command_history[history_point+1], strlen(command_history[history_point+1])+1);
+							history_point++;
+							buffer_len = strlen(buffer);
+						}
 						goto _redraw_buffer;
 					case 'B':
-						render_status_message("history down");
+						if (history_point > 0) {
+							history_point--;
+							memcpy(buffer, command_history[history_point], strlen(command_history[history_point])+1);
+						} else {
+							history_point = -1;
+							memset(buffer, 0, 1000);
+						}
+						buffer_len = strlen(buffer);
 						goto _redraw_buffer;
 					case 'C':
 					case 'D':
@@ -5673,6 +6072,19 @@ void search_mode(int direction) {
 
 	redraw_commandline();
 	printf(direction == 1 ? "/" : "?");
+	if (env->search) {
+		printf("\0337");
+		set_colors(COLOR_ALT_FG, COLOR_BG);
+		uint32_t * c = env->search;
+		while (*c) {
+			char tmp[7] = {0}; /* Max six bytes, use 7 to ensure last is always nil */
+			to_eight(*c, tmp);
+			printf("%s", tmp);
+			c++;
+		}
+		printf("\0338");
+		set_colors(COLOR_FG, COLOR_BG);
+	}
 	show_cursor();
 
 	uint32_t state = 0;
@@ -5698,6 +6110,12 @@ void search_mode(int direction) {
 				break;
 			} else if (c == ENTER_KEY || c == LINE_FEED) {
 				/* Exit search */
+				if (!buffer_len) {
+					if (env->search) {
+						search_next();
+					}
+					break;
+				}
 				if (env->search) {
 					free(env->search);
 				}
@@ -7005,13 +7423,7 @@ void line_selection_mode(void) {
 						goto _leave_select_line;
 					case ':': /* Handle command mode specially for redraw */
 						command_mode();
-						for (int i = (env->start_line < env->line_no ? env->start_line : env->line_no);
-							i <= (env->start_line < env->line_no ? env->line_no : env->start_line);
-							++i) {
-							_redraw_line(i,1);
-						}
-						place_cursor_actual();
-						continue;
+						goto _leave_select_line;
 					default:
 						handle_navigation(c);
 						break;
@@ -7264,6 +7676,9 @@ void col_selection_mode(void) {
 						redraw_text();
 						col_insert_mode();
 						goto _leave_select_col;
+					case ':':
+						command_mode();
+						goto _leave_select_col;
 					default:
 						handle_navigation(c);
 						break;
@@ -7490,6 +7905,9 @@ void char_selection_mode(void) {
 						set_preferred_column();
 						set_modified();
 						goto _leave_select_char;
+					case ':':
+						command_mode();
+						goto _leave_select_char;
 					default:
 						handle_navigation(c);
 						break;
@@ -7587,6 +8005,14 @@ void insert_mode(void) {
 						break;
 					case ENTER_KEY:
 					case LINE_FEED:
+						if (env->indent) {
+							if ((env->lines[env->line_no-1]->text[env->col_no-2].flags & 0xF) == FLAG_COMMENT &&
+								(env->lines[env->line_no-1]->text[env->col_no-2].codepoint == ' ') &&
+								(env->col_no > 3) &&
+								(env->lines[env->line_no-1]->text[env->col_no-3].codepoint == '*')) {
+								delete_at_cursor();
+							}
+						}
 						insert_line_feed();
 						redraw |= 2;
 						break;
@@ -7622,6 +8048,20 @@ void insert_mode(void) {
 						redraw |= 1;
 						set_preferred_column();
 						break;
+					case '/':
+						if (env->indent) {
+							if ((env->lines[env->line_no-1]->text[env->col_no-2].flags & 0xF) == FLAG_COMMENT &&
+								(env->lines[env->line_no-1]->text[env->col_no-2].codepoint == ' ') &&
+								(env->col_no > 3) &&
+								(env->lines[env->line_no-1]->text[env->col_no-3].codepoint == '*')) {
+								env->col_no--;
+								replace_char('/');
+								env->col_no++;
+								place_cursor_actual();
+								break;
+							}
+						}
+						goto _just_insert;
 					case '}':
 						if (env->indent) {
 							int was_whitespace = 1;
@@ -7652,6 +8092,7 @@ void insert_mode(void) {
 						}
 						/* fallthrough */
 					default:
+_just_insert:
 						insert_char(c);
 						set_preferred_column();
 						redraw |= 1;
@@ -7670,7 +8111,7 @@ void insert_mode(void) {
 	}
 }
 
-/*
+/**
  * REPLACE mode
  *
  * Like insert, but replaces characters.
@@ -8103,6 +8544,14 @@ void load_bimrc(void) {
 		if (!strcmp(l,"scrollamount") && value) {
 			global_config.scroll_amount = atoi(value);
 		}
+
+		if (!strcmp(l,"git") && value) {
+			global_config.check_git = !!atoi(value);
+		}
+
+		if (!strcmp(l,"colorgutter") && value) {
+			global_config.color_gutter = !!atoi(value);
+		}
 	}
 
 	fclose(bimrc);