tui_text.inc
8l_HeavyThing/tui_text.inc
	; ------------------------------------------------------------------------
	; HeavyThing x86_64 assembly language library and showcase programs
	; Copyright Â© 2015, 2016 2 Ton Digital 
	; Homepage: https://2ton.com.au/
	; Author: Jeff Marrison <jeff@2ton.com.au>
	;       
	; This file is part of the HeavyThing library.
	;       
	; HeavyThing is free software: you can redistribute it and/or modify
	; it under the terms of the GNU General Public License, or
	; (at your option) any later version.
	;       
	; HeavyThing is distributed in the hope that it will be useful, 
	; but WITHOUT ANY WARRANTY; without even the implied warranty of
	; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
	; GNU General Public License for more details.
	;       
	; You should have received a copy of the GNU General Public License along
	; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
	; ------------------------------------------------------------------------
	;       
	; tui_text.inc: a more feature-filled text display/editor
	;
	; NOTE: this is by no means the most efficient way to do things...
	; for some _huge_ text, this would have to do a TON of work, but for all of my purposes
	; this is more than sufficient, and a relatively clean way of doing things
	; the "TON of work" comes in from the fact that I did individual objects for each
	; line of text, so if we were doing a 512MB text file, and then our component was
	; resized, it would have to recompose [the view of] each and every line...
	; this is in opposition to the "right way", which for huge text would be a fileshadow
	; of modifications to the underlying/initial text, and then viewport of visible-only
	; but of course that requires a _LOT_ more work and calculations and is generally more
	; difficult.
	; 
	; the line-by-line composed view still WORKS for huge data, and really isn't even that
	; bad for fixed-size tui-level dimensions on the component itself, but you'd definitely
	; notice my design choice with multi-million-line text and width-resizing events.
	;
	; for right aligned text combined with !wrap, I am not sure I entirely like the way this
	; ended up... while it does work and do what it should, the way the horizontal scrolling
	; works for non-fully-recomposed views is a bit "hmm"... the cursor gutter is weird, etc.
	; YMMV.
	;
	; for right aligned text with wrap enabled, I am forcing a 1 char gutter, mainly because
	; I am lazy and don't feel like doing all the extra work necessary to only enforce it for
	; the logical end of line... TODO: someday when I am bored or need that particular feature
	; hehe, fix it up.
	;
	; Further, while my design here perfectly allows for centered and justified composition
	; none of my collection requires them, so they are breakpointed and marked with TODOs for
	; someday when I am bored and have nothing better to do.
	;

if used tui_text$vtable | defined include_everything

dalign
tui_text$vtable:
	dq      tui_text$cleanup, tui_text$clone, tui_text$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
	dq      tui_object$timer, tui_object$layoutchanged, tui_object$move, tui_object$setfocus, tui_text$gotfocus, tui_text$lostfocus
	dq      tui_text$keyevent, tui_object$domodal, tui_object$endmodal, tui_object$exit, tui_object$calcbounds, tui_object$calcchildbounds
	dq      tui_object$appendchild, tui_object$appendbastard, tui_object$prependchild, tui_object$contains, tui_object$getchildindex
	dq      tui_object$removechild, tui_object$removebastard, tui_object$removeallchildren, tui_object$removeallbastards
	dq      tui_object$getobjectsunderpoint, tui_object$flatten, tui_object$firekeyevent, tui_object$ontab, tui_object$onshifttab
	dq      tui_text$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked
	; we add one method to our vtable for onenter handling
	dq      tui_text$onenter

end if

tui_vonenter = tui_vclicked + 8

tui_text_initial_ofs = tui_object_size			; string copy of the initial text, needed in case we have to clone ourselves
tui_text_colors_ofs = tui_object_size + 8
tui_text_focuscolors_ofs = tui_object_size + 12
tui_text_multiline_ofs = tui_object_size + 16		; bool as to whether we are a multiline or singleline editor (defaults to false)
tui_text_dospinner_ofs = tui_object_size + 20		; we delay creation of our spinner until we have valid bounds, bool here as to whether we fire one up or not
tui_text_focussed_ofs = tui_object_size + 24		; bool as to whether we have focus or not
tui_text_pwdchar_ofs = tui_object_size + 28		; !0 == display all characters as this
tui_text_minlen_ofs = tui_object_size + 32		; !0 == minimum editable scroll position in the editorline... NOTE: initial text must be at least this long if this is used
tui_text_maxlen_ofs = tui_object_size + 36		; !0 == max chars we'll take
tui_text_align_ofs = tui_object_size + 40		; one of: tui_textalign_{left,right} (defaults to left)
tui_text_wrap_ofs = tui_object_size + 44		; 0 == horiz scroll, 1 == hardwrap, 2 == wordwrap (defaults to 0)
tui_text_heightlock_ofs = tui_object_size + 48		; !0 == height gets locked to text, this value == minimum value (such that adding more text will cause height++, then layoutchanged)
							; note: heightlock doesn't make sense if you initialized it with a heightperc instead.
tui_text_editable_ofs = tui_object_size + 52		; bool as to whether we allow edits or not, defaults to true
tui_text_docursor_ofs = tui_object_size + 56		; bool as to whether we bother with the cursor at all, defaults to true
tui_text_spinner_ofs = tui_object_size + 60		; pointer to a tui_spinner if we created one
tui_text_lines_ofs = tui_object_size + 68		; a list$new of our editor lines (actual text, in buffers)
tui_text_viewlines_ofs = tui_object_size + 76		; a list$new of our view lines (1:1 or 1:many from lines, in buffers)
tui_text_cursormap_ofs = tui_object_size + 84		; a list$new of our cursor map (1:1 or 1:many from lines, in buffers)
tui_text_topline_ofs = tui_object_size + 92		; a pointer to the viewlines list _item_ (so we can walk it directly) of the topmost visible line
tui_text_bottomline_ofs = tui_object_size + 100		; "" bottommost
tui_text_cursorline_ofs = tui_object_size + 108		; a pointer to the cursormap list _item_ that our cursor is sitting on
tui_text_cursor_ofs = tui_object_size + 116		; the point for our "real" cursor window position (0,0..width,height) bounded, not editor position
tui_text_prevwidth_ofs = tui_object_size + 124		; used to determine whether we need to "recompose" ourselves when a draw event comes through
tui_text_prevheight_ofs = tui_object_size + 128		; used to determine whether we grew or shrank, so we can modify topline/bottomline intelligently
tui_text_cursorx_ofs = tui_object_size + 132		; this is our x offset in BYTES into our cursorline object
							; noting here, when cursorline itself gets modified, this needs to be revalidated, along with the cursor position itself
tui_text_xscroll_ofs = tui_object_size + 136		; if we are !wrap, this is the horizontal scroll offset in bytes
tui_text_user_ofs = tui_object_size + 140		; not used in here, an extra 8 bytes for outer layers to keep if they want

tui_text_size = tui_object_size + 148


	; as mentioned in my commentary atop, this isn't the most efficient design, as each line of editor text requires 3 separate buffers
	; and the code to keep track of it all any other way would be way too complicated for my tastes...


	; we have some non-virtual functions as well, see nvgettext, nvsettext

	; also note: if you want to enable multiline (disabled by default in the new_* functions)
	; just set the tui_text_multiline_ofs dword to nonzero after your call to new_*
	; same same for the other options, just makes it nicer for argument handling in the new_ functions

	; NOTE NOTE : if you modify settings after your call to new_*, _and_ you have non-empty initial text
	; you will _have_ to call nvsettingsupdate

if used tui_text$new_ii | defined include_everything
	; six arguments: edi == width, esi == height, rdx == initialtext (string, or null), ecx == colors, r8d == focuscolors, r9d == bool as to whether we fire up a spinner or not
falign
tui_text$new_ii:
	prolog	tui_text$new_ii
	sub	rsp, 40
	mov	[rsp], edi
	mov	[rsp+4], esi
	mov	[rsp+8], rdx
	mov	[rsp+16], ecx
	mov	[rsp+20], r8d
	mov	[rsp+24], r9d
	test	rdx, rdx
	jz	.noinitialtext
	mov	rdi, rdx
	call	string$copy
	mov	[rsp+8], rax
calign
.noinitialtext:
	mov	edi, tui_text_size
	call	heap$alloc_clear
	mov	[rsp+32], rax
	mov	rdi, rax
	mov	esi, [rsp]
	mov	edx, [rsp+4]
	call	tui_object$init_ii
	mov	rdi, [rsp+32]
	mov	rsi, [rsp+8]
	mov	edx, [rsp+16]
	mov	ecx, [rsp+20]
	mov	r8d, [rsp+24]
	mov	qword [rdi], tui_text$vtable
	mov	[rdi+tui_text_initial_ofs], rsi
	mov	[rdi+tui_text_colors_ofs], edx
	mov	[rdi+tui_text_focuscolors_ofs], ecx
	mov	[rdi+tui_text_dospinner_ofs], r8d
	call	tui_text$nvsetup

	mov	rax, [rsp+32]
	add	rsp, 40
	epilog

end if

if used tui_text$new_dd | defined include_everything
	; six arguments: xmm0 == widthperc, xmm1 == heighperc, rdi == initialtext (string, or null), esi == colors, edx == focuscolors, ecx == bool dospinner
falign
tui_text$new_dd:
	prolog	tui_text$new_dd
	sub	rsp, 40
	movq	r8, xmm0
	movq	r9, xmm1
	mov	[rsp], rdi
	mov	[rsp+8], esi
	mov	[rsp+12], edx
	mov	[rsp+16], ecx
	mov	[rsp+20], r8
	mov	[rsp+28], r9
	test	rdi, rdi
	jz	.noinitialtext
	call	string$copy
	mov	[rsp], rax
calign
.noinitialtext:
	mov	edi, tui_text_size
	call	heap$alloc_clear
	movq	xmm0, [rsp+20]
	movq	xmm1, [rsp+28]
	mov	[rsp+20], rax
	mov	rdi, rax
	call	tui_object$init_dd
	mov	rdi, [rsp+20]
	mov	rsi, [rsp]
	mov	edx, [rsp+8]
	mov	ecx, [rsp+12]
	mov	r8d, [rsp+16]
	mov	qword [rdi], tui_text$vtable
	mov	[rdi+tui_text_initial_ofs], rsi
	mov	[rdi+tui_text_colors_ofs], edx
	mov	[rdi+tui_text_focuscolors_ofs], ecx
	mov	[rdi+tui_text_dospinner_ofs], r8d
	call	tui_text$nvsetup
	mov	rax, [rsp+20]
	add	rsp, 40
	epilog

end if

if used tui_text$new_id | defined include_everything
	; six arguments: edi == width, xmm0 == heighperc, rsi == initialtext (string, or null), edx == colors, ecx == focuscolors, r8d == bool dospinner
falign
tui_text$new_id:
	prolog	tui_text$new_id
	sub	rsp, 40
	movq	r9, xmm0
	mov	[rsp], edi
	mov	[rsp+4], r9
	mov	[rsp+12], rsi
	mov	[rsp+20], edx
	mov	[rsp+24], ecx
	mov	[rsp+28], r8d
	test	rsi, rsi
	jz	.noinitialtext
	mov	rdi, rsi
	call	string$copy
	mov	[rsp+12], rax
calign
.noinitialtext:
	mov	edi, tui_text_size
	call	heap$alloc_clear
	movq	xmm0, [rsp+4]
	mov	[rsp+4], rax
	mov	rdi, rax
	mov	esi, [rsp]
	call	tui_object$init_id
	mov	rdi, [rsp+4]
	mov	rsi, [rsp+12]
	mov	edx, [rsp+20]
	mov	ecx, [rsp+24]
	mov	r8d, [rsp+28]
	mov	qword [rdi], tui_text$vtable
	mov	[rdi+tui_text_initial_ofs], rsi
	mov	[rdi+tui_text_colors_ofs], edx
	mov	[rdi+tui_text_focuscolors_ofs], ecx
	mov	[rdi+tui_text_dospinner_ofs], r8d
	call	tui_text$nvsetup
	mov	rax, [rsp+4]
	add	rsp, 40
	epilog

end if

if used tui_text$new_di | defined include_everything
	; six arugments: xmm0 == widthperc, edi == height, rsi == initialtext (string, or null), edx == colors, ecx == focuscolors, r8d == bool dospinner
falign
tui_text$new_di:
	prolog	tui_text$new_di
	sub	rsp, 40
	movq	r9, xmm0
	mov	[rsp], edi
	mov	[rsp+4], r9
	mov	[rsp+12], rsi
	mov	[rsp+20], edx
	mov	[rsp+24], ecx
	mov	[rsp+28], r8d
	test	rsi, rsi
	jz	.noinitialtext
	mov	rdi, rsi
	call	string$copy
	mov	[rsp+12], rax
calign
.noinitialtext:
	mov	edi, tui_text_size
	call	heap$alloc_clear
	movq	xmm0, [rsp+4]
	mov	[rsp+4], rax
	mov	rdi, rax
	mov	esi, [rsp]
	call	tui_object$init_di
	mov	rdi, [rsp+4]
	mov	rsi, [rsp+12]
	mov	edx, [rsp+20]
	mov	ecx, [rsp+24]
	mov	r8d, [rsp+28]
	mov	qword [rdi], tui_text$vtable
	mov	[rdi+tui_text_initial_ofs], rsi
	mov	[rdi+tui_text_colors_ofs], edx
	mov	[rdi+tui_text_focuscolors_ofs], ecx
	mov	[rdi+tui_text_dospinner_ofs], r8d
	call	tui_text$nvsetup
	mov	rax, [rsp+4]
	add	rsp, 40
	epilog

end if

if used tui_text$new_rect | defined include_everything
	; five arguments: rdi == pointer to a bounds rect, rsi == initialtext (string, or null), edx == colors, ecx == focuscolors, r8d == bool dospinner
falign
tui_text$new_rect:
	prolog	tui_text$new_rect
	sub	rsp, 40
	mov	[rsp], rdi
	mov	[rsp+8], rsi
	mov	[rsp+16], edx
	mov	[rsp+20], ecx
	mov	[rsp+24], r8d
	test	rsi, rsi
	jz	.noinitialtext
	mov	rdi, rsi
	call	string$copy
	mov	[rsp+8], rax
calign
.noinitialtext:
	mov	edi, tui_text_size
	call	heap$alloc_clear
	mov	rdi, rax
	mov	rsi, [rsp]
	mov	[rsp], rax
	call	tui_object$init_rect
	mov	rdi, [rsp]
	mov	rsi, [rsp+8]
	mov	edx, [rsp+16]
	mov	ecx, [rsp+20]
	mov	r8d, [rsp+24]
	mov	qword [rdi], tui_text$vtable
	mov	[rdi+tui_text_initial_ofs], rsi
	mov	[rdi+tui_text_colors_ofs], edx
	mov	[rdi+tui_text_focuscolors_ofs], ecx
	mov	[rdi+tui_text_dospinner_ofs], r8d
	call	tui_text$nvsetup
	mov	rax, [rsp]
	add	rsp, 40
	epilog

end if

if used tui_text$nvsetup | defined include_everything
	; single argument in rdi: the tui_text object we are setting up
falign
tui_text$nvsetup:
	prolog	tui_text$nvsetup
	push	rdi
	call	list$new
	mov	rdi, [rsp]
	mov	[rdi+tui_text_viewlines_ofs], rax
	call	list$new
	mov	rdi, [rsp]
	mov	[rdi+tui_text_cursormap_ofs], rax
	call	list$new
	pop	rdi
	mov	[rdi+tui_text_lines_ofs], rax
	mov	dword [rdi+tui_text_editable_ofs], 1
	mov	dword [rdi+tui_text_docursor_ofs], 1
	mov	rsi, [rdi+tui_text_initial_ofs]
	test	rsi, rsi
	jz	.noinitialtext
	call	tui_text$nvsettext
	epilog
calign
.noinitialtext:
	; so that we have a valid editor and pointers, we need to set an empty string
	mov	rsi, .emptystr
	call	tui_text$nvsettext
	epilog
cleartext .emptystr, ''

end if



if used tui_text$nvcheckspinner | defined include_everything
	; single arugment in rdi: the tui_text object we are setting up
	; NOTE: this is only ever called from our draw method, and its only purpose is to delay
	; the creation/fireup of our spinner, as there is no sense in having a spinner/timer/update running
	; if we don't have valid bounds and/or are not-yet visible
falign
tui_text$nvcheckspinner:
	prolog	tui_text$nvcheckspinner
	cmp	dword [rdi+tui_text_dospinner_ofs], 0
	je	.nothingtodo
	cmp	qword [rdi+tui_text_spinner_ofs], 0
	jne	.nothingtodo
	push	rdi
	mov	edi, [rdi+tui_text_focuscolors_ofs]
	mov	esi, 75					; spinner speed
	call	tui_spinner$new
	pop	rdi
	mov	rsi, rax
	mov	[rdi+tui_text_spinner_ofs], rax
	mov	rdx, [rdi]
	mov	ecx, [rdi+tui_text_cursor_ofs]		; x is the lower 32 bits
	mov	r8d, [rdi+tui_text_cursor_ofs+4]	; y is the upper
	mov	[rsi+tui_absolutex_ofs], ecx
	mov	[rsi+tui_absolutey_ofs], r8d
	mov	eax, 0
	mov	ecx, 1
	cmp	dword [rdi+tui_text_focussed_ofs], 0
	cmovne	eax, ecx
	mov	[rsi+tui_visible_ofs], eax
	call	qword [rdx+tui_vappendbastard]
	epilog
calign
.nothingtodo:
	epilog

end if

if used tui_text$setcursor | defined include_everything
	; three arguments: rdi == our tui_text object, esi == x, edx == y
	; NOTE: if we have no spinner, this just calls tui_object$setcursor
	; otherwise, we set our bastard's absolutex/y to our cursor, only if they are different to our cursor
