From 88be8e48c57929da57f0ccf8aec525cbea4dbe3b Mon Sep 17 00:00:00 2001
From: Devin Hussey <husseydevin@gmail.com>
Date: Fri, 25 May 2018 22:57:27 -0400
Subject: [PATCH 1/1] Use buffered reading and a progress meter for faster file
 opening.

Instead of repeatedly reading the file byte by byte, nano will now read in 8KiB
chunks. This makes large file reading significantly faster, with a 49MB file
opening 3 times faster.

Additionally, in a non-tiny build on files 8MiB or larger, nano will show the
percentage of the file read, followed by a message saying that it is processing.

The 8MiB limit is when the [ Reading File ] message begins to become visible,
at least on my mediocre 2009 MacBook with a 5400 RPM 1 TB drive.

These times are from doing the following:
 1. sudo purge to purge any cache
 2. starting a QuickTime screen recording on my iTerm2 window
 3. opening a 49M file (nano's configure script 50 times)
 4. buffering a ^X to exit as soon as the file is opened (this should really
    cancel the read, but whatever).

Before: https://streamable.com/9dnts
real    0m22.280s
user    0m7.402s
sys     0m0.520s

After: https://streamable.com/7p1g8
real    0m7.166s
user    0m2.213s
sys     0m0.341s

Signed-off-by: Devin Hussey <husseydevin@gmail.com>
---
 src/files.c | 154 +++++++++++++++++++++++++++++++++-------------------
 1 file changed, 98 insertions(+), 56 deletions(-)

diff --git a/src/files.c b/src/files.c
index 00e2e03a..1003cfc8 100644
--- a/src/files.c
+++ b/src/files.c
@@ -728,6 +728,8 @@ void read_file(FILE *f, int fd, const char *filename, bool undoable)
 		/* The length of the current line of the file. */
 	char input = '\0';
 		/* The current input character. */
+	char iobuf[8192];
+		/* The 8K buffer that we use to read the file in chunks into. */
 	char *buf;
 		/* The buffer in which we assemble each line of the file. */
 	size_t bufx = MAX_BUF_SIZE;
@@ -736,14 +738,22 @@ void read_file(FILE *f, int fd, const char *filename, bool undoable)
 		/* The top of the new buffer where we store the read file. */
 	filestruct *bottomline;
 		/* The bottom of the new buffer. */
-	int input_int;
-		/* The current value we read from the file, whether an input
-		 * character or EOF. */
 	bool writable = TRUE;
 		/* Whether the file is writable (in case we care). */
+	off_t count = 0;
+	off_t i = 0;
+		/* Both counters for file reading. */
 #ifndef NANO_TINY
 	int format = 0;
 		/* 0 = *nix, 1 = DOS, 2 = Mac, 3 = both DOS and Mac. */
+	struct stat filestat;
+		/* A stat to check the filesize. */
+	off_t bytesread = 0;
+		/* The number of bytes read in the file. */
+	off_t filesize;
+		/* The size of the file. */
+	int percent = -1;
+		/* The percentage of the file read for the status line. */
 #endif
 
 	buf = charalloc(bufx);
@@ -754,6 +764,10 @@ void read_file(FILE *f, int fd, const char *filename, bool undoable)
 
 	if (ISSET(SOFTWRAP))
 		was_leftedge = leftedge_for(xplustabs(), openfile->current);
+
+	/* Check the size of the file for a potential progress bar. */
+	fstat(fd, &filestat);
+	filesize = filestat.st_size;
 #endif
 
 	/* Create an empty buffer. */
@@ -761,73 +775,93 @@ void read_file(FILE *f, int fd, const char *filename, bool undoable)
 	bottomline = topline;
 
 	/* Read the entire file into the new buffer. */
-	while ((input_int = getc(f)) != EOF) {
-		input = (char)input_int;
-
-		/* If it's a *nix file ("\n") or a DOS file ("\r\n"), and file
-		 * conversion isn't disabled, handle it! */
-		if (input == '\n') {
+	while (!feof(f) && !ferror(f)) {
+		i = 0;
 #ifndef NANO_TINY
-			/* If it's a DOS file or a DOS/Mac file ('\r' before '\n' on
-			 * the first line if we think it's a *nix file, or on any
-			 * line otherwise), and file conversion isn't disabled,
-			 * handle it! */
-			if ((num_lines == 0 || format != 0) && !ISSET(NO_CONVERT) &&
-						len > 0 && buf[len - 1] == '\r') {
-				if (format == 0 || format == 2)
-					format++;
+		/* Show a reading percentage on files >= 8 MB, which seems to be the point
+		 * where nano starts to "take its time" reading the file. */
+		if (filesize >= 8 * 1024 * 1024 && bytesread < filesize) {
+			int oldpercent = percent;
+			percent = bytesread * 100 / filesize;
+			if (percent != oldpercent) {
+				statusline(HUSH, _("Reading File... %d%%"), percent);
 			}
-		/* If it's a Mac file ('\r' without '\n' on the first line if we
-		 * think it's a *nix file, or on any line otherwise), and file
-		 * conversion isn't disabled, handle it! */
-		} else if ((num_lines == 0 || format != 0) && !ISSET(NO_CONVERT) &&
-						len > 0 && buf[len - 1] == '\r') {
-			/* If we currently think the file is a *nix file, set format
-			 * to Mac.  If we currently think the file is a DOS file,
-			 * set format to both DOS and Mac. */
-			if (format == 0 || format == 1)
-				format += 2;
+			bytesread += 8192;
+		}
 #endif
-		} else {
-			/* Store the character. */
-			buf[len] = input;
-
-			/* Keep track of the total length of the line.  It might have
-			 * nulls in it, so we can't just use strlen() later. */
-			len++;
-
-			/* If needed, increase the buffer size, MAX_BUF_SIZE characters at
-			 * a time.  Don't bother decreasing it; it is freed at the end. */
-			if (len == bufx) {
-				bufx += MAX_BUF_SIZE;
-				buf = charealloc(buf, bufx);
-			}
+		count = fread(iobuf, sizeof(char), 8192, f);
+
+		if (!count) {
 			continue;
 		}
 
+		while (i < count) {
+			input = iobuf[i++];
+
+			/* If it's a *nix file ("\n") or a DOS file ("\r\n"), and file
+			 * conversion isn't disabled, handle it! */
+			if (input == '\n') {
 #ifndef NANO_TINY
-		/* 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 it's a DOS file or a DOS/Mac file ('\r' before '\n' on
+				 * the first line if we think it's a *nix file, or on any
+				 * line otherwise), and file conversion isn't disabled,
+				 * handle it! */
+				if ((num_lines == 0 || format != 0) && !ISSET(NO_CONVERT) &&
+							len > 0 && buf[len - 1] == '\r') {
+					if (format == 0 || format == 2)
+						format++;
+				}
+			/* If it's a Mac file ('\r' without '\n' on the first line if we
+			 * think it's a *nix file, or on any line otherwise), and file
+			 * conversion isn't disabled, handle it! */
+			} else if ((num_lines == 0 || format != 0) && !ISSET(NO_CONVERT) &&
+							len > 0 && buf[len - 1] == '\r') {
+				/* If we currently think the file is a *nix file, set format
+				 * to Mac.  If we currently think the file is a DOS file,
+				 * set format to both DOS and Mac. */
+				if (format == 0 || format == 1)
+					format += 2;
+#endif
+			} else {
+				/* Store the character. */
+				buf[len] = input;
+
+				/* Keep track of the total length of the line.  It might have
+				 * nulls in it, so we can't just use strlen() later. */
+				len++;
+
+				/* If needed, increase the buffer size, MAX_BUF_SIZE characters at
+				 * a time.  Don't bother decreasing it; it is freed at the end. */
+				if (len == bufx) {
+					bufx += MAX_BUF_SIZE;
+					buf = charealloc(buf, bufx);
+				}
+				continue;
+			}
+
+#ifndef NANO_TINY
+			/* 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';
 #endif
 
-		/* Store the data and make a new line. */
-		bottomline->data = encode_data(buf, len);
-		bottomline->next = make_new_node(bottomline);
-		bottomline = bottomline->next;
-		num_lines++;
+			/* Store the data and make a new line. */
+			bottomline->data = encode_data(buf, len);
+			bottomline->next = make_new_node(bottomline);
+			bottomline = bottomline->next;
+			num_lines++;
 
-		/* Reset the length in preparation for the next line. */
-		len = 0;
+			/* Reset the length in preparation for the next line. */
+			len = 0;
 
 #ifndef NANO_TINY
-		/* If it happens to be a Mac line, store the character after the \r
-		 * as the first character of the next line. */
-		if (input != '\n')
-			buf[len++] = input;
+			/* If it happens to be a Mac line, store the character after the \r
+			 * as the first character of the next line. */
+			if (input != '\n')
+				buf[len++] = input;
 #endif
+		}
 	}
-
 	/* Perhaps this could use some better handling. */
 	if (ferror(f))
 		nperror(filename);
@@ -872,6 +906,14 @@ void read_file(FILE *f, int fd, const char *filename, bool undoable)
 
 	free(buf);
 
+#ifndef NANO_TINY
+	/* Parsing files can take a while, and there isn't really a faster way to do this.
+	 * Again, only show progress on files >= 8 MB.
+	 * TODO: Show progress for processing. */
+	if (filesize >= 8 * 1024 * 1024)
+		statusbar(_("Processing file, please wait..."));
+#endif
+
 	/* Insert the just read buffer into the current one. */
 	ingraft_buffer(topline);
 
-- 
2.17.0

