From 3eb0a64df3b2d71f109f640bf33429ddfd068c3a Mon Sep 17 00:00:00 2001
From: james <jamesragray@bitbucket.org>
Date: Sun, 19 Feb 2023 23:56:53 +0200
Subject: [PATCH] Improve cursor tracking.

---
 terminal.d | 127 +++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 88 insertions(+), 39 deletions(-)

diff --git a/terminal.d b/terminal.d
index 3ad87ab..e0537cf 100644
--- a/terminal.d
+++ b/terminal.d
@@ -2158,7 +2158,10 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
 	void writeln(T...)(T t) {
 		write(t, "\n");
 	}
-
+        import std.uni;
+        int[Grapheme] graphemeWidth;
+        bool willInsertFollowingLine = false;
+        bool uncertainIfAtEndOfLine = false;
 	/+
 	/// A combined moveTo and writef that puts the cursor back where it was before when it finishes the write.
 	/// Only works in cellular mode. 
@@ -2180,51 +2183,93 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
 		writeStringRaw(toWrite, ForceOption.alwaysSend);
 	}
 	+/
+        void writePrintableString(const(char)[] s, ForceOption force = ForceOption.automatic) {
+                while(s.length > 0) {
+                        size_t index = 0;
+                        import std.utf;
+                        import std.uni;
+                        import std.range;
+                        bool written=false;
+                        loop:
+                        foreach(g; s.byDchar.byGrapheme) {
+                                index += g[].byChar.walkLength;
+                                if(uncertainIfAtEndOfLine) {
+                                        uncertainIfAtEndOfLine = false;
+                                        writePrintableString_(s[0..index],force);
+                                        written = true;
+                                        updateCursorPosition();
+                                        break;
+                                }
+                                if(willInsertFollowingLine) {
+                                        willInsertFollowingLine = false;
+                                        _cursorX = 0;
+                                        _cursorY++;
+                                        if(_cursorY >= height) { _cursorY--; }
+                                }
+                                switch(g[0]) {
+                                        case '\n':
+                                                _cursorX = 0;
+                                                _cursorY++;
+                                                break;
+                                        case '\r':
+                                                _cursorX = 0;
+                                                break;
+                                        case '\t':
+                                                // FIXME: get the actual tabstop, if possible
+                                                int diff = 8 - (_cursorX % 8);
+                                                if(diff == 0)
+                                                        diff = 8;
+                                                _cursorX += diff;
+                                                break;
+                                        default:
+                                                if(auto ptr = g in graphemeWidth) {
+                                                        _cursorX += *ptr;
 
-	void writePrintableString(const(char)[] s, ForceOption force = ForceOption.automatic) {
+                                                        if(_wrapAround) {
+                                                                if(_cursorX == width) {
+                                                                        _cursorX--;
+                                                                        willInsertFollowingLine = true;
+                                                                }
+                                                                else if(cursorX > width) {
+                                                                        _cursorX = *ptr;
+                                                                        _cursorY++;
+                                                                        if(_cursorY == height)
+                                                                                _cursorY--;
+                                                                }
+                                                        }
+
+                                                }
+                                                else {
+                                                        int x = _cursorX;
+                                                        int y = _cursorY;
+                                                        writePrintableString_(s[0..index],force);
+                                                        written = true;
+                                                        updateCursorPosition();
+                                                        //FIXME: At some point it might be worthwhile to check if the next wrtie will wrap on those terminals that support it
+                                                        //Note that: tmux (and screen?) seem to signal that the next write will wrap by reporting cursorX==width
+                                                        if (_cursorX+1< width || _cursorX == width)  {
+                                                                graphemeWidth[g] = _cursorY > y
+                                                                        ? _cursorX
+                                                                        : _cursorX - x;
+                                                                if(_cursorX == width) { willInsertFollowingLine = true; }
+                                                        }
+                                                        else { uncertainIfAtEndOfLine = true; }
+                                                        break loop;
+                                                }
+                                }
+                        }
+                        if(!written) { writePrintableString_(s[0..index],force); }
+                        s = s[index..$];
+                }
+        }
+
+	void writePrintableString_(const(char)[] s, ForceOption force = ForceOption.automatic) {
 		// an escape character is going to mess things up. Actually any non-printable character could, but meh
 		// assert(s.indexOf("\033") == -1);
 
 		if(s.length == 0)
 			return;
 
-		// tracking cursor position
-		// FIXME: by grapheme?
-		foreach(dchar ch; s) {
-			switch(ch) {
-				case '\n':
-					_cursorX = 0;
-					_cursorY++;
-				break;
-				case '\r':
-					_cursorX = 0;
-				break;
-				case '\t':
-					// FIXME: get the actual tabstop, if possible
-					int diff = 8 - (_cursorX % 8);
-					if(diff == 0)
-						diff = 8;
-					_cursorX += diff;
-				break;
-				default:
-					_cursorX++;
-			}
-
-			if(_wrapAround && _cursorX > width) {
-				_cursorX = 0;
-				_cursorY++;
-			}
-
-			if(_cursorY == height)
-				_cursorY--;
-
-			/+
-			auto index = getIndex(_cursorX, _cursorY);
-			if(data[index] != ch) {
-				data[index] = ch;
-			}
-			+/
-		}
 
 		version(TerminalDirectToEmulator) {
 			// this breaks up extremely long output a little as an aid to the
@@ -2454,6 +2499,10 @@ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683193%28v=vs.85%29.as
 			terminal._cursorY = terminal.tew.terminalEmulator.cursorY;
 		} else
 			updateCursorPosition_impl();
+               if(_cursorX == width) {
+                       willInsertFollowingLine = true;
+                       _cursorX--;
+               }
 	}
 	private void updateCursorPosition_impl() {
 		auto terminal = &this;