falign
tui_text$setcursor:
	prolog	tui_text$setcursor
	cmp	qword [rdi+tui_text_spinner_ofs], 0
	je	.parentonly
	mov	eax, [rdi+tui_text_cursor_ofs]
	mov	ecx, [rdi+tui_text_cursor_ofs+4]
	mov	r8, [rdi+tui_text_spinner_ofs]
	cmp	eax, [r8+tui_absolutex_ofs]
	jne	.spinupdate
	cmp	ecx, [r8+tui_absolutey_ofs]
	je	.parentonly
calign
.spinupdate:
	push	rdi rsi rdx
	mov	[r8+tui_absolutex_ofs], eax
	mov	[r8+tui_absolutey_ofs], ecx
	; we need to call the spinner's calcbounds
	mov	rdi, r8
	mov	rcx, [r8]
	call	qword [rcx+tui_vcalcbounds]
	pop	rdx rsi rdi
calign
.parentonly:
	call	tui_object$setcursor
	epilog

end if

if used tui_text$cleanup | defined include_everything
	; single argument in rdi: our tui_text object
falign
tui_text$cleanup:
	prolog	tui_text$cleanup
	push	rbx
	mov	rbx, rdi
	mov	rdi, [rdi+tui_text_lines_ofs]
	mov	rsi, .linedestroy
	call	list$clear
	mov	rdi, [rbx+tui_text_lines_ofs]
	call	heap$free
	mov	rdi, [rbx+tui_text_viewlines_ofs]
	mov	rsi, buffer$destroy
	call	list$clear
	mov	rdi, [rbx+tui_text_viewlines_ofs]
	call	heap$free
	mov	rdi, [rbx+tui_text_cursormap_ofs]
	mov	rsi, buffer$destroy
	call	list$clear
	mov	rdi, [rbx+tui_text_cursormap_ofs]
	call	heap$free
	mov	rdi, rbx
	call	tui_object$cleanup
	pop	rbx
	epilog
falign
.linedestroy:
	; called with rdi == our editor line entry, which is a buffer, but since we have two lists attached to it
	; we need to clear them as well
	push	rbx
	mov	rbx, rdi
	mov	rdi, [rdi+buffer_user_ofs+8]
	xor	esi, esi
	call	list$clear
	mov	rdi, [rbx+buffer_user_ofs+8]
	call	heap$free
	mov	rdi, [rbx+buffer_user_ofs]
	xor	esi, esi
	call	list$clear
	mov	rdi, [rbx+buffer_user_ofs]
	call	heap$free
	mov	rdi, rbx
	call	buffer$destroy
	pop	rbx
	ret


end if

if used tui_text$clone | defined include_everything
	; single argument in rdi: the tui_text object to clone
falign
tui_text$clone:
	prolog	tui_text$clone
	push	rbx r12
	mov	r12, rdi
	mov	edi, tui_text_size
	call	heap$alloc_clear
	mov	rcx, [r12]
	mov	rbx, rax
	mov	[rax], rcx
	mov	rdi, rax
	mov	rsi, r12
	call	tui_object$init_copy
	mov	rdi, [r12+tui_text_initial_ofs]
	call	string$copy
	mov	[rbx+tui_text_initial_ofs], rax
	mov	edi, [r12+tui_text_multiline_ofs]
	mov	esi, [r12+tui_text_pwdchar_ofs]
	mov	edx, [r12+tui_text_maxlen_ofs]
	mov	ecx, [r12+tui_text_align_ofs]
	mov	r8d, [r12+tui_text_wrap_ofs]
	mov	r9d, [r12+tui_text_heightlock_ofs]
	mov	r10d, [r12+tui_text_editable_ofs]
	mov	r11d, [r12+tui_text_docursor_ofs]
	mov	[rbx+tui_text_multiline_ofs], edi
	mov	[rbx+tui_text_pwdchar_ofs], esi
	mov	[rbx+tui_text_maxlen_ofs], edx
	mov	[rbx+tui_text_align_ofs], ecx
	mov	[rbx+tui_text_wrap_ofs], r8d
	mov	[rbx+tui_text_heightlock_ofs], r9d
	mov	[rbx+tui_text_editable_ofs], r10d
	mov	[rbx+tui_text_docursor_ofs], r11d
	mov	edi, [r12+tui_text_focussed_ofs]
	mov	esi, [r12+tui_text_colors_ofs]
	mov	edx, [r12+tui_text_focuscolors_ofs]
	mov	ecx, [r12+tui_text_dospinner_ofs]
	mov	r8d, [r12+tui_text_minlen_ofs]
	mov	[rbx+tui_text_focussed_ofs], edi
	mov	[rbx+tui_text_colors_ofs], esi
	mov	[rbx+tui_text_focuscolors_ofs], edx
	mov	[rbx+tui_text_dospinner_ofs], ecx
	mov	[rbx+tui_text_minlen_ofs], r8d
	
	call	list$new
	mov	[rbx+tui_text_viewlines_ofs], rax
	call	list$new
	mov	[rbx+tui_text_cursormap_ofs], rax
	call	list$new
	mov	[rbx+tui_text_lines_ofs], rax
	
	mov	rdi, rbx
	mov	rsi, [rbx+tui_text_initial_ofs]
	call	tui_text$nvsettext

	mov	rax, rbx
	pop	r12 rbx
	epilog
	
end if

if used tui_text$nvdocursor | defined include_everything
	; single argument in rdi: the tui_text object
	; this is called from gotfocus/lostfocus to update our actual cursor position
falign
tui_text$nvdocursor:
	prolog	tui_text$nvdocursor
	cmp	dword [rdi+tui_text_focussed_ofs], 0
	je	.nothingtodo
	cmp	dword [rdi+tui_text_docursor_ofs], 0
	je	.nothingtodo

	mov	r11d, [rdi+tui_text_cursor_ofs]
	mov	r8d, [rdi+tui_text_cursor_ofs+4]
	
	; if we are _scrolled_, bounds.a will be way out of whack

	mov	esi, [rdi+tui_bounds_ax_ofs]
	add	esi, r11d
	mov	edx, [rdi+tui_bounds_ay_ofs]
	add	edx, r8d
	mov	rcx, [rdi]
	call	qword [rcx+tui_vsetcursor]
	epilog
calign
.nothingtodo:
	epilog

end if

if used tui_text$draw | defined include_everything
	; single argument in rdi: our tui_text object
falign
tui_text$draw:
	prolog	tui_text$draw
	mov	eax, [rdi+tui_text_prevwidth_ofs]
	cmp	dword [rdi+tui_width_ofs], 0
	je	.nothingtodo
	cmp	dword [rdi+tui_height_ofs], 0
	je	.nothingtodo
	push	rbx
	mov	rbx, rdi

	; make sure our cursor is where it belongs:
	; NOTE: while this does cause a lot of extra calls to setcursor, tui_render doesn't duplicate them
	; over the wire...
	call	tui_text$nvdocursor
	mov	rdi, rbx

	; update: blasted this somewhere along the lines
	mov	eax, [rbx+tui_text_prevwidth_ofs]

	; if our current width != our prevwidth, we need to compose ourselves
	cmp	eax, [rdi+tui_width_ofs]
	je	.skipcompose
	mov	ecx, [rdi+tui_width_ofs]
	mov	[rdi+tui_text_prevwidth_ofs], ecx
	call	tui_text$nvcompose
	; nvcompose sets/adjusts our topline/bottomline/cursorline
	mov	eax, [rbx+tui_height_ofs]
	mov	[rbx+tui_text_prevheight_ofs], eax
	cmp	dword [rbx+tui_text_heightlock_ofs], 0
	je	.skipheightchange
	mov	rdi, rbx
	call	tui_text$nvheightchange
	jmp	.skipheightchange
calign
.skipcompose:
	; if our height != our prevheight, we need to adjust topline/bottomline
	mov	eax, [rbx+tui_height_ofs]
	cmp	eax, [rbx+tui_text_prevheight_ofs]
	je	.skipheightchange
	mov	[rbx+tui_text_prevheight_ofs], eax
	mov	rdi, rbx
	call	tui_text$nvheightchange
calign
.skipheightchange:
	; check our spinner, prefill our attr/text
	mov	rdi, rbx
	call	tui_text$nvcheckspinner
	mov	eax, [rbx+tui_width_ofs]
	mov	rdi, [rbx+tui_attr_ofs]
	mul	dword [rbx+tui_height_ofs]
	mov	esi, [rbx+tui_text_colors_ofs]
	mov	ecx, [rbx+tui_text_focuscolors_ofs]
	shl	eax, 2
	cmp	dword [rbx+tui_text_focussed_ofs], 0
	cmovne	esi, ecx
	mov	edx, eax
	push	rax
	call	memset32
	mov	rdi, [rbx+tui_text_ofs]
	mov	esi, ' '
	pop	rdx
	call	memset32

	; so, our viewlines must contain valid goods, so from our topline to our bottom line, copy
	; each composed viewline into our text

	; now, from our topline to our bottomline inclusive, dump each composed
	; line straight into our tui_text_ofs buffer
	mov	rdi, [rbx+tui_text_topline_ofs]
	push	r12 r13
	mov	r12, [rbx+tui_text_ofs]
	mov	r13, rdi			; topline/bottomline point to the list item, not value
calign
.lineloop:
	; so now, our viewline buffer is at [r13]
	; copy tui_width*4 worth of bytes from this value into r12, update r12, keep going
	mov	rdi, r12
	mov	rsi, [r13]
	mov	ecx, [rbx+tui_text_xscroll_ofs]	; xscroll in bytes
	mov	edx, [rbx+tui_width_ofs]
	mov	r8d, [rsi+buffer_length_ofs]
	mov	rsi, [rsi+buffer_itself_ofs]
	shl	edx, 2
	add	r12, rdx
	add	rsi, rcx
	sub	r8d, ecx
	cmp	edx, r8d
	cmova	edx, r8d
	call	memcpy
	cmp	r13, [rbx+tui_text_bottomline_ofs]
	je	.lineloop_done
	mov	r13, [r13+_list_nextofs]
	test	r13, r13
	jnz	.lineloop
calign
.lineloop_done:
	mov	rdi, rbx
	mov	rsi, [rdi]
	call	qword [rsi+tui_vupdatedisplaylist]
	pop	r13 r12 rbx
	epilog
calign
.nothingtodo:
	epilog

end if

if used tui_text$gotfocus | defined include_everything
	; single argument in rdi: our tui_text object
falign
tui_text$gotfocus:
	prolog	tui_text$gotfocus
	mov	dword [rdi+tui_text_focussed_ofs], 1
	push	rdi
	cmp	dword [rdi+tui_text_docursor_ofs], 0
	je	.skipshowcursor
	mov	rsi, [rdi]
	call	qword [rsi+tui_vshowcursor]
	mov	rdi, [rsp]
calign
.skipshowcursor:
	cmp	qword [rdi+tui_text_spinner_ofs], 0
	jne	.withspinner
	call	tui_text$nvdocursor
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	epilog
calign
.withspinner:
	call	tui_text$nvdocursor
	pop	rdi
	mov	rcx, [rdi+tui_text_spinner_ofs]
	mov	dword [rcx+tui_visible_ofs], 1
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	epilog

end if

if used tui_text$lostfocus | defined include_everything
	; single argument in rdi: our tui_text object
falign
tui_text$lostfocus:
	prolog	tui_text$lostfocus
	mov	dword [rdi+tui_text_focussed_ofs], 0
	push	rdi
	cmp	dword [rdi+tui_text_docursor_ofs], 0
	je	.skiphidecursor
	mov	rsi, [rdi]
	call	qword [rsi+tui_vhidecursor]
	mov	rdi, [rsp]
calign
.skiphidecursor:
	cmp	qword [rdi+tui_text_spinner_ofs], 0
	jne	.withspinner
	call	tui_text$nvdocursor
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	epilog
calign
.withspinner:
	call	tui_text$nvdocursor
	pop	rdi
	mov	rcx, [rdi+tui_text_spinner_ofs]
	mov	dword [rcx+tui_visible_ofs], 0
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	epilog

end if

if used tui_text$nvfindcursor | defined include_everything
	; three arguments: rdi == our tui_text object, rsi == editorline we are looking for, edx == new offset into the current editorline
falign
tui_text$nvfindcursor:
	prolog	tui_text$nvfindcursor
	; burning purpose: because a single keyevent can completely redo our view lines
	; we need to scan from the topline to the bottomline in our view until we find our editorline
	; this gives us our initial cursory position
	; then we can iterate that editorline's cursormap list, to find the correct cursory position
	; as well as the correct cursor x position
	;
	; very unpleasant logic, but is required nonetheless
	;
	xor	r8d, r8d			; our y counter
	mov	rcx, [rdi+tui_text_topline_ofs]
	;
	; so, it is possible that the topmost visible line is a partial view of the editor line it represents
	; meaning, more than one viewline exists, and the topline != its first line
	; in which case, we need to know precisely what the real offset is, or our cursor pos will be jacked
	cmp	qword [rcx+_list_prevofs], 0
	je	.firstsearch			; no prev == we are at the topline
	mov	r9, [rcx]			; the buffer at this list item
	mov	r10, [r9+buffer_user_ofs]	; the editorline it refers to
calign
.negoffset:
	mov	r11, [rcx+_list_prevofs]
	mov	rax, [r11]			; the buffer at the previous line
	cmp	r10, [rax+buffer_user_ofs]
	jne	.firstsearch
	; otherwise, it is equal
	sub	r8d, 1
	mov	rcx, r11
	cmp	qword [r11+_list_prevofs], 0
	jne	.negoffset

calign
.firstsearch:
	mov	r9, [rcx]			; the buffer at this list item
	cmp	rsi, qword [r9+buffer_user_ofs]
	je	.firstfound
	add	r8d, 1
	mov	rcx, [rcx+_list_nextofs]
	jmp	.firstsearch
calign
.firstfound:
	; if this editorline has no prev, _and_ minlen is nonzero, we have to validate the offset we are looking for
	cmp	dword [rdi+tui_text_minlen_ofs], 0
	je	.firstfound_nomincheck
	mov	rcx, [rsi+buffer_user_ofs+16]	; the editorline's list item
	cmp	qword [rcx+_list_prevofs], 0
	jne	.firstfound_nomincheck
	mov	r9d, [rdi+tui_text_minlen_ofs]
	shl	r9d, 2
	cmp	r9d, edx
	cmova	edx, r9d
calign
.firstfound_nomincheck:
	; so now, we found the first visible line that contains the right editor line
	; search that editor line's cursormap list until we find the offset we are looking for
	; we safely assume that the offset we are looking for _definitely_ exists somewhere in the list
	; the cursormap's buffer length is correct for whatever mode we are in

	; r9 is sitting on the viewline buffer that contains the right editorline
	; even though we already have the editor line sitting in rsi, the first search
	; was needed to determine its real/visible offset into our viewing window
	; so now that we have the offset of our editorline sitting in r8d, now we can traverse
	; its cursormap list, looking for our new offset
	mov	rcx, [rsi+buffer_user_ofs+8]	; its cursormap list, which contains list item pointers from the real cursormap list
	mov	rcx, [rcx+_list_first_ofs]
calign
.secondsearch:
	mov	r9, [rcx]			; the list item
	mov	r9, [r9]			; the cursormap buffer object itself
	mov	eax, [r9+buffer_length_ofs]
	mov	r10, [r9+buffer_itself_ofs]
	xor	r11d, r11d			; our x offset counter
calign
.linesearch:
	cmp	edx, dword [r10]
	je	.foundit
	add	r10, 4
	add	r11d, 1
	sub	eax, 4
	jnz	.linesearch
	; if we made it to here, we didn't find our offset on that line
	add	r8d, 1
	mov	rcx, [rcx+_list_nextofs]
	jmp	.secondsearch
calign
.foundit:
	mov	r9, [rcx]			; the list item
	mov	[rdi+tui_text_cursorline_ofs], r9
	mov	eax, r11d
	shl	eax, 2
	mov	[rdi+tui_text_cursorx_ofs], eax
	; so our "effective x/y" is sitting in r11d/r8d, set the cursor accordingly, adjusting xscroll as necessary
	cmp	r11d, [rdi+tui_width_ofs]
	jae	.xscroll
	mov	dword [rdi+tui_text_xscroll_ofs], 0
	mov	dword [rdi+tui_text_cursor_ofs], r11d
	mov	dword [rdi+tui_text_cursor_ofs+4], r8d

	cmp	dword [rdi+tui_text_focussed_ofs], 0
	je	.nocursor
	cmp	dword [rdi+tui_text_docursor_ofs], 0
	je	.nocursor
	
	mov	esi, [rdi+tui_bounds_ax_ofs]
	add	esi, r11d
	mov	edx, [rdi+tui_bounds_ay_ofs]
	add	edx, r8d
	mov	rcx, [rdi]
	call	qword [rcx+tui_vsetcursor]
	epilog
calign
.nocursor:
	epilog
calign
.xscroll:
	; we are in nowrap mode, and we ran past the end of our line
	; so, our cursor can't hang off the end of our visible area
	; so at the very least, xscroll is 1 now
	; + the difference between r11d and our width
	mov	r10d, r11d
	mov	r9d, [rdi+tui_width_ofs]
	sub	r10d, [rdi+tui_width_ofs]
	add	r10d, 1
	sub	r9d, 1
	shl	r10d, 2
	mov	[rdi+tui_text_xscroll_ofs], r10d
	mov	dword [rdi+tui_text_cursor_ofs], r9d
	mov	dword [rdi+tui_text_cursor_ofs+4], r8d

	cmp	dword [rdi+tui_text_focussed_ofs], 0
	je	.nocursor
	cmp	dword [rdi+tui_text_docursor_ofs], 0
	je	.nocursor
	
	mov	esi, [rdi+tui_bounds_ax_ofs]
	add	esi, r9d
	mov	edx, [rdi+tui_bounds_ay_ofs]
	add	edx, r8d
	mov	rcx, [rdi]
	call	qword [rcx+tui_vsetcursor]
	epilog

end if


if used tui_text$nvexpandby | defined include_everything
	; two arguments: rdi == our tui_text object, rsi == editor buffer to _exclude_, edx == count of chars to expand
