private void doUpdate() {
		long stopMS = System.currentTimeMillis() + MAX_UPDATE_INTERVAL_MS;

		// track the caret manually because removing the text where the caret is located
		// will reset the caret position to 0, even with the update police NEVER_UPDATE.
		int caretPos = getCaretPosition();
		boolean caretInvalidated = false;
		synchronized (messageList) {
			// Holding the sync lock on the messageList will block the thread producing
			// messages while we clear the queue.  This is desirable to throttle a run-away
			// GhidraScript.
			while (!messageList.isEmpty() && (System.currentTimeMillis() < stopMS)) {
				MessageWrapper msg = messageList.removeFirst();
				caretInvalidated |= appendString(msg.getMessage(), msg.getAttributes());
			}
			if (!messageList.isEmpty()) {
				updateManager.updateLater();
			}
		}
		if (!scrollLock || caretInvalidated) {
			// manually set the caret position because it was
			// 1) invalidated (even though scroll lock was true), or
			// 2) is tracking the bottom of the console (normal mode)
			int newDocLen = getDocument().getLength();
			setCaretPosition(scrollLock ? Math.min(caretPos, newDocLen) : newDocLen);
		}
	}
% of characters to delete when truncation is necessary
Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java