From 9c20356b360ef2e1fb596467470529348b567e5d Mon Sep 17 00:00:00 2001
From: Saagar Jha <saagar@saagarjha.com>
Date: Sun, 16 Feb 2020 03:51:38 -0800
Subject: [PATCH] Add an option to detect indentation

With this option enabled, nano will try to detect indentation from the
file automatically, overriding any syntax-specific or tabs-to-spaces
settings for that buffer.

Signed-off-by: Saagar Jha <saagar@saagarjha.com>
---
 src/files.c  | 43 +++++++++++++++++++++++++++++++++++++++++++
 src/nano.c   |  8 +++++++-
 src/nano.h   |  5 ++++-
 src/rcfile.c |  3 +++
 src/text.c   | 12 +++++++++++-
 5 files changed, 68 insertions(+), 3 deletions(-)

diff --git a/src/files.c b/src/files.c
index 62ab9609..80842946 100644
--- a/src/files.c
+++ b/src/files.c
@@ -86,6 +86,8 @@ void make_new_buffer(void)
 	openfile->last_saved = NULL;
 	openfile->last_action = OTHER;
 
+	openfile->indent = NULL;
+
 	openfile->current_stat = NULL;
 	openfile->lock_filename = NULL;
 #endif
@@ -635,6 +637,8 @@ void close_buffer(void)
 	free(orphan->lock_filename);
 	/* Free the undo stack. */
 	discard_until(NULL, orphan, TRUE);
+
+	free(openfile->indent);
 #endif
 
 	openfile = orphan->prev;
@@ -689,6 +693,10 @@ void read_file(FILE *f, int fd, const char *filename, bool undoable)
 #ifndef NANO_TINY
 	int format = 0;
 		/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
+	size_t indent_counts[16] = {};
+		/* How often each indent is seen. 0 is for tabs. */
+	size_t last_indent = 0;
+		/* The number of spaces of indentation in the last line we saw. */
 #endif
 
 	buf = charalloc(bufx);
@@ -770,6 +778,24 @@ void read_file(FILE *f, int fd, const char *filename, bool undoable)
 		/* If it's a DOS or Mac line, strip the '\r' from it. */
 		if (len > 0 && buf[len - 1] == '\r' && !ISSET(NO_CONVERT))
 			buf[--len] = '\0';
+
+		if (ISSET(DETECT_INDENT)) {
+			if (*buf == '\t') {
+				indent_counts[last_indent = 0]++;
+			} else {
+				size_t indent = 0;
+				while (indent < len && buf[indent] == ' ') {
+					++indent;
+				}
+				size_t difference = indent < last_indent ?
+					last_indent - indent : indent - last_indent;
+				if (difference && difference < sizeof(indent_counts) /
+					sizeof(*indent_counts)) {
+					indent_counts[difference]++;
+				}
+				last_indent = indent;
+			}
+		}
 #endif
 
 		/* Store the data and make a new line. */
@@ -890,6 +916,23 @@ void read_file(FILE *f, int fd, const char *filename, bool undoable)
 
 	if (ISSET(MAKE_IT_UNIX))
 		openfile->fmt = NIX_FILE;
+
+	char *indent = charalloc(sizeof(indent_counts) /
+		sizeof(*indent_counts) + 1);
+	int prevailing_indent = 0;
+	for (size_t i = 0; i < sizeof(indent_counts) / sizeof(*indent_counts); ++i) {
+		if (indent_counts[prevailing_indent] < indent_counts[i]) {
+			prevailing_indent = i;
+		}
+	}
+	if (!prevailing_indent) {
+		*indent = '\t';
+		++prevailing_indent;
+	} else {
+		memset(indent, ' ', prevailing_indent);
+	}
+	indent[prevailing_indent] = '\0';
+	openfile->indent = indent;
 #endif
 }
 
diff --git a/src/nano.c b/src/nano.c
index 911c3e59..a6fae32a 100644
--- a/src/nano.c
+++ b/src/nano.c
@@ -1837,6 +1837,7 @@ int main(int argc, char **argv)
 		{"atblanks", 0, NULL, 'a'},
 		{"autoindent", 0, NULL, 'i'},
 		{"cutfromcursor", 0, NULL, 'k'},