falign
tui_text$nvexpandby:
	prolog	tui_text$nvexpandby
	; this is only called for !wrap, where we grew our buffer length, said growth resulted
	; in the overall size (maxx) being increased, and since we try NOT to do full recomposing
	; after every keystroke, this is required so that all the other viewlines and cursormaps
	; can be expanded without a full recompose
	push	rbx r12 r13 r14 r15
	mov	rbx, rdi
	mov	r12d, edx
	mov	rdx, [rdi+tui_text_viewlines_ofs]
	mov	rcx, [rdi+tui_text_cursormap_ofs]
	mov	r15, rsi
	mov	r13, [rdx+_list_first_ofs]
	mov	r14, [rcx+_list_first_ofs]
calign
.top:
	push	r13 r14
calign
.doit:
	; the value at [r13] and [r14] is each buffer 
	mov	rdi, [r13]
	cmp	r15, [rdi+buffer_user_ofs]		; its link back to the editorbuffer
	je	.excluded
	cmp	dword [rbx+tui_text_align_ofs], tui_textalign_right
	je	.rightaligned
	mov	esi, ' '
	call	buffer$append_dword
	mov	rdi, [r14]
	mov	esi, 0xffffffff
	call	buffer$append_dword
	mov	r13, [r13+_list_nextofs]
	mov	r14, [r14+_list_nextofs]
	test	r13, r13
	jnz	.doit
	pop	r14 r13
	sub	r12d, 1
	jnz	.top
	pop	r15 r14 r13 r12
	epilog
calign
.rightaligned:
	sub	rsp, 8
	mov	qword [rsp], ' '
	xor	esi, esi
	mov	rdx, rsp
	mov	ecx, 4
	call	buffer$insert
	mov	dword [rsp], 0
	mov	rdi, [r14]
	xor	esi, esi
	mov	rdx, rsp
	mov	ecx, 4
	call	buffer$insert
	add	rsp, 8
	mov	r13, [r13+_list_nextofs]
	mov	r14, [r14+_list_nextofs]
	test	r13, r13
	jnz	.doit
	pop	r14 r13
	sub	r12d, 1
	jnz	.top
	pop	r15 r14 r13 r12
	epilog
calign
.excluded:
	mov	r13, [r13+_list_nextofs]
	mov	r14, [r14+_list_nextofs]
	test	r13, r13
	jnz	.doit

	pop	r14 r13
	sub	r12d, 1
	jnz	.top
	pop	r15 r14 r13 r12 rbx
	epilog

end if

if used tui_text$nvdeleteline | defined include_everything
	; two arguments: rdi == our tui_text object, rsi == the editor line buffer that we are deleting
	; this takes "special handling" for in-place deletion without fully recomposing our entire linelist
falign
tui_text$nvdeleteline:
	prolog	tui_text$nvdeleteline

	; this is a wee-bit messy, we have to destroy each viewline and cursormap entry that belongs to us
	push	rbx r12 r13
	mov	rbx, rdi
	mov	r12, rsi
	mov	r13, [rsi+buffer_user_ofs]		; the viewline list for this editorline
	mov	r13, [r13+_list_first_ofs]
	; sanity only, TODO: remove me for production
	test	r13, r13
	jz	.nolines
calign
.viewloop:
	; so our editor buffer's viewline list is a list of items from the real viewline list
	; free the buffer, then list$remove the item from the tui_text_viewlines_ofs list
	mov	rdi, [r13]				; the item from the editorbuffer list
	mov	rdi, [rdi]				; the buffer
	call	buffer$destroy
	mov	rdi, [rbx+tui_text_viewlines_ofs]
	mov	rsi, [r13]
	call	list$remove
	mov	r13, [r13+_list_nextofs]
	test	r13, r13
	jnz	.viewloop
	mov	r13, [r12+buffer_user_ofs+8]		; the cursormap list for this editorline
	mov	r13, [r13+_list_first_ofs]
	; sanity only, TODO: remove me for production
	test	r13, r13
	jz	.nolines
calign
.cursorloop:
	mov	rdi, [r13]				; the item from the editorbuffer list
	mov	rdi, [rdi]				; the buffer
	call	buffer$destroy
	mov	rdi, [rbx+tui_text_cursormap_ofs]
	mov	rsi, [r13]
	call	list$remove
	mov	r13, [r13+_list_nextofs]
	test	r13, r13
	jnz	.cursorloop
calign
.nolines:
	; now we have to empty and free both our editorbuffer lists
	mov	rdi, [r12+buffer_user_ofs]
	xor	esi, esi
	call	list$clear
	mov	rdi, [r12+buffer_user_ofs]
	call	heap$free
	mov	rdi, [r12+buffer_user_ofs+8]
	xor	esi, esi
	call	list$clear
	mov	rdi, [r12+buffer_user_ofs+8]
	call	heap$free

	; last but not least, we need to remove ourselves from the real tui_text_lines_ofs list
	mov	rdi, [rbx+tui_text_lines_ofs]
	mov	rsi, [r12+buffer_user_ofs+16]
	call	list$remove
	; now we can off our buffer and be done with it
	mov	rdi, r12
	call	buffer$destroy
	pop	r13 r12 rbx
	epilog

end if



if used tui_text$keyevent | defined include_everything
	; three arguments: rdi == our tui_text object, esi == key, edx == esc_key
falign
tui_text$keyevent:
	prolog	tui_text$keyevent
	cmp	dword [rdi+tui_text_focussed_ofs], 0
	je	.falseret
	cmp	dword [rdi+tui_text_editable_ofs], 0
	je	.scrollcheck
	; otherwise, we are editable
	cmp	edx, 0x41			; up arrow
	je	.uparrow
	cmp	edx, 0x42			; down arrow
	je	.downarrow
	cmp	edx, 0x43			; right arrow
	je	.rightarrow
	cmp	edx, 0x46			; end, well, shift-end
	je	.shiftend
	cmp	edx, 0x44			; left arrow
	je	.leftarrow
	cmp	edx, 0x48			; home, well, shift-home
	je	.shifthome
	cmp	esi, 0x7f			; backspace
	je	.backspace
	cmp	edx, 0x33			; delete
	je	.delete
	cmp	esi, 9				; tab
	je	.tab
	cmp	edx, 0x5a			; shift-tab
	je	.shifttab
	cmp	esi, 13				; enter
	je	.cr
	cmp	esi, 32
	jb	.falseret
	; otherwise, pass the key to our editorline
	; so our cursorline is a list item pointer into the cursormap
	; and our cursorx is the offset in bytes into the cursormap itself
	; and the value at that cursormap position is the offset into the editor line
	; that we are ultimately inserting into

	mov	rdx, [rdi+tui_text_cursorline_ofs]	; item pointer into the cursormap list
	mov	rcx, [rdx]				; the actual cursormap buffer object
	mov	r8d, [rdi+tui_text_cursorx_ofs]
	mov	rax, [rcx+buffer_itself_ofs]
	mov	r9d, [rax+r8]

	cmp	dword [rdi+tui_text_maxlen_ofs], 0
	je	.skiplengthcheck
	mov	eax, [rdi+tui_text_maxlen_ofs]
	mov	r10, [rcx+buffer_user_ofs]
	shl	eax, 2
	mov	r11d, [r10+buffer_length_ofs]
	cmp	r11d, eax
	jae	.falseret
calign
.skiplengthcheck:
	; save rdi and r9 prior to our insert, because we'll need them to update the rest of our goods
	push	rdi r9
	; also save the editorline that we are referencing so we can recompose it
	push	qword [rcx+buffer_user_ofs]

	; first order of business is inserting the dword esi into the buffer at the specified offset
	push	rsi
	mov	rdi, [rcx+buffer_user_ofs]		; the editorline that the cursormap buffer object references
	mov	rsi, r9					; the offset that we are inserting at
	mov	rdx, rsp
	mov	ecx, 4
	call	buffer$insert
	add	rsp, 8

	; if we are doing wrap, it is possible that our actual height may change, in which case
	; we'll need to call nvheightchange so it can redo our view
	mov	rsi, [rsp]
	mov	rcx, [rsi+buffer_user_ofs]		; its viewline list
	mov	rdi, [rsp+16]
	push	qword [rcx+_list_size_ofs]

	; next order of business is recomposing the editorline
	call	tui_text$nvcomposeline

	pop	rax
	mov	rsi, [rsp]
	mov	rdi, [rsp+16]
	mov	rcx, [rsi+buffer_user_ofs]		; its viewline list
	cmp	rax, [rcx+_list_size_ofs]
	je	.noheightchange
	
	call	tui_text$nvheightchange

calign
.noheightchange:
	; if !wrap, _and_ our editorbuffer's length is the same length as its first viewline, then that means
	; that the line we are on was responsible for a single character growth in width, in which case
	; all other viewlines/cursormaps need expanded by 1 as well
	mov	rsi, [rsp]
	mov	rdi, [rsp+16]
	cmp	dword [rdi+tui_text_wrap_ofs], 0
	jne	.noexpand

	mov	rdx, [rsi+buffer_length_ofs]

	mov	rcx, [rsi+buffer_user_ofs]		; its viewline list
	mov	rcx, [rcx+_list_first_ofs]		; viewline list first item, which is itself an item
	mov	rcx, [rcx]				; the real item
	mov	rcx, [rcx]				; the buffer of hte first viewline

	cmp	rdx, [rcx+buffer_length_ofs]
	jne	.noexpand
	; otherwise, we are doing a full expansion of viewlines and cursormaps
	; since we know our buffer grew by precisely one, and we know that our viewlines and cursormaps are 1:1
	; thanks to wrap being zero
	; all we have to do is pass our editor buffer to the expansion, and they can all grow by one
	mov	edx, 1
	call	tui_text$nvexpandby
calign
.noexpand:
	; last order of business is our cursor positions (all of them)
	; and in this case, we need to search for our new editorline offset (which is the insert position+4)
	; in our cursormap, and if it isn't on the current cursormap line, walk forward to the next til we find it
	; updating our real cursor position as we go

	; no matter what, our cursorx position got increased by four bytes
	pop	rsi rdx
	mov	rdi, [rsp]				; the editorline we are looking for
	add	rdx, 4					; the new offset (of the editorline)
	; so rsi is the offset that we inserted into, which we now need to _refind_ in the cursormap (because it got fully blasted)
	call	tui_text$nvfindcursor

	; last but not least, we need to issue a draw
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog
	
calign
.uparrow:
	cmp	dword [rdi+tui_text_multiline_ofs], 0
	je	.falseret
	; see if cursorline has a prev, if not, falseret
	mov	rsi, [rdi+tui_text_cursorline_ofs]
	cmp	qword [rsi+_list_prevofs], 0
	je	.falseret
	;
	; some commentary on up/down arrow functionality... hahah
	;
	; this is not necessarily simple, because we let the cursor walk _through_ multiple viewlines
	; which may actually be the same editorline, or may not be
	; if the previous line is the same editorline object, then our mission is simple
	; if it is not the same editorline object, we have to move up one anyway, but validate the
	; cursorx position against the actual editorline object's valid offsets
	;
	; now, whether that makes SENSE or not, hahah, most unpleasant really
	; if we hit the up arrow from the middle of our line, and the line above us only has one
	; word, we need to snap the cursorx to the end of the buffer
	; but, if the position directly above us is _valid_, then it makes sense to actually cursor
	; directly to that position, such that cursorx snapping only happens when there IS no text
	; at the actual position, but deciding which way to snap will be tricky...
	;
	; realistically, it means that we are past the end of the text... for right-aligned, does
	; that mean we want to place the cursor at the end of the line? I think it does
	; so
	; that simplifies it a bit, whether that is the "right" thing to do or not from a human-interface
	; perspective, i dunno.. might have to come back and change that thinking a bit.
	; for left, center, and justified though, snapping to the end of the bufferline makes the most
	; sense, and ensures we have a valid cursor position.
	;
	; worst case for right-aligned, is we change it to snap to the "nearest" by walking cursorx right
	; instead of snapping to the end of the buffer line (which is "odd" movement for a cursor-up/dn)

	; we need to determine whether the cursorline is at the top, or bottom
	; and the only way to do that is to figure out which viewline corresponds to the current cursorline
	; fortunately, we won't have too many to iterate, since we can walk just the editor line's list
	; of viewlines and cursorlines together and stop when we find the cursorline

	mov	rdx, [rsi]				; the cursormap buffer itself
	mov	rcx, [rdx+buffer_user_ofs]		; the editor line buffer it is referencing
	; so now, we need to walk both lists til we find our cursormap
	mov	r8, [rcx+buffer_user_ofs]
	mov	r9, [rcx+buffer_user_ofs+8]
	; set those each to the first
	mov	r8, [r8+_list_first_ofs]
	mov	r9, [r9+_list_first_ofs]
calign
.uparrow_viewscan:
	; the value at this editorline's list is actually another item, so we need the value AT [r9]
	cmp	rsi, [r9]
	je	.uparrow_viewscan_done
	mov	r8, [r8+_list_nextofs]
	mov	r9, [r9+_list_nextofs]
	jmp	.uparrow_viewscan
calign
.uparrow_viewscan_done:
	mov	r8, [r8]
	mov	r9, [r9]
	; so now, based on what is in [r8], we should be able to tell if the cursorline is at the top and/or bottom
	; probably could have added other state variables to eliminate that loop, but since it is single-editor-line
	; traversal of that to viewlines only, the penalty really isn't that bad.
	mov	rsi, [rsi+_list_prevofs]
	mov	[rdi+tui_text_cursorline_ofs], rsi
	cmp	r8, [rdi+tui_text_topline_ofs]
	jne	.uparrow_notop
	; we were at the topline, so scroll up both topline and bottomline
	mov	r8, [r8+_list_prevofs]
	mov	[rdi+tui_text_topline_ofs], r8
	mov	r9, [rdi+tui_text_bottomline_ofs]
	mov	r9, [r9+_list_prevofs]
	mov	[rdi+tui_text_bottomline_ofs], r9
calign
.uparrow_notop:
	; so now, all we have left to do is validate cursorx
	; so first, pull the value out of the new cursormap buffer, to see if it is a -1
	mov	rdx, [rsi]				; the cursormap buffer itself (of the new cursor line
	mov	r8d, [rdi+tui_text_cursorx_ofs]
	mov	rax, [rdx+buffer_itself_ofs]
	mov	r9d, [rax+r8]
	; r9d now contains the value inside the cursormap, if it is a -1 then we need to findcursor for the
	; end of the buffer it references, else, we do a findcursor for the value we are sitting at
	; which will position our cursor, as well as our spinner if any for us
	cmp	r9d, 0xffffffff
	je	.uparrow_bufpos
	push	rdi
	mov	rsi, [rdx+buffer_user_ofs]		; the editorline
	mov	edx, r9d				; the offset we want
	call	tui_text$nvfindcursor
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog
calign
.uparrow_bufpos:
	push	rdi
	mov	rsi, [rdx+buffer_user_ofs]		; the editorline
	mov	rdx, [rsi+buffer_length_ofs]
	call	tui_text$nvfindcursor
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog

calign
.downarrow:
	cmp	dword [rdi+tui_text_multiline_ofs], 0
	je	.falseret
	; see if cursorline has a next, if not, falseret
	mov	rsi, [rdi+tui_text_cursorline_ofs]
	cmp	qword [rsi+_list_nextofs], 0
	je	.falseret

	; basically the same thing as above for uparrow
	mov	rdx, [rsi]				; the cursormap buffer object itself
	mov	rcx, [rdx+buffer_user_ofs]		; the editor line buffer it is referencing

	mov	r8, [rcx+buffer_user_ofs]		; the editor line's viewline list
	mov	r9, [rcx+buffer_user_ofs+8]		; the editor line's cursormap list
	; set those each to the first
	mov	r8, [r8+_list_first_ofs]
	mov	r9, [r9+_list_first_ofs]
calign
.downarrow_viewscan:
	; the value at this editorline's list is actually another item, so we need the value AT [r9]
	cmp	rsi, [r9]
	je	.downarrow_viewscan_done
	mov	r8, [r8+_list_nextofs]
	mov	r9, [r9+_list_nextofs]
	jmp	.downarrow_viewscan
calign
.downarrow_viewscan_done:
	mov	r8, [r8]
	mov	r9, [r9]

	; move the cursorline itself down one
	mov	rsi, [rsi+_list_nextofs]
	mov	[rdi+tui_text_cursorline_ofs], rsi
	cmp	r8, [rdi+tui_text_bottomline_ofs]
	jne	.downarrow_nobottom
	; we were at the bottomline, so scroll down both topline and bottomline
	mov	r8, [r8+_list_nextofs]
	mov	[rdi+tui_text_bottomline_ofs], r8
	mov	r9, [rdi+tui_text_topline_ofs]
	mov	r9, [r9+_list_nextofs]
	mov	[rdi+tui_text_topline_ofs], r9
calign
.downarrow_nobottom:
	; so now, all we have left to do is validate cursorx
	; so first, pull the value out of the new cursormap buffer, to see if it is a -1
	mov	rdx, [rsi]				; the cursormap buffer itself (of the new cursor line
	mov	r8d, [rdi+tui_text_cursorx_ofs]
	mov	rax, [rdx+buffer_itself_ofs]
	mov	r9d, [rax+r8]
	; r9d now contains the value inside the cursormap, if it is a -1 then we need to findcursor for the
	; end of the buffer it references, else, we do a findcursor for the value we are sitting at
	; which will position our cursor, as well as our spinner if any for us
	cmp	r9d, 0xffffffff
	je	.downarrow_bufpos
	push	rdi
	mov	rsi, [rdx+buffer_user_ofs]		; the editorline
	mov	edx, r9d				; the offset we want
	call	tui_text$nvfindcursor
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog
calign
.downarrow_bufpos:
	push	rdi
	mov	rsi, [rdx+buffer_user_ofs]		; the editorline
	mov	rdx, [rsi+buffer_length_ofs]		; the offset we want
	call	tui_text$nvfindcursor
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog
	

calign
.rightarrow:
	; this should take us right one character in our editorline, which may also cause scroll
	mov	rsi, [rdi+tui_text_cursorline_ofs]
	mov	rdx, [rsi]				; the cursormap buffer object itself
	mov	rcx, [rdx+buffer_user_ofs]		; the editor line buffer it is referencing
	mov	r8d, [rdi+tui_text_cursorx_ofs]
	mov	rax, [rdx+buffer_itself_ofs]
	mov	r9d, [rax+r8]
	mov	r10, [rcx+buffer_length_ofs]
	; if r9d is equal to the editor line's buffer length, falseret
	cmp	r9, r10
	je	.falseret
	; otherwise, we can increment it by 4 and call findcursor
	add	r9d, 4
	push	rdi
	mov	rsi, rcx
	mov	edx, r9d
	call	tui_text$nvfindcursor
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog

calign
.shiftend:
	; this should take us to the end of the real editorline, even if it spans multiple viewlines
	; which also means, findcursor may have to scroll in order to get it in view
	mov	rsi, [rdi+tui_text_cursorline_ofs]
	mov	rdx, [rsi]				; the cursormap buffer object itself
	mov	rcx, [rdx+buffer_user_ofs]		; the editor line buffer it is referencing
	mov	r8d, [rdi+tui_text_cursorx_ofs]
	mov	rax, [rdx+buffer_itself_ofs]
	mov	r9d, [rax+r8]
	mov	r10, [rcx+buffer_length_ofs]
	push	rdi
	mov	rsi, rcx
	mov	edx, r10d
	call	tui_text$nvfindcursor
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog
calign
.leftarrow:
	; this should take us left one character in our editorline, which may also cause scroll
	; regardless of whether cursorx is zero or not, we need to determine the editorline's offset
	; that it is pointing to, and check that _that_ is nonzero, then decrement it
	mov	rsi, [rdi+tui_text_cursorline_ofs]
	mov	rdx, [rsi]				; the cursormap buffer object itself
	mov	rcx, [rdx+buffer_user_ofs]		; the editor line buffer it is referencing
	mov	r8d, [rdi+tui_text_cursorx_ofs]
	mov	rax, [rdx+buffer_itself_ofs]
	mov	r9d, [rax+r8]
	; if r9d is zero here, falseret
	test	r9d, r9d
	jz	.falseret
	; otherwise, we can decrement it by 4, and call findcursor
	sub	r9d, 4
	push	rdi
	mov	rsi, rcx
	mov	edx, r9d
	call	tui_text$nvfindcursor
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog

calign
.shifthome:
	; this should take us to the beginning of the real editorline, even if it spans multiple viewlines
	; which also means, findcursor may have to scroll in order to get it in view
	mov	rsi, [rdi+tui_text_cursorline_ofs]
	mov	rdx, [rsi]				; the cursormap buffer object itself
	mov	rcx, [rdx+buffer_user_ofs]
	push	rdi
	mov	rsi, rcx
	xor	edx, edx
	call	tui_text$nvfindcursor
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog

calign
.backspace_startofline:
	; if there is no previous line to our current one, do _nothing_
	push	rbx r12 r13 r14
	mov	rbx, rdi
	mov	r12, [rcx+buffer_user_ofs]
	; we need r12's tui_text_lines_ofs ITEM entry to determine whether we have a prev or not
	mov	rsi, [r12+buffer_user_ofs+16]
	cmp	qword [rsi+_list_prevofs], 0
	je	.backspace_startofline_bailout
	; otherwise, there _is_ a previous line... 
	mov	rsi, [rsi+_list_prevofs]
	mov	r13, [rsi]				; the previous editorline's buffer
	; so, first thing to determine is whether our current line is empty or not
	cmp	qword [r12+buffer_length_ofs], 0
	je	.backspace_startofline_nomove

	; before we modify r13, we need to know what its first viewline length is
	mov	rcx, [r13+buffer_user_ofs]		; its viewline list
	mov	rcx, [rcx+_list_first_ofs]		; the viewline list first item, which is itself an item
	mov	rcx, [rcx]				; the real item
	mov	rcx, [rcx]				; the buffer of the first viewline
	mov	r14, [rcx+buffer_length_ofs]

	; we have to copy the contents of our entire buffer onto the end of our previous buffer
	; and if wrap is zero, we may also have to expand every other line
	mov	rdi, r13
	mov	rsi, [r12+buffer_itself_ofs]
	mov	rdx, [r12+buffer_length_ofs]
	call	buffer$append
	; now, we need to recompose our previous editorline
	mov	rdi, rbx
	mov	rsi, r13
	call	tui_text$nvcomposeline
	; if wrap is nonzero, we don't need to worry about expansion
	cmp	dword [rbx+tui_text_wrap_ofs], 0
	jne	.backspace_startofline_nomove

	; further, if the length of our previous buffer is not equal to its first viewline length
	; we don't need to worry about expansion
	mov	rdx, [r13+buffer_length_ofs]
	mov	rcx, [r13+buffer_user_ofs]		; its viewline list
	mov	rcx, [rcx+_list_first_ofs]		; viewline list first item, which is itself an item
	mov	rcx, [rcx]				; the real item
	mov	rcx, [rcx]				; the buffer of hte first viewline
	cmp	rdx, [rcx+buffer_length_ofs]
	jne	.backspace_startofline_nomove
	; otherwise, we need to expando-matic

	; the # of characters to expand by is the difference between what it _was_ and what it is now

	mov	rdi, rbx
	mov	rsi, r13				; our exclude
	sub	rdx, r14
	shr	rdx, 2
	call	tui_text$nvexpandby
calign
.backspace_startofline_nomove:
	; there is nothing in our current line's buffer, or we fellthrough from above
	; there are several factors to consider here to make sure that findcursor will
	; not die a thousand deaths, and that our cursorline/topline/bottomline themselves
	; don't get invalidated by our completely deleting this editorline and associated
	; viewline(s) and cursormap(s)
	
	; the simple case, where topline/bottomline/cursorline ALL do not reference the line we are on
	; in which case we can just delete the line and be done with it
	; so, loadup each of the three and check
	mov	rdi, [rbx+tui_text_topline_ofs]
	mov	rsi, [rbx+tui_text_bottomline_ofs]
	mov	rdx, [rbx+tui_text_cursorline_ofs]
	
	; each of those are list items, so we need to dereference them one more time
	mov	r8, [rdi]
	mov	r9, [rsi]
	mov	r10, [rdx]
	
	; now, any those buffer_user_ofs is equal to r12, more difficult case
	cmp	r12, [r8+buffer_user_ofs]
	je	.backspace_startofline_topline
	cmp	r12, [r9+buffer_user_ofs]
	je	.backspace_startofline_bottomline
	cmp	r12, [r10+buffer_user_ofs]
	je	.backspace_startofline_cursorline
	; so this is the simple case fallthrough, none of them reference our editorline
calign
.backspace_startofline_dodelete:
	mov	rdi, rbx
	mov	rsi, r12
	call	tui_text$nvdeleteline
	mov	rdi, rbx
	call	tui_text$nvheightchange
	; last but not least, find our cursor
	mov	rdi, rbx
	mov	rsi, r13
	mov	rdx, [r13+buffer_length_ofs]
	call	tui_text$nvfindcursor
	; call our draw method
	mov	rdi, rbx
	mov	rsi, [rbx]
	call	qword [rsi+tui_vdraw]
	pop	r14 r13 r12 rbx
	mov	eax, 1
	epilog
calign
.backspace_startofline_topline:
	; so, we need to set topline to its previous item (cuz we know we have a previous one)
	mov	rdi, [rdi+_list_prevofs]
	mov	[rbx+tui_text_topline_ofs], rdi
	cmp	r12, [r9+buffer_user_ofs]
	je	.backspace_startofline_bottomline
	cmp	r12, [r10+buffer_user_ofs]
	je	.backspace_startofline_cursorline
	jmp	.backspace_startofline_dodelete
calign
.backspace_startofline_bottomline:
	; we need to set bottomline to its previous item
	mov	rsi, [rsi+_list_prevofs]
	mov	[rbx+tui_text_bottomline_ofs], rsi
	; see if we are the cursorline
	cmp	r12, [r10+buffer_user_ofs]
	jne	.backspace_startofline_dodelete
calign
.backspace_startofline_cursorline:
	; move the cursorline to its prev
	mov	rdx, [rdx+_list_prevofs]
	mov	[rbx+tui_text_cursorline_ofs], rdx
	jmp	.backspace_startofline_dodelete
calign
.backspace_startofline_bailout:
	xor	eax, eax
	pop	r14 r13 r12 rbx
	epilog
calign
.backspace:
	; there are quite a few scenarios here that need dealing with
	; if we are at 0 offset in our buffer, and there is no previous line, do nothing
	
	; if we are at 0 offset in our buffer, and there is a previous line:
	;    new cursor position is the previous line's length before we modify it
	;    if we have anything in our buffer, concatenate it all to the previous line, and delete our line
	;       noting that our concatenation might result in a global expansion by more than 1 character if !wrap
	;    if we don't have anything in our buffer, just delete our line
	; if we are not at 0 offset in our buffer, remove the character _before_ our offset, backup one spot
	;    and recompose

	mov	rdx, [rdi+tui_text_cursorline_ofs]	; item pointer into the cursormap list
	mov	rcx, [rdx]				; the actual cursormap buffer object
	mov	r8d, [rdi+tui_text_cursorx_ofs]
	mov	rax, [rcx+buffer_itself_ofs]
	mov	r9d, [rax+r8]

	; the editorline we are interested in is at [rcx+buffer_user_ofs]
	test	r9d, r9d
	jz	.backspace_startofline

	; in case minlen is being used, we also need to determine whether they are at the minlen or not
	mov	r10d, [rdi+tui_text_minlen_ofs]
	shl	r10d, 2					; in bytes
	test	r10d, r10d
	jz	.backspace_nomincheck
	; in addition, our cursorline must have no prev
	cmp	qword [rdx+_list_prevofs], 0
	jne	.backspace_nomincheck
	cmp	r9d, r10d
	jbe	.falseret
calign
.backspace_nomincheck:

	; otherwise, we are not at the leftmost of our editorbuffer, simple case
	; where we just have to remove the character _before_ ours, and back up our cursorofs
	; recompose our line and be done

	; save rdi and r9 prior to our remove, because we'll need them to update the rest of our goods
	push	rdi r9
	; also save the editorline that we are referencing so we can recompose it
	push	qword [rcx+buffer_user_ofs]

	; first order of business is inserting the dword esi into the buffer at the specified offset
	mov	rdi, [rcx+buffer_user_ofs]		; the editorline that the cursormap buffer object references
	mov	rsi, r9					; the offset that we are removing from
	mov	edx, 4
	sub	rsi, 4
	call	buffer$remove

	mov	rsi, [rsp]
	mov	rcx, [rsi+buffer_user_ofs]		; its viewline list
	mov	rdi, [rsp+16]
	push	qword [rcx+_list_size_ofs]

	; next order of business is recomposing the editorline
	call	tui_text$nvcomposeline

	pop	rax

	mov	rsi, [rsp]
	mov	rdi, [rsp+16]
	mov	rcx, [rsi+buffer_user_ofs]		; its viewline list
	cmp	rax, [rcx+_list_size_ofs]
	je	.bs_noheightchange

	; [rsp] == editorline
	; [rsp+8] == r9
	; [rsp+16] == original rdi
	; because nvheightchange _also_ calls nvfindcursor, we need to modify it beforehand
	mov	rdx, [rsp+8]
	sub	rdx, 4
	call	tui_text$nvfindcursor

	mov	rsi, [rsp]
	mov	rdi, [rsp+16]
	
	call	tui_text$nvheightchange

calign
.bs_noheightchange:

	mov	rsi, [rsp]
	mov	rdi, [rsp+16]

	mov	rcx, [rsi+buffer_user_ofs]		; its viewline list
	mov	rcx, [rcx+_list_first_ofs]		; viewline list first item, which is itself an item
	mov	rcx, [rcx]				; the real item
	mov	rcx, [rcx]				; the buffer of hte first viewline

	; last order of business is our cursor positions (all of them)
	; and in this case, we need to search for our new editorline offset (which is the insert position+4)
	; in our cursormap, and if it isn't on the current cursormap line, walk forward to the next til we find it
	; updating our real cursor position as we go

	; no matter what, our cursorx position got decreased by four bytes
	pop	rsi rdx
	mov	rdi, [rsp]				; the editorline we are looking for
	sub	rdx, 4					; the new offset (of the editorline)
	; so rsi is the offset that we inserted into, which we now need to _refind_ in the cursormap (because it got fully blasted)
	call	tui_text$nvfindcursor

	; last but not least, we need to issue a draw
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog

calign
.delete_endofline:
	; if there is no next line to our current one, do _nothing_
	push	rbx r12 r13 r14
	mov	rbx, rdi
	mov	r12, rax
	mov	rsi, [r12+buffer_user_ofs+16]
	cmp	qword [rsi+_list_nextofs], 0
	je	.delete_endofline_bailout
	; otherwise, there is a next line
	mov	rsi, [rsi+_list_nextofs]
	mov	r13, [rsi]				; the next editorline's buffer
	mov	r14d, r9d				; save the current buffer length
	; deal with the simple case first, where the nextline is empty
	; and the reason we care, is because deleting the line below us doesn't change our linelength, cursor position, or anything
	cmp	qword [r13+buffer_length_ofs], 0
	je	.delete_endofline_nextline_empty
	; otherwise, we have to concat the next line to our current line, save our offset so we can refind our cursor after we recompose

	mov	rdi, r12
	mov	rsi, [r13+buffer_itself_ofs]
	mov	rdx, [r13+buffer_length_ofs]
	call	buffer$append

	; we can now recompose our current line
	mov	rdi, rbx
	mov	rsi, r12
	call	tui_text$nvcomposeline

	; further, if the length of our previous buffer is not equal to its first viewline length
	; we don't need to worry about expansion
	mov	rdx, [r12+buffer_length_ofs]
	mov	rcx, [r12+buffer_user_ofs]		; its viewline list
	mov	rcx, [rcx+_list_first_ofs]		; viewline list first item, which is itself an item
	mov	rcx, [rcx]				; the real item
	mov	rcx, [rcx]				; the buffer of hte first viewline
	cmp	rdx, [rcx+buffer_length_ofs]
	jne	.delete_endofline_nomove
	; otherwise, we need to expando-matic

	; the # of characters to expand by is the difference between what it _was_ and what it is now

	mov	rdi, rbx
	mov	rsi, r12				; our exclude
	sub	rdx, r14
	shr	rdx, 2
	call	tui_text$nvexpandby
calign
.delete_endofline_nomove:

	; delete the next line
	mov	rdi, rbx
	mov	rsi, r13
	call	tui_text$nvdeleteline

	mov	rdi, rbx
	call	tui_text$nvheightchange
	
	mov	rdi, rbx
	mov	rsi, r12
	mov	edx, r14d
	call	tui_text$nvfindcursor
	
	mov	rdi, rbx
	mov	rsi, [rbx]
	call	qword [rsi+tui_vdraw]
	
	pop	r14 r13 r12 rbx
	mov	eax, 1
	epilog

calign
.delete_endofline_nextline_empty:
	; simpler case:
	mov	rdi, rbx
	mov	rsi, r13
	call	tui_text$nvdeleteline
	
	mov	rdi, rbx
	call	tui_text$nvheightchange

	mov	rdi, rbx
	mov	rsi, r12
	mov	edx, r14d
	call	tui_text$nvfindcursor
	
	mov	rdi, rbx
	mov	rsi, [rbx]
	call	qword [rsi+tui_vdraw]

	pop	r14 r13 r12 rbx
	mov	eax, 1
	epilog

calign
.delete_endofline_bailout:
	pop	r14 r13 r12 rbx
	xor	eax, eax
	epilog
calign
.delete:
	; similar to backspace, there are several scenarios here that need dealing with
	; if we are at the end of our buffer, and there is no next line, do nothing

	; if we are at the end of our buffer, and there is a next line:
	;   cursor position doesn't change
	;   we concatenate the next line to our current line, and delete the next line
	;
	; if we are not at the end of our buffer, remove the character _at_ our offset and recompose

	mov	rdx, [rdi+tui_text_cursorline_ofs]	; item pointer into the cursormap list
	mov	rcx, [rdx]				; the actual cursormap buffer object
	mov	r8d, [rdi+tui_text_cursorx_ofs]
	mov	rax, [rcx+buffer_itself_ofs]
	mov	r9d, [rax+r8]				; the editorline offset
	
	; the editorline we are sitting on is at [rcx+buffer_user_ofs]
	mov	rax, [rcx+buffer_user_ofs]
	cmp	r9, [rax+buffer_length_ofs]
	je	.delete_endofline

	; otherwise, we are not at the end of our buffer, simple case where we just have to
	; remove the character at the r9 position, and leave everything else alone
	push	rdi r9 rax
	mov	rdi, rax
	mov	rsi, r9
	mov	edx, 4
	call	buffer$remove

	; recompose the editorline
	mov	rsi, [rsp]
	mov	rdi, [rsp+16]				; our tui_text object
	call	tui_text$nvcomposeline

	; our cursor position did not change, BUT, recomposition may have screwed us all up
	; so we still need to find our cursor position
	pop	rsi rdx
	mov	rdi, [rsp]
	call	tui_text$nvfindcursor

	; last but not least, we need to issue a draw
	pop	rdi
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	mov	eax, 1
	epilog

calign
.tab:
	mov	rsi, [rdi]
	call	qword [rsi+tui_vontab]
	mov	eax, 1
	epilog

calign
.shifttab:
	mov	rsi, [rdi]
	call	qword [rsi+tui_vonshifttab]
	mov	eax, 1
	epilog

calign
.cr:
	cmp	dword [rdi+tui_text_multiline_ofs], 0
	jne	.cr_multiline
	mov	rsi, [rdi]
	call	qword [rsi+tui_vonenter]
	mov	eax, 1
	epilog
calign
.cr_multiline:
	; so, enter was pressed, we need to determine our offset into the current buffer
	; and see whether there is more data _after_ our cursor
	; and if there is, move it to our newline that we have to create and truncate the current one

	mov	rdx, [rdi+tui_text_cursorline_ofs]	; item pointer into the cursormap list
	mov	rcx, [rdx]				; the actual cursormap buffer object
	mov	r8d, [rdi+tui_text_cursorx_ofs]
	mov	rax, [rcx+buffer_itself_ofs]
	mov	r9d, [rax+r8]				; offset into the editorline from the cursormap location
	; deal with the simple case first
	; the editorline buffer is at [rcx+buffer_user_ofs]
	mov	r10, [rcx+buffer_user_ofs]
	; now, if r9 == its length, then no move is required
	cmp	r9, [r10+buffer_length_ofs]
	je	.cr_multiline_nomove

	push	rbx r12 r13 r14
	mov	rbx, rdi
	mov	r12, r10
	mov	r14d, r9d			; the offset we were sitting on
	call	buffer$new
	mov	r13, rax
	call	list$new
	mov	[r13+buffer_user_ofs], rax
	call	list$new
	mov	[r13+buffer_user_ofs+8], rax
	mov	rdi, [rbx+tui_text_lines_ofs]
	mov	rsi, [r12+buffer_user_ofs+16]
	mov	rdx, r13
	call	list$insert_after
	mov	[r13+buffer_user_ofs+16], rax
	; so now, append from r14-r12.length to our new buffer, then truncate r12
	mov	rdi, r13
	mov	rsi, [r12+buffer_itself_ofs]
	mov	rdx, [r12+buffer_length_ofs]
	add	rsi, r14
	sub	rdx, r14
	call	buffer$append

	; now we have to compose our new line
	mov	rdi, rbx
	mov	rsi, r13
	call	tui_text$nvcomposeline
	; now we have to truncate r12 by the length of our r13 buffer
	mov	rdi, [r12+buffer_itself_ofs]
	mov	rsi, [r13+buffer_length_ofs]
	mov	rdx, [r12+buffer_length_ofs]
	sub	rdx, rsi
	mov	[r12+buffer_length_ofs], rdx
	add	rdi, rdx
	mov	[r12+buffer_endptr_ofs], rdi
	; now we need to recompose r12
	mov	rdi, rbx
	mov	rsi, r12
	call	tui_text$nvcomposeline

	; our overall height obviously has changed, we need to revalidate our height
	mov	rdi, rbx
	call	tui_text$nvheightchange

	; so now, we should have a valid cursormap hanging off our new editorbuffer
	; we need to set cursorline to the first one in our list
	; and set cursorx and xscroll to 0
	mov	dword [rbx+tui_text_xscroll_ofs], 0
	mov	dword [rbx+tui_text_cursorx_ofs], 0
	mov	rdi, [r13+buffer_user_ofs+8]
	mov	rsi, [rdi+_list_first_ofs]
	; the value at rsi is the ITEM we want
	mov	rdx, [rsi]
	mov	[rbx+tui_text_cursorline_ofs], rdx
	; then call nvfindcursor
	mov	rdi, rbx
	mov	rsi, r13
	xor	edx, edx
	call	tui_text$nvfindcursor
	; then call draw
	mov	rdi, rbx
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]

	pop	r14 r13 r12 rbx
	mov	eax, 1
	epilog

calign
.cr_multiline_nomove:
	; cursor was at the end of the editorline, so we don't have to do any copying
	; so now, the pointer at [r10+buffer_user_ofs+16] is the list _item_ pointer in the editorline list
	; so we need to insert_after a new empty bufferline, and create our viewline and cursormap line
	; we can do that with a composeline of just the newline
	; save rdi and r10
	push	rbx r12 r13
	mov	rbx, rdi
	mov	r12, r10
	call	buffer$new
	mov	r13, rax
	call	list$new
	mov	[r13+buffer_user_ofs], rax
	call	list$new
	mov	[r13+buffer_user_ofs+8], rax
	mov	rdi, [rbx+tui_text_lines_ofs]
	mov	rsi, [r12+buffer_user_ofs+16]
	mov	rdx, r13
	call	list$insert_after
	mov	[r13+buffer_user_ofs+16], rax
	; now we have to compose it
	mov	rdi, rbx
	mov	rsi, r13
	call	tui_text$nvcomposeline
	; our overall height obviously has changed, we need to revalidate our height
	mov	rdi, rbx
	call	tui_text$nvheightchange

	; so now, we should have a valid cursormap hanging off our new editorbuffer
	; we need to set cursorline to the first one in our list
	; and set cursorx and xscroll to 0
	mov	dword [rbx+tui_text_xscroll_ofs], 0
	mov	dword [rbx+tui_text_cursorx_ofs], 0
	mov	rdi, [r13+buffer_user_ofs+8]
	mov	rsi, [rdi+_list_first_ofs]
	; the value at rsi is the ITEM we want
	mov	rdx, [rsi]
	mov	[rbx+tui_text_cursorline_ofs], rdx
	; then call nvfindcursor
	mov	rdi, rbx
	mov	rsi, r13
	xor	edx, edx
	call	tui_text$nvfindcursor
	; then call draw
	mov	rdi, rbx
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]

	pop	r13 r12 rbx
	mov	eax, 1
	epilog