+		{"detectindent", 0, NULL, 'q'},
 		{"unix", 0, NULL, 'u'},
 		{"afterends", 0, NULL, 'y'},
 		{"softwrap", 0, NULL, '$'},
@@ -1896,7 +1897,7 @@ int main(int argc, char **argv)
 		SET(RESTRICTED);
 
 	while ((optchr = getopt_long(argc, argv, "ABC:DEFGHIJ:KLMNOPQ:RST:UVWX:Y:Z"
-				"abcdef:ghijklmno:pr:s:tuvwxyz$", long_options, NULL)) != -1) {
+				"abcdef:ghijklmno:pqr:s:tuvwxyz$", long_options, NULL)) != -1) {
 		switch (optchr) {
 #ifndef NANO_TINY
 			case 'A':
@@ -2094,6 +2095,11 @@ int main(int argc, char **argv)
 #endif
 				break;
 #endif
+#ifndef NANO_TINY
+			case 'q':
+				SET(DETECT_INDENT);
+				break;
+#endif
 #ifdef ENABLE_SPELLER
 			case 's':
 				alt_speller = mallocstrcpy(alt_speller, optarg);
diff --git a/src/nano.h b/src/nano.h
index c008d385..6cf6a14a 100644
--- a/src/nano.h
+++ b/src/nano.h
@@ -393,6 +393,8 @@ typedef struct openfilestruct {
 		/* The undo item at which the file was last saved. */
 	undo_type last_action;
 		/* The type of the last action the user performed. */
+	char *indent;
+		/* The detected indent for this file. */
 #endif
 	bool modified;
 		/* Whether the file has been modified. */
@@ -535,7 +537,8 @@ enum
 	LET_THEM_ZAP,
 	BREAK_LONG_LINES,
 	JUMPY_SCROLLING,
-	EMPTY_LINE
+	EMPTY_LINE,
+	DETECT_INDENT,
 };
 
 /* Flags for the menus in which a given function should be present. */
diff --git a/src/rcfile.c b/src/rcfile.c
index 5213e277..c30da345 100644
--- a/src/rcfile.c
+++ b/src/rcfile.c
@@ -102,6 +102,7 @@ static const rcoption rcopts[] = {
 	{"backupdir", 0},
 	{"casesensitive", CASE_SENSITIVE},
 	{"cutfromcursor", CUT_FROM_CURSOR},
+	{"detectindent", DETECT_INDENT},
 	{"guidestripe", 0},
 	{"locking", LOCKING},
 	{"matchbrackets", 0},
@@ -453,6 +454,8 @@ keystruct *strtosc(const char *input)
 			s->toggle = SMART_HOME;
 		else if (!strcmp(input, "autoindent"))
 			s->toggle = AUTOINDENT;
+		else if (!strcmp(input, "detectindent"))
+			s->toggle = DETECT_INDENT;
 		else if (!strcmp(input, "cutfromcursor"))
 			s->toggle = CUT_FROM_CURSOR;
 #ifdef ENABLE_WRAPPING
diff --git a/src/text.c b/src/text.c
index bed68de2..b92c8675 100644
--- a/src/text.c
+++ b/src/text.c
@@ -67,8 +67,13 @@ void do_mark(void)
  * of spaces that a tab would normally take up. */
 void do_tab(void)
 {
+#ifndef NANO_TINY
+	if (ISSET(DETECT_INDENT)) {
+		inject(openfile->indent, strlen(openfile->indent));
+	}
+#endif
 #ifdef ENABLE_COLOR
-	if (openfile->syntax && openfile->syntax->tab)
+	else if (openfile->syntax && openfile->syntax->tab)
 		inject(openfile->syntax->tab, strlen(openfile->syntax->tab));
 	else
 #endif
@@ -157,6 +162,11 @@ void do_indent(void)
 	for (line = top; line != bot->next; line = line->next) {
 		char *real_indent = (line->data[0] == '\0') ? "" : indentation;
 
+#ifndef NANO_TINY
+		if (ISSET(DETECT_INDENT))
+			real_indent = openfile->indent;
+#endif
+
 		indent_a_line(line, real_indent);
 		update_multiline_undo(line->lineno, real_indent);
 	}
-- 
2.25.0