calign
.scrollcheck:
	; when we are not editable, if we have focus, we still may allow scrolling our text view
	breakpoint
calign
.trueret:
	mov	eax, 1
	epilog
calign
.falseret:
	xor	eax, eax
	epilog

end if


if used tui_text$onenter | defined include_everything
	; single argument in rdi: our tui_text object
	; default one here doesn't do anything
falign
tui_text$onenter:
	prolog	tui_text$onenter
	; donothing
	epilog

end if

if used tui_text$nvcomposeline | defined include_everything
	; two arguments: rdi == our tui_text object, rsi == editorline we are recomposing
	; this one is called from keyevent after we modify an editorline
	; so we have to figure out what kind of editor goods we are
	; and call the corresponding composer
falign
tui_text$nvcomposeline:
	prolog	tui_text$nvcomposeline
	mov	eax, [rdi+tui_text_align_ofs]
	xchg	rdi, rsi
	cmp	eax, tui_textalign_left
	je	.left
	cmp	eax, tui_textalign_center
	je	.center
	cmp	eax, tui_textalign_right
	je	.right
	call	tui_text$nvjustifiedcompose
	epilog
calign
.left:
	call	tui_text$nvleftcompose
	epilog
calign
.center:
	call	tui_text$nvcentercompose
	epilog
calign
.right:
	call	tui_text$nvrightcompose
	epilog

end if


if used tui_text$nvcompose | defined include_everything
	; single argument in rdi: our tui_text object
falign
tui_text$nvcompose:
	prolog	tui_text$nvcompose
	; burning purpose: our width changed, so we need to "recompose" all our viewlines
	; in addition to recomposing, we also need to take care to preserve (where possible) our topline/bottomline/cursorline offsets
	push	rbx r12 r13 r14
	mov	rbx, rdi
	xor	r12d, r12d			; if this is zero/null, we set everything up as initial
	cmp	qword [rdi+tui_text_topline_ofs], 0
	je	.nocursormap			; skip searching if we are doing the initial one

	; otherwise, we are doing a recompose of already visible goods, meaning, we have valid
	; viewlines and cursormaps, and valid cursorline

	; this obviously presents some complexities, due to the fact that the # of viewlines
	; may increase or decrease, and that all of them are getting blasted and redone
	
	; so, our burning requirement is: keep the cursor position glued to exactly where it is
	; in the editor lines, and "try our best" to keep the view looking similar to the way it
	; is before our resize.
	
	; we of course can't be accurate with that last bit, simply because our size may have
	; changed quite a bit, and there is no way to preserve exact scrolling/viewing lines/etc


	; first up: get the editor line buffer from the cursorline
	mov	rsi, [rdi+tui_text_cursorline_ofs]	; it is a list item
	mov	rdx, [rsi]				; and its value is a buffer
	mov	r12, [rdx+buffer_user_ofs]		; linkback to the editorline it references

	; next up: get the actual offset
	mov	r8d, [rdi+tui_text_cursorx_ofs]		; x position into the cursormap
	mov	rax, [rdx+buffer_itself_ofs]		; cursormap buffer
	mov	r13d, [rax+r8]				; the offset into the editorline
	
	; we need to know how many lines down from the top the cursormap was
	mov	rdx, [rdi+tui_text_topline_ofs]
	xor	r14d, r14d
	xor	eax, eax
calign
.topmost_findcursor:
	mov	r9, [rdx]
	cmp	r12, [r9+buffer_user_ofs]
	cmove	r14d, eax
	add	eax, 1
	cmp	rdx, [rdi+tui_text_bottomline_ofs]
	je	.nocursormap
	mov	rdx, [rdx+_list_nextofs]
	jmp	.topmost_findcursor

	; r12 == real editorline of the cursor
	; r13d == real editorline offset of the cursor
	; so now we have the # of lines down from the top that our cursor was at in r14d

calign
.nocursormap:
	; now we can blast both lists, recompose everything, and then re-establish
	; our bottom/cursor, as well as set our "real" cursor
	; in addition to blasting the viewlines and cursormap lists, we need to also
	; clear both of them hanging off each editor line
	mov	rdi, [rbx+tui_text_lines_ofs]
	mov	rsi, .lineclear
	call	list$foreach

	mov	rdi, [rbx+tui_text_viewlines_ofs]
	mov	rsi, buffer$destroy
	call	list$clear
	mov	rdi, [rbx+tui_text_cursormap_ofs]
	mov	rsi, buffer$destroy
	call	list$clear
	mov	eax, [rbx+tui_text_align_ofs]
	mov	rsi, tui_text$nvleftcompose
	mov	rdx, rbx
	mov	r8, tui_text$nvcentercompose
	mov	r9, tui_text$nvrightcompose
	mov	r10, tui_text$nvjustifiedcompose
	cmp	eax, tui_textalign_center
	cmove	rsi, r8
	cmp	eax, tui_textalign_right
	cmove	rsi, r9
	cmp	eax, tui_textalign_justified
	cmove	rsi, r10
	mov	rdi, [rbx+tui_text_lines_ofs]
	call	list$foreach_arg

	; so now, we have fresh viewlines and cursormaps
	test	r12d, r12d
	jz	.initialvalues

	; first order of business: set topline and bottomline
	mov	rdi, [rbx+tui_text_viewlines_ofs]
	mov	rsi, [rdi+_list_first_ofs]
	mov	[rbx+tui_text_topline_ofs], rsi
	mov	[rbx+tui_text_bottomline_ofs], rsi

	; save our topline item entry for later scanning:
	mov	r8, rsi
	mov	r9, rsi

	; next up: expand bottomline to accommodate our actual height, or end
	mov	ecx, [rbx+tui_height_ofs]
	sub	ecx, 1			; we already have one
	jz	.no_bottom_expansion	; special case for when we only have one line
calign
.bottom_expansion:
	cmp	qword [rsi+_list_nextofs], 0
	je	.no_bottom_expansion
	mov	rsi, [rsi+_list_nextofs]
	mov	[rbx+tui_text_bottomline_ofs], rsi
	sub	ecx, 1
	jnz	.bottom_expansion
calign
.no_bottom_expansion:
	; then, walk both forward until we have the right editorline in our visible area
	cmp	qword [rsi+_list_nextofs], 0
	je	.no_scrollforward	; skip walking if we can't walk forward anyway
	mov	r9, r8			; start at the top
	; otherwise, scan from top to bottom searching for the r12 editorbuffer, make sure we find it
calign
.cursor_search:
	mov	rcx, [r9]		; its value is a viewline buffer
	cmp	r12, [rcx+buffer_user_ofs]	; is this the cursor line?
	je	.no_scrollforward	; if we found it, let nvfindcursor do the deed for us
	cmp	r9, rsi
	je	.cursor_search_scroll
	mov	r9, [r9+_list_nextofs]
	jmp	.cursor_search
calign
.cursor_search_scroll:
	mov	r8, [r8+_list_nextofs]
	mov	rsi, [rsi+_list_nextofs]
	mov	[rbx+tui_text_topline_ofs], r8
	mov	[rbx+tui_text_bottomline_ofs], rsi
	jmp	.no_bottom_expansion
calign
.no_scrollforward:
	; then, call nvfindcursor to get the right spot
	mov	rdi, rbx
	mov	rsi, r12
	mov	edx, r13d
	call	tui_text$nvfindcursor

	; last but not least, the value in r14d was hte old # of visible lines down to the cursorline
	; leaving it here means the cursor will always be at/near the bottom in a resize event
	; which for my purposes is just fine... TODO: someday when I am bored, honour the linecount downward and adjust the scroll position

	pop	r14 r13 r12 rbx
	epilog


falign
.lineclear:
	; called with rdi == our editor line buffer object
	; all we need to do is clear both lists hanging off our object
	push	rdi
	mov	rdi, [rdi+buffer_user_ofs+8]
	xor	esi, esi
	call	list$clear
	pop	rdi
	xor	esi, esi
	mov	rdi, [rdi+buffer_user_ofs]
	call	list$clear
	ret
	


calign
.initialvalues:
	; no previous topline existed, so set our cursorline, cursoroffset, topline, bottomline, and our real cursor positions
	; all to their initial positions
	mov	rdi, [rbx+tui_text_viewlines_ofs]
	mov	rsi, [rdi+_list_last_ofs]
	mov	[rbx+tui_text_bottomline_ofs], rsi
	mov	eax, [rbx+tui_height_ofs]
	mov	ecx, 1
	sub	eax, 1
	jz	.initialvalues_topgood
	cmp	qword [rsi+_list_prevofs], 0
	je	.initialvalues_topgood
calign
.initialvalues_topsearch:
	mov	rsi, [rsi+_list_prevofs]
	add	ecx, 1
	sub	eax, 1
	jz	.initialvalues_topgood
	cmp	qword [rsi+_list_prevofs], 0
	jne	.initialvalues_topsearch
calign
.initialvalues_topgood:
	mov	[rbx+tui_text_topline_ofs], rsi
	; ecx has how many viewlines we really have, which may or may not be our tui_height
	mov	rdi, [rbx+tui_text_cursormap_ofs]
	mov	rsi, [rdi+_list_last_ofs]
	mov	[rbx+tui_text_cursorline_ofs], rsi
	; now we need to determine our real cursor x position (noting here that we may have to xscroll this)
	mov	r8, [rsi]			; the cursormap buffer
	mov	r9, [r8+buffer_itself_ofs]
	mov	r10, [r8+buffer_length_ofs]
	; we need to be able to compare the dword value with the length of the LINE buffer that it references
	; to determine the real x position
	mov	r11, [r8+buffer_user_ofs]	; the editor line buffer it references
	mov	r12, [r11+buffer_length_ofs]
	xor	edx, edx
calign
.initialvalues_xsearch:
	cmp	dword [r9+rdx*4], r12d
	je	.initialvalues_xfound
	add	edx, 1
	sub	r10d, 4
	jnz	.initialvalues_xsearch
calign
.initialvalues_xfound:
	; so now, edx == our "view based x modifier"
	; and ecx == how many lines we took up out of the height, aka our cursor y modifier
	; take one from ecx, since our scroll position is 0 based, not 1 based
	sub	ecx, 1

	; so, if edx >= width, we need to enable xscroll
	cmp	edx, [rbx+tui_width_ofs]
	jae	.initialvalues_xscroll

	mov	dword [rbx+tui_text_cursor_ofs], edx
	mov	dword [rbx+tui_text_cursor_ofs+4], ecx

	; we need to set our cursorx offset to edx in BYTES (this corresponds to the current cursorline pointer)
	mov	eax, edx
	shl	eax, 2
	mov	dword [rbx+tui_text_cursorx_ofs], eax

	cmp	dword [rbx+tui_text_focussed_ofs], 0
	je	.initialvalues_xsearch_nocursor
	cmp	dword [rbx+tui_text_docursor_ofs], 0
	je	.initialvalues_xsearch_nocursor
	
	mov	esi, [rbx+tui_bounds_ax_ofs]
	add	esi, edx
	mov	edx, [rbx+tui_bounds_ay_ofs]
	add	edx, ecx
	mov	rdi, rbx
	mov	rcx, [rbx]
	call	qword [rcx+tui_vsetcursor]
calign
.initialvalues_xsearch_nocursor:
	pop	r14 r13 r12 rbx
	epilog

calign
.initialvalues_xscroll:
	mov	r10d, edx
	mov	r9d, [rbx+tui_width_ofs]
	sub	r10d, [rbx+tui_width_ofs]
	add	r10d, 1
	sub	r9d, 1
	shl	r10d, 2

	mov	[rbx+tui_text_xscroll_ofs], r10d

	mov	dword [rbx+tui_text_cursor_ofs], r9d
	
	; copy of the above
	mov	dword [rbx+tui_text_cursor_ofs+4], ecx

	; we need to set our cursorx offset to edx in BYTES (this corresponds to the current cursorline pointer)
	mov	eax, edx
	shl	eax, 2
	mov	dword [rbx+tui_text_cursorx_ofs], eax

	cmp	dword [rbx+tui_text_focussed_ofs], 0
	je	.initialvalues_xsearch_nocursor
	cmp	dword [rbx+tui_text_docursor_ofs], 0
	je	.initialvalues_xsearch_nocursor
	
	mov	esi, [rbx+tui_bounds_ax_ofs]
	add	esi, edx
	mov	edx, [rbx+tui_bounds_ay_ofs]
	add	edx, ecx
	mov	rdi, rbx
	mov	rcx, [rbx]
	call	qword [rcx+tui_vsetcursor]

	pop	r14 r13 r12 rbx
	epilog




end if


	; some notes here on our various compose methods...
	;
	; since we support an editable dynamic layout, which is to say: non 1:1 underlying editor text to display text
	; we have to deal with several scenarios
	;
	; if our wrap is 0, aka horiz scroll, then our viewlines and cursormap contains a 1:1 ratio to the editor lines
	; and the width of each line is max(longest line length, tui_width)
	;
	; if our wrap is nonzero, either hardwrap or wordwrap, our viewlines and cursormap contains our tui_width's lines
	; however many are necessary to accommodate our editor line. Simple if hardwrap, highly variable otherwise.
	;
	; the cursor map is essential to maintain underlying editor sanity during wordwrap, justification, etc
	; whereby what ends up onscreen has nothign to do with the underlying editor (space/linefeed-wise)
	;
	; for example, if we are fully justifying text, and we add 6 spaces between words, a cursor position into any
	; of our 6 "contrived" spaces all point to the real editor line's single space offset, thus allowing sensible
	; editing and dynamic layout of on-the-fly text
	;
	; not really sure how the "bigboy" word processors do this same functionality, but I can't imagine there to be
	; very many different ways of skinning this particular cat.. haha
	;


if used tui_text$nvnewviewline | defined include_everything
	; three arguments: rdi == our tui_text object, rsi == editor line buffer object, rdx == previous viewline editor buffer object
	; NONSTANDARD RETURN: returns viewline buffer object in rax, cursormap buffer object in rdx, both reset/cleared

	; NOTE: this assumes that if rdx is nonzero, there _IS_ a previous entry (meaning: rdx really is already in the editor line's viewlist)
	;       the way this routine is called from the compose methods ensures this..
falign
tui_text$nvnewviewline:
	prolog	tui_text$nvnewviewline
	push	rbx r12
	mov	rbx, rdi
	mov	r12, rsi
	; so the idea here is: if one of our compose methods got called "freshlike", then the two lists
	; hanging off our editor line buffer object will be empty, and we can just create two new buffers and return
	; if however, the compose method was called after an editor line modification, then we need to recompose
	; our viewlines/cursormap lines _in place_ (and in this way, after a keypress, the entirety of our linelist
	; doesn't need to be fully recomposed after every editor event)
	test	rdx, rdx
	jz	.noprev
	; otherwise, search through the editor lines entries, see above re: assumption, rdx _must_ exist in the list already
	; we need to walk forward in _both_ lists simultaneously, until we find rdx
	mov	rdi, [r12+buffer_user_ofs]
	mov	rsi, [r12+buffer_user_ofs+8]
	mov	rdi, [rdi+_list_first_ofs]
	mov	rsi, [rsi+_list_first_ofs]
calign
.searchloop:
	; the editor line entries contain list _item_ entries from the actual viewlist and cursormap lists in the tui_text object
	; which means: the value at [rdi] and [rsi] is actually itself another list ITEM
	mov	r8, [rdi]
	mov	r9, [rsi]
	; so now, the one we are looking for is at [r8]
	cmp	rdx, [r8]
	je	.foundit
	; otherwise, keep going
	; TODO: remove the following sanity check for production
	cmp	qword [rdi+_list_nextofs], 0
	je	.kakked
	mov	rdi, [rdi+_list_nextofs]
	mov	rsi, [rsi+_list_nextofs]
	jmp	.searchloop
calign
.kakked:
	breakpoint
calign
.foundit:
	; now, if there is no next, which is entirely possible, we create fresh ones and are done
	; if there is a next one, then we reset both buffers and return them

	; if we jump straight to .freshies, then our new line gets placed at the _end_ of the
	; tui_text's lists, but if we were editing a line in place, and our editing caused this
	; editor line to expand in linecount, then our newly created line would end up incorrectly
	; placed...

	cmp	qword [rdi+_list_nextofs], 0
	je	.freshies_insertorder


	; otherwise, move to the next spot for both lists
	mov	rdi, [rdi+_list_nextofs]
	mov	rsi, [rsi+_list_nextofs]
	mov	r8, [rdi]
	mov	r9, [rsi]
	; buffers are at [r8] and [r9]
	push	qword [r8]
	push	qword [r9]
	mov	rdi, [r8]
	call	buffer$reset
	mov	rdi, [rsp]
	call	buffer$reset
	pop	rdx rax r12 rbx
	epilog
calign
.noprev:
	; if our editor line's list is empty, create new ones and be done
	mov	rdx, [r12+buffer_user_ofs]
	mov	rcx, [r12+buffer_user_ofs+8]
	mov	r8, [rdx+_list_first_ofs]
	mov	r9, [rcx+_list_first_ofs]
	test	r8, r8
	jz	.freshies
	; otherwise, our return is the first from each list

	; so the values at [r8] and [r9] are the item from the tui_text list proper
	; and the values at those values again are the buffer objects
	mov	r8, [r8]
	mov	r9, [r9]				; the list items

	mov	rax, [r8]
	mov	rdx, [r9]				; the buffer objects

	push	rax rdx
	mov	rdi, rax
	call	buffer$reset
	mov	rdi, [rsp]
	call	buffer$reset

	pop	rdx rax
	epilog
calign
.freshies_insertorder:
	; if r8 has no next, go straight to normal freshies which adds us to the end
	cmp	qword [r8+_list_nextofs], 0
	je	.freshies
	; otherwise, our new entry has to occur for both lists in place, after r8 and r9 in the tui_text's
	; viewlines and cursormap list
	push	r13 r14
	mov	r13, r8
	mov	r14, r9
	call	buffer$new
	push	rax
	mov	[rax+buffer_user_ofs], r12			; backward link from the viewline buffer object to the editor line buffer
	mov	rdi, [rbx+tui_text_viewlines_ofs]
	mov	rsi, r13
	mov	rdx, rax
	call	list$insert_after
	mov	rsi, rax
	mov	rdi, [r12+buffer_user_ofs]
	call	list$push_back
	; do the same again for cursormap
	call	buffer$new
	push	rax
	mov	[rax+buffer_user_ofs], r12			; backward link from the viewline buffer object to the editor line buffer
	mov	rdi, [rbx+tui_text_cursormap_ofs]
	mov	rsi, r14
	mov	rdx, rax
	call	list$insert_after
	mov	rsi, rax
	mov	rdi, [r12+buffer_user_ofs+8]
	call	list$push_back
	pop	rdx rax r14 r13 r12 rbx
	epilog
calign
.freshies:

	;
	; two case scenarios here: 1) our editor buffer is the last of its kind
	; 2) our editor buffer is actually in the middle somewhere and has a next, in which case
	; we have to do insert_after instead of push_back to the tui_text_viewlines_ofs and tui_text_cursormap_ofs
	;
	mov	rax, [r12+buffer_user_ofs+16]			; its list item position
	cmp	qword [rax+_list_nextofs], 0
	jne	.freshies_insertbefore

calign
.freshies_doit:
	call	buffer$new
	push	rax		; save it for our return
	mov	[rax+buffer_user_ofs], r12			; backward link from the viewline buffer object to the editor line buffer
	; we add the actual buffer to the viewlines/cursormap list in our tui_text object, and then we add
	; the list _item_ itself to our editor line buffer's lists
	mov	rdi, [rbx+tui_text_viewlines_ofs]
	mov	rsi, rax
	call	list$push_back
	; list's last is what we need to insert into our editor line list
	mov	rsi, rax
	mov	rdi, [r12+buffer_user_ofs]
	call	list$push_back
	; do same again for the cursormap entry
	call	buffer$new
	push	rax		; save it for our return
	mov	[rax+buffer_user_ofs], r12			; backward link from the cursormap buffer object to the editor line buffer
	mov	rdi, [rbx+tui_text_cursormap_ofs]
	mov	rsi, rax
	call	list$push_back
	mov	rsi, rax
	mov	rdi, [r12+buffer_user_ofs+8]
	call	list$push_back

	pop	rdx rax r12 rbx
	epilog
calign
.freshies_insertbefore:
	; if our initial text contained multiple lines, it is possible that even though we had a
	; next editorbuffer, that it wasn't actually setup yet, so make sure it really does have one
	mov	rax, [rax+_list_nextofs]
	mov	rax, [rax]					; the editorbuffer line at the next spot
	mov	rcx, [rax+buffer_user_ofs]			; its viewline list
	cmp	qword [rcx+_list_first_ofs], 0
	je	.freshies_doit

	push	r13 r14
	; we need the first _item_ from the next editor buffer's viewlines list and cursormap list
	mov	rdx, [rax+buffer_user_ofs+8]			; its cursormap list
	mov	rcx, [rcx+_list_first_ofs]
	mov	rdx, [rdx+_list_first_ofs]
	mov	r13, [rcx]
	mov	r14, [rdx]

	call	buffer$new
	push	rax
	mov	[rax+buffer_user_ofs], r12			; backward link from the viewline buffer object to the editor line buffer
	mov	rdi, [rbx+tui_text_viewlines_ofs]
	mov	rsi, r13
	mov	rdx, rax
	call	list$insert_before
	mov	rsi, rax
	mov	rdi, [r12+buffer_user_ofs]
	call	list$push_back
	; do same again for the cursormap entry
	call	buffer$new
	push	rax
	mov	[rax+buffer_user_ofs], r12			; backward link from the cursormap buffer object to the editor line buffer
	mov	rdi, [rbx+tui_text_cursormap_ofs]
	mov	rsi, r14
	mov	rdx, rax
	call	list$insert_before
	mov	rsi, rax
	mov	rdi, [r12+buffer_user_ofs+8]
	call	list$push_back
	pop	rdx rax r14 r13 r12 rbx
	epilog

end if

if used tui_text$nvlastviewline | defined include_everything
	; three arguments: rdi == our tui_text object, rsi == editor line buffer object, rdx == previous viewline editor buffer object
	; this clears/truncates viewlines/cursormap entries (if any) that may exist _after_ rdx

	; similar to the newviewline, this assumes that rdx really is in the list
falign
tui_text$nvlastviewline:
	prolog	tui_text$nvlastviewline
	; and in line with the above, this makes sure that rdx is the _last_ line, and we'll remove viewlines/cursormap entries
	; that may have belonged to us before but no longer do now
	push	rbx r12
	mov	rbx, rdi
	mov	r12, rsi

	; search through the editor lines entries, see above re: assumption, rdx _must_ exist in the list already
	; we need to walk forward in _both_ lists simultaneously, until we find rdx
	mov	rdi, [r12+buffer_user_ofs]
	mov	rsi, [r12+buffer_user_ofs+8]
	mov	rdi, [rdi+_list_first_ofs]
	mov	rsi, [rsi+_list_first_ofs]
calign
.searchloop:
	; the editor line entries contain list _item_ entries from the actual viewlist and cursormap lists in the tui_text object
	; which means: the value at [rdi] and [rsi] is actually itself another list ITEM
	mov	r8, [rdi]
	mov	r9, [rsi]
	; so now, the one we are looking for is at [r8]
	cmp	rdx, [r8]
	je	.foundit
	; otherwise, keep going
	; TODO: remove the following sanity check for production
	cmp	qword [rdi+_list_nextofs], 0
	je	.kakked
	mov	rdi, [rdi+_list_nextofs]
	mov	rsi, [rsi+_list_nextofs]
	jmp	.searchloop
calign
.kakked:
	breakpoint
calign
.foundit:
	; now, if there is no next, then we have nothing to do, as rdx already is the last one
	cmp	qword [rdi+_list_nextofs], 0
	je	.alldone

	; save rdi and rsi for our re-entry (they will remain valid after our list removals)
	push	rdi rsi
	
	; save the previous cursormap entry, in case we have to set cursorline to it
	mov	rax, rsi
	; otherwise, move to the next spot, we need to remove the next spot from BOTH lists for each viewline and cursormap
	mov	rdi, [rdi+_list_nextofs]
	mov	rsi, [rsi+_list_nextofs]

	; if the cursorline pointer for our tui_text object is pointing at rsi, set it to its previous value
	cmp	rsi, [rbx+tui_text_cursorline_ofs]
	jne	.notcursor
	mov	[rbx+tui_text_cursorline_ofs], rax
calign
.notcursor:
	; so [rdi] is now the list _item_ entry in the tui_text's viewline list
	; and [rsi] is the list _item_ entry in the tui_text's cursormap list
	mov	r8, [rdi]
	mov	r9, [rsi]
	; buffers are at [r8] and [r9]
	push	qword [r8]
	push	qword [r9]

	; so, not only do we need to destroy the buffers now on the stack, we have 4 list removes do do
	; rdi gets removed from [r12+buffer_user_ofs]
	; rsi gets removed from [r12+buffer_user_ofs+8]
	; r8 gets removed from [rbx+tui_text_viewlines_ofs]
	; r9 gets removed from [rbx+tui_text_cursormap_ofs]
	push	rdi rsi r8
	mov	rdi, [rbx+tui_text_cursormap_ofs]
	mov	rsi, r9
	call	list$remove
	pop	rsi
	mov	rdi, [rbx+tui_text_viewlines_ofs]
	call	list$remove
	pop	rsi
	mov	rdi, [r12+buffer_user_ofs+8]
	call	list$remove
	pop	rsi
	mov	rdi, [r12+buffer_user_ofs]
	call	list$remove
	pop	rdi
	call	buffer$destroy
	pop	rdi
	call	buffer$destroy

	; restore rsi and rdi back to our "found rdx" position in the list, and go do it again
	pop	rsi rdi
	jmp	.foundit
calign
.alldone:
	pop	r12 rbx
	epilog

end if




if used tui_text$nvleftcompose | defined include_everything
	; two arguments: rdi == our editor line buffer object, rsi == tui_text object
falign
tui_text$nvleftcompose:
	prolog	tui_text$nvleftcompose


	; called with rdi == our editor line buffer, rsi == tui_text object
	push	rbp rbx r12 r13 r14 r15
	push	rsi					; one extra save of our tui_text object, since we blast rbx later
	mov	rbx, rsi
	mov	r12, rdi
	mov	rbp, [rdi+buffer_itself_ofs]
	mov	r13, [rdi+buffer_length_ofs]
	xor	r14d, r14d				; clear our previous viewline buffer object, needed for newviewline calls

	; for all methods of wrapping, we do not do "dangling valid cursor positions"
	; so if the last line is _full_ we create a new empty one with a single valid cursormap position
	
	cmp	dword [rsi+tui_text_wrap_ofs], 0
	je	.nowrap
	; else, hardwrap (1) or wordwrap (2), fixed viewlines to our tui width, minimum 1 line
calign
.wrap:
	mov	rdi, rbx
	mov	rsi, r12
	mov	rdx, r14
	call	tui_text$nvnewviewline
	mov	r14, rax
	mov	r15, rdx
	
	mov	rdi, rax
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$reserve
	mov	rdi, r15
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$reserve
	mov	rdi, r14
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$append_nocopy
	mov	rdi, r15
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$append_nocopy
	mov	rdi, [r14+buffer_itself_ofs]
	mov	esi, ' '
	mov	edx, [rbx+tui_width_ofs]
	shl	edx, 2
	call	memset32
	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, 0xffffffff
	mov	edx, [rbx+tui_width_ofs]
	shl	edx, 2
	call	memset32
	; r13d has our source text length in bytes
	mov	edx, [rbx+tui_width_ofs]
	mov	rdi, [r14+buffer_itself_ofs]
	mov	rsi, rbp
	shl	edx, 2
	cmp	r13d, edx
	cmovb	edx, r13d
	; if there was less than a full line, we don't do wordwrap either
	jb	.wrap_nowordwrap

	; if there is still space left in the buffer _after_ edx bytes are consumed
	; and if wrap == 2, we need to attempt to wordwrap it to the next line
	; up to half our width we'll walk backward looking for a space
	cmp	dword [rbx+tui_text_wrap_ofs], 2
	jne	.wrap_nowordwrap
	mov	eax, r13d
	sub	eax, edx
	jz	.wrap_nowordwrap
	test	edx, edx
	jz	.wrap_nowordwrap		; sanity only
	; so, from a starting position of edx-4 backward for a maximum of half our width
	; we need to look for a space, if we make it backward for half our width
	; without finding a space, leave it alone and do hardwrap normally

	mov	r8d, [rbx+tui_width_ofs]
	shr	r8d, 1				; limit for our search
	mov	r9d, edx
	sub	r9d, 4				; search position relative to rsi
	test	r8d, r8d
	jz	.wrap_nowordwrap		; special case if we are ridiculously small
	test	r9d, r9d
	jz	.wrap_nowordwrap		; ""
calign
.wrap_wordwrap:
	cmp	dword [rsi+r9], 32		; split on space
	je	.wrap_wordwrap_chop
	cmp	dword [rsi+r9], '-'		; split on hyphens too
	sub	r9d, 4
	jz	.wrap_nowordwrap
	sub	r8d, 1
	jz	.wrap_nowordwrap
	jmp	.wrap_wordwrap
calign
.wrap_wordwrap_chop_pwdchar:
	mov	esi, [rbx+tui_text_pwdchar_ofs]
	call	memset32

	; populate the cursormap with our modified offset + the real offset
	mov	rax, rbp
	sub	rax, [r12+buffer_itself_ofs]

	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, [rsp]
	mov	rdx, [r15+buffer_length_ofs]
	add	esi, eax				; modified correct offset
	call	memset32

	pop	rdx
	mov	rdi, [r15+buffer_itself_ofs]
	jmp	.wrap_nowordwrap_doit
calign
.wrap_wordwrap_chop:
	; so we walked backward and found a space suitable for wordwrap
	; we can safely include the space on our line
	add	r9d, 4
	; r9d is now the correct length of our memcpy, but we also need to preserve it for afterthefact
	mov	edx, r9d
	push	r9

	cmp	dword [rbx+tui_text_pwdchar_ofs], 0
	jne	.wrap_wordwrap_chop_pwdchar
	call	memcpy
	
	; populate the cursormap with our modified offset + the real offset
	mov	rax, rbp
	sub	rax, [r12+buffer_itself_ofs]

	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, [rsp]
	mov	rdx, [r15+buffer_length_ofs]
	add	esi, eax				; modified correct offset
	call	memset32

	pop	rdx
	mov	rdi, [r15+buffer_itself_ofs]
	jmp	.wrap_nowordwrap_doit
.wrap_nowordwrap_pwdchar:
	mov	esi, [rbx+tui_text_pwdchar_ofs]
	call	memset32
	; populate our cursormap next, noting it is possible
	; we have a zero-length entry
	mov	rdi, [r15+buffer_itself_ofs]
	mov	edx, [rbx+tui_width_ofs]
	shl	edx, 2
	cmp	r13d, edx
	cmovb	edx, r13d
	jmp	.wrap_nowordwrap_doit
calign
.wrap_nowordwrap:
	cmp	dword [rbx+tui_text_pwdchar_ofs], 0
	jne	.wrap_nowordwrap_pwdchar
	call	memcpy
	; populate our cursormap next, noting it is possible
	; we have a zero-length entry
	mov	rdi, [r15+buffer_itself_ofs]
	mov	edx, [rbx+tui_width_ofs]
	shl	edx, 2
	cmp	r13d, edx
	cmovb	edx, r13d
calign
.wrap_nowordwrap_doit:
	; we need to determine our real offset into the source line, which is rbp - [r12+buffer_itself_ofs]
	mov	rax, rbp
	sub	rax, [r12+buffer_itself_ofs]
	xor	ecx, ecx
	; we can safely decrement r13d and increment rbp by edx at this stage
	sub	r13d, edx
	add	rbp, rdx
	test	edx, edx
	jz	.wrap_cursormap_done
calign
.wrap_cursormap:
	mov	[rdi+rcx], eax
	add	ecx, 4
	add	eax, 4
	sub	edx, 4
	jnz	.wrap_cursormap
calign
.wrap_cursormap_done:
	; if r13d is zero now, we have to add one more spot as a valid cursor position
	; _but_, r13d is zero and rcx == r15's buffer length, then we have a special case of adding an empty line
	test	r13d, r13d
	jnz	.wrap				; go back to the top and do it again if we still have more left
	; otherwise, we have exhaused our source line
	cmp	rcx, [r15+buffer_length_ofs]
	jne	.wrap_partial_last
	push	rax
	; otherwise, we need to add one more empty line
	mov	rdi, rbx
	mov	rsi, r12
	mov	rdx, r14
	call	tui_text$nvnewviewline
	mov	r14, rax
	mov	r15, rdx
	
	mov	rdi, rax
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$reserve
	mov	rdi, r15
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$reserve
	mov	rdi, r14
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$append_nocopy
	mov	rdi, r15
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$append_nocopy
	mov	rdi, [r14+buffer_itself_ofs]
	mov	esi, ' '
	mov	edx, [rbx+tui_width_ofs]
	shl	edx, 2
	call	memset32
	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, 0xffffffff
	mov	edx, [rbx+tui_width_ofs]
	shl	edx, 2
	call	memset32

	mov	rdi, [r15+buffer_itself_ofs]
	xor	ecx, ecx
	pop	rax
calign
.wrap_partial_last:
	mov	[rdi+rcx], eax				; last entry in the cursormap is a valid cursor position (dangling)
	; one last call to lastviewline
	pop	rdi
	mov	rsi, r12
	mov	rdx, r14
	call	tui_text$nvlastviewline
	; done, dusted
	pop	r15 r14 r13 r12 rbx rbp
	epilog
calign
.nowrap:
	; get our viewline buffer object and cursormap buffer object from newviewline
	mov	rdi, rbx
	mov	rsi, r12
	mov	rdx, r14
	call	tui_text$nvnewviewline
	mov	r14, rax
	mov	r15, rdx
	; cursormap has to be calculated, viewline needs ' ' fill + text copy
	mov	rdi, rbx
	call	tui_text$nvmaxx
	mov	ecx, [rbx+tui_width_ofs]
	cmp	eax, ecx
	cmovb	eax, ecx

	mov	r8d, [rbx+tui_text_pwdchar_ofs]
	push	r8
	
	; eax now contains the correct length of our viewline to add in characters
	mov	ebx, eax
	shl	ebx, 2
	mov	rdi, r14
	mov	esi, ebx
	call	buffer$reserve
	mov	rdi, r14
	mov	esi, ebx
	call	buffer$append_nocopy
	mov	rdi, [r14+buffer_itself_ofs]
	mov	esi, ' '
	mov	edx, ebx
	call	memset32
	mov	rdi, [r14+buffer_itself_ofs]
	mov	rsi, [r12+buffer_itself_ofs]
	mov	edx, r13d
	pop	r8
	test	r8d, r8d
	jz	.nowrap_memcpy
	mov	esi, r8d
	call	memset32
	; viewline is done, cursormap is next
	mov	rdi, r15
	mov	esi, ebx
	add	esi, 4					; +1 for cursormap, so that we can find a dangling one, but only for .nowrap
	call	buffer$reserve
	mov	rdi, r15
	mov	esi, ebx
	add	esi, 4					; +1 for cursormap, so that we can find a dangling one, but only for .nowrap
	call	buffer$append_nocopy
	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, 0xffffffff
	mov	edx, ebx
	call	memset32
	push	r13					; save the length again for the finale
	; next up, loop to set our offset positions for our cursormap, noting that we take _byte_ offsets, not character offsets
	mov	rdi, [r15+buffer_itself_ofs]
	mov	edx, r13d				; save the length of our source for the final placement
	xor	eax, eax
	test	r13d, r13d
	jz	.nowrap_cursormap_done
	jmp	.nowrap_cursormap
calign
.nowrap_memcpy:
	call	memcpy
	; viewline is done, cursormap is next
	mov	rdi, r15
	mov	esi, ebx
	add	esi, 4					; +1 for cursormap, so that we can find a dangling one, but only for .nowrap
	call	buffer$reserve
	mov	rdi, r15
	mov	esi, ebx
	add	esi, 4					; +1 for cursormap, so that we can find a dangling one, but only for .nowrap
	call	buffer$append_nocopy
	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, 0xffffffff
	mov	edx, ebx
	call	memset32
	push	r13					; save the length again for the finale
	; next up, loop to set our offset positions for our cursormap, noting that we take _byte_ offsets, not character offsets
	mov	rdi, [r15+buffer_itself_ofs]
	mov	edx, r13d				; save the length of our source for the final placement
	xor	eax, eax
	test	r13d, r13d
	jz	.nowrap_cursormap_done
calign
.nowrap_cursormap:
	mov	[rdi+rax], eax
	add	eax, 4
	sub	r13d, 4
	jnz	.nowrap_cursormap
calign
.nowrap_cursormap_done:
	pop	r13
	; since we are not doing wrap, we set the last position off the end as a valid cursor position
	mov	rsi, [rsp]
	mov	[rdi+rax], edx				; last position off the end is a valid cursor position
	; one last call to lastviewline
	pop	rdi
	mov	rsi, r12
	mov	rdx, r14
	call	tui_text$nvlastviewline
	; done, dusted
	pop	r15 r14 r13 r12 rbx rbp
	epilog

end if




if used tui_text$nvrightcompose | defined include_everything
	; two arguments: rdi == our editor line buffer, rsi == tui_text object
falign
tui_text$nvrightcompose:
	prolog	tui_text$nvrightcompose
	push	rbp rbx r12 r13 r14 r15
	push	rsi					; one extra save of our tui_text object, since we blast rbx later
	mov	rbx, rsi
	mov	r12, rdi
	mov	rbp, [rdi+buffer_itself_ofs]
	mov	r13, [rdi+buffer_length_ofs]
	xor	r14d, r14d				; clear our previous viewline buffer object, needed for newviewline calls

	; for all methods of wrapping, we do not do "dangling valid cursor positions"
	; so if the last line is _full_ we create a new empty one with a single valid cursormap position
	
	cmp	dword [rsi+tui_text_wrap_ofs], 0
	je	.nowrap
	; else, hardwrap (1) or wordwrap (2), fixed viewlines to our tui width, minimum 1 line
calign
.wrap:
	mov	rdi, rbx
	mov	rsi, r12
	mov	rdx, r14
	call	tui_text$nvnewviewline
	mov	r14, rax
	mov	r15, rdx
	
	mov	rdi, rax
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$reserve
	mov	rdi, r15
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$reserve
	mov	rdi, r14
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$append_nocopy
	mov	rdi, r15
	mov	esi, [rbx+tui_width_ofs]
	shl	esi, 2
	call	buffer$append_nocopy
	mov	rdi, [r14+buffer_itself_ofs]
	mov	esi, ' '
	mov	edx, [rbx+tui_width_ofs]
	shl	edx, 2
	call	memset32
	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, 0xffffffff
	mov	edx, [rbx+tui_width_ofs]
	shl	edx, 2
	call	memset32
	; r13d has our source text length in bytes
	mov	edx, [rbx+tui_width_ofs]
	mov	rdi, [r14+buffer_itself_ofs]
	sub	edx, 1					; enforce our 1 char gutter for right-aligned
	mov	rsi, rbp
	shl	edx, 2
	cmp	r13d, edx
	cmovb	edx, r13d
	; if there was less than a full line, we don't do wordwrap either
	jb	.wrap_nowordwrap

	; if there is still space left in the buffer _after_ edx bytes are consumed
	; and if wrap == 2, we need to attempt to wordwrap it to the next line
	; up to half our width we'll walk backward looking for a space
	cmp	dword [rbx+tui_text_wrap_ofs], 2
	jne	.wrap_nowordwrap
	mov	eax, r13d
	sub	eax, edx
	jz	.wrap_nowordwrap
	test	edx, edx
	jz	.wrap_nowordwrap		; sanity only
	; so, from a starting position of edx-4 backward for a maximum of half our width
	; we need to look for a space, if we make it backward for half our width
	; without finding a space, leave it alone and do hardwrap normally

	mov	r8d, [rbx+tui_width_ofs]
	shr	r8d, 1				; limit for our search
	mov	r9d, edx
	sub	r9d, 4				; search position relative to rsi
	test	r8d, r8d
	jz	.wrap_nowordwrap		; special case if we are ridiculously small
	test	r9d, r9d
	jz	.wrap_nowordwrap		; ""
calign
.wrap_wordwrap:
	cmp	dword [rsi+r9], 32		; split on space
	je	.wrap_wordwrap_chop
	cmp	dword [rsi+r9], '-'		; split on hyphens too
	sub	r9d, 4
	jz	.wrap_nowordwrap
	sub	r8d, 1
	jz	.wrap_nowordwrap
	jmp	.wrap_wordwrap
calign
.wrap_wordwrap_chop_pwdchar:
	mov	esi, [rbx+tui_text_pwdchar_ofs]
	call	memset32
	
	; populate the cursormap with our modified offset + the real offset
	mov	rax, rbp
	sub	rax, [r12+buffer_itself_ofs]

	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, [rsp]
	mov	rdx, [r15+buffer_length_ofs]
	add	esi, eax				; modified correct offset
	call	memset32

	pop	rdx
	mov	rdi, [r15+buffer_itself_ofs]
	jmp	.wrap_nowordwrap_doit
calign
.wrap_wordwrap_chop:
	; so we walked backward and found a space suitable for wordwrap
	
	; we can safely include the space on our line
	; unlike left align, we don't add the space to _this_ line:  add	r9d, 4
	; r9d is now the correct length of our memcpy, but we also need to preserve it for afterthefact
	mov	edx, r9d

	; also unlike left align, we need to offset rdi
	mov	r10d, [r14+buffer_length_ofs]
	sub	r10d, edx
	sub	r10d, 4					; +1 for our forced gutter
	add	rdi, r10

	push	r9
	cmp	dword [rbx+tui_text_pwdchar_ofs], 0
	jne	.wrap_wordwrap_chop_pwdchar
	call	memcpy
	
	; populate the cursormap with our modified offset + the real offset
	mov	rax, rbp
	sub	rax, [r12+buffer_itself_ofs]

	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, [rsp]
	mov	rdx, [r15+buffer_length_ofs]
	add	esi, eax				; modified correct offset
	call	memset32

	pop	rdx
	mov	rdi, [r15+buffer_itself_ofs]
	jmp	.wrap_nowordwrap_doit
calign
.wrap_nowordwrap_pwdchar:
	mov	esi, [rbx+tui_text_pwdchar_ofs]
	call	memset32
	; populate our cursormap next, noting it is possible
	; we have a zero-length entry
	mov	rdi, [r15+buffer_itself_ofs]
	mov	edx, [rbx+tui_width_ofs]
	sub	edx, 1
	shl	edx, 2
	cmp	r13d, edx
	cmovb	edx, r13d
	jmp	.wrap_nowordwrap_doit
calign
.wrap_nowordwrap:
	; also unlike left align, we need to offset rdi
	mov	r10d, [r14+buffer_length_ofs]
	sub	r10d, edx
	sub	r10d, 4					; +1 for our forced gutter
	add	rdi, r10

	cmp	dword [rbx+tui_text_pwdchar_ofs], 0
	jne	.wrap_nowordwrap_pwdchar

	call	memcpy
	; populate our cursormap next, noting it is possible
	; we have a zero-length entry
	mov	rdi, [r15+buffer_itself_ofs]
	mov	edx, [rbx+tui_width_ofs]
	sub	edx, 1
	shl	edx, 2
	cmp	r13d, edx
	cmovb	edx, r13d
calign
.wrap_nowordwrap_doit:
	; we need to determine our real offset into the source line, which is rbp - [r12+buffer_itself_ofs]
	mov	rax, rbp
	sub	rax, [r12+buffer_itself_ofs]
	; for right aligned, we need to offset rcx by the difference in lengths

	mov	r11d, edx				; hangon to our actual bytes we did
	add	r11d, 4					; + 1 for our enforced gutter

	mov	ecx, [r15+buffer_length_ofs]
	sub	ecx, r11d

	; we can safely decrement r13d and increment rbp by edx at this stage
	sub	r13d, edx
	add	rbp, rdx
	test	edx, edx
	jz	.wrap_cursormap_done
calign
.wrap_cursormap:
	mov	[rdi+rcx], eax
	add	ecx, 4
	add	eax, 4
	sub	edx, 4
	jnz	.wrap_cursormap
calign
.wrap_cursormap_done:
	; if r13d is zero now, we have to add one more spot as a valid cursor position
	; _but_, r13d is zero and rcx == r15's buffer length, then we have a special case of adding an empty line
	test	r13d, r13d
	jnz	.wrap				; go back to the top and do it again if we still have more left
	; otherwise, we have exhaused our source line

	; because we have an enforced right gutter of 1 char, we don't need to add a separate line?
	jmp	.wrap_partial_last
calign
.wrap_partial_last:
	mov	[rdi+rcx], eax				; last entry in the cursormap is a valid cursor position (dangling)
	; one last call to lastviewline
	pop	rdi
	mov	rsi, r12
	mov	rdx, r14
	call	tui_text$nvlastviewline
	; done, dusted
	pop	r15 r14 r13 r12 rbx rbp
	epilog
calign
.nowrap_pwdchar:
	mov	esi, r8d
	call	memset32
	; viewline is done, cursormap is next
	mov	rdi, r15
	mov	esi, ebx
	add	esi, 4					; +1 for cursormap, so that we can find a dangling one, but only for .nowrap
	call	buffer$reserve
	mov	rdi, r15
	mov	esi, ebx
	add	esi, 4					; +1 for cursormap, so that we can find a dangling one, but only for .nowrap
	call	buffer$append_nocopy
	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, 0xffffffff
	mov	edx, ebx
	call	memset32
	push	r13					; save the length again for the finale
	; next up, loop to set our offset positions for our cursormap, noting that we take _byte_ offsets, not character offsets
	mov	rdi, [r15+buffer_itself_ofs]
	mov	edx, r13d				; save the length of our source for the final placement

	mov	ecx, ebx
	sub	ecx, r13d

	xor	eax, eax
	test	r13d, r13d
	jz	.nowrap_cursormap_done
	jmp	.nowrap_cursormap
calign
.nowrap:
	; get our viewline buffer object and cursormap buffer object from newviewline
	mov	rdi, rbx
	mov	rsi, r12
	mov	rdx, r14
	call	tui_text$nvnewviewline
	mov	r14, rax
	mov	r15, rdx
	; cursormap has to be calculated, viewline needs ' ' fill + text copy
	mov	rdi, rbx
	call	tui_text$nvmaxx
	mov	ecx, [rbx+tui_width_ofs]
	cmp	eax, ecx
	cmovb	eax, ecx

	mov	r8d, [rbx+tui_text_pwdchar_ofs]
	push	r8
	
	; eax now contains the correct length of our viewline to add in characters
	mov	ebx, eax
	shl	ebx, 2
	mov	rdi, r14
	mov	esi, ebx
	call	buffer$reserve
	mov	rdi, r14
	mov	esi, ebx
	call	buffer$append_nocopy
	mov	rdi, [r14+buffer_itself_ofs]
	mov	esi, ' '
	mov	edx, ebx
	call	memset32
	mov	rdi, [r14+buffer_itself_ofs]
	mov	rsi, [r12+buffer_itself_ofs]
	mov	edx, r13d

	; right aligned == offset
	mov	r10d, ebx
	sub	r10d, edx
	add	rdi, r10

	pop	r8
	test	r8d, r8d
	jnz	.nowrap_pwdchar

	call	memcpy
	; viewline is done, cursormap is next
	mov	rdi, r15
	mov	esi, ebx
	add	esi, 4					; +1 for cursormap, so that we can find a dangling one, but only for .nowrap
	call	buffer$reserve
	mov	rdi, r15
	mov	esi, ebx
	add	esi, 4					; +1 for cursormap, so that we can find a dangling one, but only for .nowrap
	call	buffer$append_nocopy
	mov	rdi, [r15+buffer_itself_ofs]
	mov	esi, 0xffffffff
	mov	edx, ebx
	call	memset32
	push	r13					; save the length again for the finale
	; next up, loop to set our offset positions for our cursormap, noting that we take _byte_ offsets, not character offsets
	mov	rdi, [r15+buffer_itself_ofs]
	mov	edx, r13d				; save the length of our source for the final placement

	mov	ecx, ebx
	sub	ecx, r13d

	xor	eax, eax
	test	r13d, r13d
	jz	.nowrap_cursormap_done
calign
.nowrap_cursormap:
	mov	[rdi+rcx], eax
	add	ecx, 4
	add	eax, 4
	sub	r13d, 4
	jnz	.nowrap_cursormap
calign
.nowrap_cursormap_done:
	pop	r13
	; since we are not doing wrap, we set the last position off the end as a valid cursor position
	mov	rsi, [rsp]
	mov	[rdi+rcx], edx				; last position off the end is a valid cursor position
	; one last call to lastviewline
	pop	rdi
	mov	rsi, r12
	mov	rdx, r14
	call	tui_text$nvlastviewline
	; done, dusted
	pop	r15 r14 r13 r12 rbx rbp
	epilog

end if





if used tui_text$nvcentercompose | defined include_everything
	; two arguments: rdi == our editor line buffer, rsi == tui_text object
falign
tui_text$nvcentercompose:
	prolog	tui_text$nvcentercompose
	breakpoint
	; TODO: someday when I am bored, see commentary atop
	epilog

end if

if used tui_text$nvjustifiedcompose | defined include_everything
	; two arguments: rdi == our editor line buffer, rsi == tui_text object
falign
tui_text$nvjustifiedcompose:
	prolog	tui_text$nvjustifiedcompose
	breakpoint
	; TODO: someday when I am bored, see commentary atop
	epilog

end if


if used tui_text$nvmaxx | defined include_everything
	; single argument in rdi: our tui_text object
	; returns the maximum width in characters in our lines list
falign
tui_text$nvmaxx:
	prolog	tui_text$nvmaxx
	xor	eax, eax
	mov	rdi, [rdi+tui_text_lines_ofs]
	mov	rsi, [rdi+_list_first_ofs]
	test	rsi, rsi
	jz	.ret
calign
.iter:
	mov	rdx, [rsi+_list_valueofs]
	mov	r8, [rdx+buffer_length_ofs]
	shr	r8, 2
	cmp	r8, rax
	cmova	rax, r8
	mov	rsi, [rsi+_list_nextofs]
	test	rsi, rsi
	jnz	.iter
	epilog
calign
.ret:
	epilog

end if

if used tui_text$nvheightchange | defined include_everything
	; single argument in rdi: our tui_text object
	; 
	; height changed, we have to adjust our topline and/or bottomline
	; this occurs when either the actual tui_height changes, or during the course
	; of editing, our height has changed
	; in other words: we are responsible for keeping topline/bottomline correct
falign
tui_text$nvheightchange:
	prolog	tui_text$nvheightchange
	; ok so... there are quite a few possible scenarios here
	; if we have more visible lines than our tui_height, then we need to shrink either the top or the bottom
	; depending on where the cursor is
	;
	; if we have less visible lines than our tui_height, we need to make sure all our lines are indeed visible
	; (e.g. we expanded during editing, and bottomline didn't get modified to reflect new lines, which is
	; our job here)
	;
	; so first up, or viewlines list _size_ has the actual total count of lines
	; we need to walk from topline to bottomline to determine how many we think are visible
	; and our tui_height of course is valid
	;	
	; we need to know which "half" our cursorline is sitting in too, depending on how our size is changing
	; will determine which side we truncate/add lines to
	;
	push	rbx
	mov	rbx, rdi

	mov	rdi, [rdi+tui_text_viewlines_ofs]
	mov	esi, [rdi+_list_size_ofs]

	; deal with the first very simple case: if heightlock is selected, we need to change our
	; actual tui_height to match our list size in esi
	cmp	dword [rbx+tui_text_heightlock_ofs], 0
	jne	.firstlast_heightlock

	; deal with the second simple case first, if our viewline count is <= tui_height
	cmp	esi, [rbx+tui_height_ofs]
	jbe	.firstlast

	; otherwise, we have more viewlines than we do display area
	; we need to find the cursorline's editorobject as we walk the viewlines
	mov	rdx, [rbx+tui_text_cursorline_ofs]	; cursorline is a list item
	mov	rcx, [rdx]				; and its value is a buffer
	mov	r8, [rcx+buffer_user_ofs]		; link back ot the editorline it references

	xor	ecx, ecx				; we'll use this as the cursorline entry
	xor	eax, eax
	mov	rdi, [rbx+tui_text_topline_ofs]
calign
.countvisible:
	; rdi is a list item, whose value is a buffer with a linkback
	mov	r9, [rdi]				
	cmp	r8, [r9+buffer_user_ofs]
	cmove	ecx, eax
	add	eax, 1
	cmp	rdi, [rbx+tui_text_bottomline_ofs]
	je	.countvisible_done
	mov	rdi, [rdi+_list_nextofs]
	jmp	.countvisible
calign
.countvisible_done:
	; ok so, eax is our viscount, esi is our actual linecount
	; ecx is the index in our viscount of where the cursor is at
	
	; so if our viscount is the same as our height, then we scroll both values together
	; and the direction we choose is dependent on which half the cursor is in
	mov	rdi, [rbx+tui_text_topline_ofs]
	mov	rsi, [rbx+tui_text_bottomline_ofs]

	mov	r8d, [rbx+tui_height_ofs]
	shr	r8d, 1					; half our height for cursor half comparisons

	cmp	eax, [rbx+tui_height_ofs]
	je	.heightvismatch
	jb	.growing
	; so, our viscount is more than our actual height, we need to shrink our topline/bottomline list
	; and which end we chop depends on where the cursor is
	; if the cursor is in the tophalf, shrink the bottom (bottom->prev til viscount and height the same)
	; if the cursor is in the bottomhalf, shrink the top (top->next til viscount and height the same)
	
	sub	eax, [rbx+tui_height_ofs]		; the difference

	cmp	ecx, r8d
	jb	.shrinking_tophalf
	; else, cursor is mid or bottomhalf
calign
.shrinking_bottomhalf:
	mov	rdi, [rdi+_list_nextofs]
	sub	eax, 1
	jnz	.shrinking_bottomhalf
	mov	[rbx+tui_text_topline_ofs], rdi
	pop	rbx
	epilog
calign
.shrinking_tophalf:
	mov	rsi, [rsi+_list_prevofs]
	sub	eax, 1
	jnz	.shrinking_tophalf
	mov	[rbx+tui_text_bottomline_ofs], rsi
	pop	rbx
	epilog

calign
.growing:
	; our viscount is less than our actual height, we need to grow our topline/bottomline list
	; and which end we grow depends on where the cursor is
	; if the cursor is in the tophalf, grow the bottom (bottom->next til viscount and height the same)
	; if the cursor is in the bottomhalf, grow the top (top->prev til viscount and height the same)

	mov	edx, [rbx+tui_height_ofs]
	sub	edx, eax				; the difference
	
	cmp	ecx, r8d
	jb	.growing_tophalf
	; cursor is mid or bottomhalf, grow the top
calign
.growing_bottomhalf:
	mov	rdi, [rdi+_list_prevofs]
	sub	edx, 1
	jnz	.growing_bottomhalf
	mov	[rbx+tui_text_topline_ofs], rdi
	pop	rbx
	epilog
calign
.growing_tophalf:
	; grow hte bottom
	mov	rsi, [rsi+_list_nextofs]
	sub	edx, 1
	jnz	.growing_tophalf
	mov	[rbx+tui_text_bottomline_ofs], rsi
	pop	rbx
	epilog

calign
.heightvismatch:
	; viscount is the same as our tui_height, and our viewlines is > than our height
	; if the cursor is in the tophalf, and there is no topline prev, direction = next
	; if the cursor is in the bottomhalf, and there is no bottomline next, direction = prev
	cmp	ecx, r8d
	jb	.heightvismatch_tophalf
	; else, cursor is mid or lower, so, scroll "up", meaning, both topline and bottomline
	; move to their next position, only if bottomline has a next, otherwise, prev it is
	cmp	qword [rsi+_list_nextofs], 0
	je	.heightvismatch_doprev
calign
.heightvismatch_donext:
	mov	rdi, [rdi+_list_nextofs]
	mov	rsi, [rsi+_list_nextofs]
	mov	[rbx+tui_text_topline_ofs], rdi
	mov	[rbx+tui_text_bottomline_ofs], rsi
	pop	rbx
	epilog
calign
.heightvismatch_tophalf:
	; cursor is upper, so scroll "down", meaning, both topline and bottomline move to
	; their prev position, only if topline has a prev, otherwise, next it is
	cmp	qword [rdi+_list_prevofs], 0
	je	.heightvismatch_donext
calign
.heightvismatch_doprev:
	mov	rdi, [rdi+_list_prevofs]
	mov	rsi, [rsi+_list_prevofs]
	mov	[rbx+tui_text_topline_ofs], rdi
	mov	[rbx+tui_text_bottomline_ofs], rsi
	pop	rbx
	epilog
calign
.firstlast:
	; our viewline count is less than or equal to our tui_height, just brute force
	; topline and bottomline to our viewline list's first and last
	mov	rdx, [rdi+_list_first_ofs]
	mov	rcx, [rdi+_list_last_ofs]
	mov	[rbx+tui_text_topline_ofs], rdx
	mov	[rbx+tui_text_bottomline_ofs], rcx
	pop	rbx
	epilog
calign
.firstlast_heightlock:
	; set topline/bottomline to our first and last, set our tui_height to esi
	; and do the necessary updating indicating our layout has changed

	; Hmmmmm, this needs more consideration as to what exactly we need to do here..

	mov	dword [rbx+tui_text_prevheight_ofs], esi		; make sure it doesn't get called twice
	mov	rdx, [rdi+_list_first_ofs]
	mov	rcx, [rdi+_list_last_ofs]
	mov	[rbx+tui_text_topline_ofs], rdx
	mov	[rbx+tui_text_bottomline_ofs], rcx
	mov	dword [rbx+tui_height_ofs], esi
	mov	rdi, rbx
	mov	rsi, [rbx]
	call	qword [rsi+tui_vsizechanged]
	mov	rdi, rbx
	mov	rsi, [rbx]
	call	qword [rsi+tui_vcalcbounds]
	mov	rdi, rbx
	mov	rsi, [rbx]
	call	qword [rsi+tui_vlayoutchanged]
	pop	rbx
	epilog


end if


if used tui_text$nvgettext | defined include_everything
	; single argument in rdi: our tui_text object
	; returns a _new_ heap$alloc'd string representation of what is in the editor lines
falign
tui_text$nvgettext:
	prolog	tui_text$nvgettext
	cmp	dword [rdi+tui_text_multiline_ofs], 0
	je	.singleline
	mov	rdi, [rdi+tui_text_lines_ofs]
	; there is _always_ at least one editor line
	push	rbx r12
	mov	rbx, rdi
	call	buffer$new
	mov	r12, rax
	mov	rdi, rbx
	mov	rsi, .addline
	mov	rdx, rax
	call	list$foreach_arg
	mov	rdi, [r12+buffer_itself_ofs]
	mov	rsi, [r12+buffer_length_ofs]
	call	string$from_utf32
	mov	rdi, r12
	mov	r12, rax
	call	buffer$destroy
	mov	rax, r12
	pop	r12 rbx
	epilog
calign
.addline:
	; passed in rdi == our editor line buffer, rsi == the buffer we are to append it to
	xchg	rdi, rsi
	mov	rdx, [rsi+buffer_length_ofs]
	mov	rsi, [rsi+buffer_itself_ofs]
	push	rdi
	call	buffer$append
	pop	rdi
	mov	esi, 10
	call	buffer$append_dword
	ret
calign
.singleline:
	; the first line only, no trailing LF
	mov	rdi, [rdi+tui_text_lines_ofs]
	mov	rdi, [_list_first]
	mov	rdi, [rdi]			; the line buffer itself
	mov	rsi, [rdi+buffer_length_ofs]
	mov	rdi, [rdi+buffer_itself_ofs]
	call	string$from_utf32
	epilog

end if

if used tui_text$nvsettext | defined include_everything
	; two arguments: rdi == our tui_text object, rsi == string (noting we do not assume ownership or anything)
falign
tui_text$nvsettext:
	prolog	tui_text$nvsettext
	push	rbx r12 r13
	mov	rbx, rdi
	mov	r12, rsi
	mov	rdi, [rdi+tui_text_lines_ofs]
	mov	rsi, .linedestroy
	call	list$clear
	mov	rdi, [rbx+tui_text_viewlines_ofs]
	mov	rsi, buffer$destroy
	call	list$clear
	mov	rdi, [rbx+tui_text_cursormap_ofs]
	mov	rsi, buffer$destroy
	call	list$clear
	; split our text by linefeeds, then create new buffers per line
	mov	rdi, r12
	mov	esi, 10
	call	string$split
	mov	r13, rax
	cmp	qword [rax+_list_size_ofs], 0
	je	.empty
	mov	rdi, rax
	mov	rsi, .addline
	mov	rdx, [rbx+tui_text_lines_ofs]
	call	list$foreach_arg
	mov	rdi, r13
	mov	rsi, heap$free
	call	list$clear
	mov	rdi, r13
	call	heap$free
	; clear topline/bottomline/cursorline, cursor, prev*, xscroll
	xor	ecx, ecx
	mov	[rbx+tui_text_topline_ofs], rcx
	mov	[rbx+tui_text_bottomline_ofs], rcx
	mov	[rbx+tui_text_cursorline_ofs], rcx
	mov	[rbx+tui_text_cursor_ofs], rcx
	mov	[rbx+tui_text_prevwidth_ofs], rcx	; includes prevheight as well
	mov	[rbx+tui_text_xscroll_ofs], ecx
	; last but not least, call our draw method
	; the draw method takes care of composition, and initial cursor positioning as well
	mov	rdi, rbx
	mov	rsi, [rbx]
	call	qword [rsi+tui_vdraw]
	
	pop	r13 r12 rbx
	epilog
calign
.empty:
	mov	rdi, r12
	mov	rsi, [rbx+tui_text_lines_ofs]
	call	.addline
	mov	rdi, r13
	call	heap$free
	; clear topline/bottomline/cursorline, cursor, prev*, xscroll
	xor	ecx, ecx
	mov	[rbx+tui_text_topline_ofs], rcx
	mov	[rbx+tui_text_bottomline_ofs], rcx
	mov	[rbx+tui_text_cursorline_ofs], rcx
	mov	[rbx+tui_text_cursor_ofs], rcx
	mov	[rbx+tui_text_prevwidth_ofs], rcx	; includes prevheight as well
	mov	[rbx+tui_text_xscroll_ofs], ecx
	; last but not least, call our draw method
	; the draw method takes care of composition, and initial cursor positioning as well
	mov	rdi, rbx
	mov	rsi, [rbx]
	call	qword [rsi+tui_vdraw]
	
	pop	r13 r12 rbx
	epilog

falign
.addline:
	; called with rdi == string, rsi == tui_text_lines_ofs list


	; the spot at our buffer's buffer_user_ofs+16 needs to be set to the list item itself, so they can be backreferenced
	; during newline/deleteline operations (without scanning for the editorline, etc)

	; because we decoupled the editor lines from the actual cursormap/viewlines (as we must, due to the way it all works)
	; we have to be able to, from a cursormap + editor line buffer entry all by itself, be able to get into the actual
	; editorlines list (but only when we hit enter, or backspace/delete a line altogether)

	push	rdi rsi
	call	buffer$new
	mov	rdi, [rsp]
	mov	rsi, rax
	mov	[rsp], rax
	call	list$push_back
	; push_back returns us the list item pointer itself, which we need to toss into our buffer at buffer_user_ofs+16
	; now we have to toss our string at [rsp+8] into our buffer at [rsp]
	mov	rdi, [rsp]
	mov	rsi, [rsp+8]

	mov	[rdi+buffer_user_ofs+16], rax		; see above commentary about why we have to do this
if string_bits = 32
	mov	rsi, [rsi]
	shl	rsi, 2
	push	rsi
	call	buffer$reserve
	pop	rdx
	mov	rdi, [rsp]
	mov	rsi, [rsp+8]
	add	rsi, 8
	call	buffer$append
else
	; otherwise, we need to determine how many utf32 codepoints there are, which may not be the utf16 length
	mov	rdi, rsi
	call	string$utf32_length
	shl	rax, 2
	mov	rdi, [rsp]
	mov	rsi, rax
	push	rax
	call	buffer$reserve
	pop	rsi
	mov	rdi, [rsp]
	call	buffer$append_nocopy
	mov	rdi, [rsp+8]
	mov	rsi, [rsp]
	mov	rsi, [rsi+buffer_itself_ofs]
	call	string$to_utf32
end if
	; create two lists hanging off our buffer at [rsp], so each editor line can maintain a list
	; of which viewlines and cursormap entries belong to it
	call	list$new
	mov	rdi, [rsp]
	mov	[rdi+buffer_user_ofs], rax
	call	list$new
	mov	rdi, [rsp]
	mov	[rdi+buffer_user_ofs+8], rax
	add	rsp, 16
	ret
falign
.linedestroy:
	; called with rdi == our editor line entry, which is a buffer, but since we have two lists attached to it
	; we need to clear them as well
	push	rbx
	mov	rbx, rdi
	mov	rdi, [rdi+buffer_user_ofs+8]
	xor	esi, esi
	call	list$clear
	mov	rdi, [rbx+buffer_user_ofs+8]
	call	heap$free
	mov	rdi, [rbx+buffer_user_ofs]
	xor	esi, esi
	call	list$clear
	mov	rdi, [rbx+buffer_user_ofs]
	call	heap$free
	mov	rdi, rbx
	call	buffer$destroy
	pop	rbx
	ret


end if


if used tui_text$nvsettingsupdate | defined include_everything
	; single argument in rdi: our tui_text object

	; burning purpose: see commentary atop, but restated here:
	; if, after you called tui_text$new_*, and you modified the default settings
	; _and_ you have non-empty initialtext, _and_ you have non-dynamic dimensions (fixed width/height)
	; then draw gets called once at time of new, but not again, so if your settings modify alignment, 
	; etc, then what you see before the first keypress or redraw is _not_ reflecting your settings
	; so, if you experience that, hehe, calling this will remedy it
falign
tui_text$nvsettingsupdate:
	prolog	tui_text$nvsettingsupdate
	mov	dword [rdi+tui_text_prevwidth_ofs], 0		; this forces a recomposition
	mov	rsi, [rdi]
	call	qword [rsi+tui_vdraw]
	epilog

end if
