06-18-2026, 02:14 PM
Another editor for NaaLaa 7, I was a bit nostalgic and old school and while reading and doing it in c lang I decide to give a try to NaaLaa. WIP
PHP Code:
' ============================================================
' NOVA EDITOR - A kilo-inspired text editor for N7/NaaLaa
' ============================================================
' Based on the "kilo" editor by Salvatore Sanfilippo
' Adapted to N7/NaaLaa's windowed graphics model
'
' Author: Nelson
'
' Features:
' - Multi-line text editing with syntax highlighting
' - N7/NaaLaa keyword highlighting
' - File open / save
' - Incremental search with Ctrl+F (arrows = next/prev)
' - Status bar (filename, dirty flag, line/col)
' - Vertical + horizontal scrolling
' - Mouse click to position cursor
'
' Controls:
' Arrows Move cursor Page Up/Down Scroll page
' Home / End Start / end of line Ctrl+Home/End First / last line
' Backspace Delete before cursor Delete Delete after cursor
' Enter Insert newline Tab Insert spaces
' Ctrl+S Save Ctrl+Q Quit (x3 if dirty)
' Ctrl+F Find (ESC cancel) Ctrl+O Open file prompt
' Ctrl+C Copy line Ctrl+X Cut line
' Ctrl+V Paste clipboard
' Ctrl+R Compile + run current file
' Ctrl+T Toggle theme
' ============================================================
#win32
w = screenw()*2/3
h = screenh()*2/3
set window "Nova Editor 0.1", w, h, false, 0, min(w, 640), min(h, 480)
set redraw off
' ============================================================
' CONSTANTS
' ============================================================
constant NE_VERSION = "0.1.0"
constant NE_TAB_SIZE = 4
constant NE_QUIT_TIMES = 3
constant NE_SCROLL_AREA_W = 0
constant NE_AUTOSAVE_FILE = "nova_editor.autosave"
constant NE_AUTOSAVE_MS = 1500
' Highlight type IDs
constant HL_NORMAL = 0
constant HL_COMMENT = 1
constant HL_KEYWORD = 2
constant HL_BUILTIN = 3
constant HL_STRING = 4
constant HL_NUMBER = 5
constant HL_MATCH = 6
' Key repeat timing (ms)
constant KR_DELAY = 400
constant KR_RATE = 45
' ============================================================
' GLOBAL STATE
' ============================================================
visible neFont
visible neCharW = 10
visible neCharH = 18
visible neScrRows = 0
visible neScrCols = 0
visible neAreaH = 0 ' pixel height of text area
visible neTextX = 0 ' x offset where text content starts (gutter aware)
visible neShowLineNumbers = true
visible neGutterChars = 0
visible neRows = [] ' array of {text: string}
visible neFilename = ""
visible neDocName = "[New File]"
visible neDirty = false
visible neStatusMsg = ""
visible neStatusMsgTime = 0
visible neCx = 0, neCy = 0 ' cursor col, row (0-based)
visible neRowOff = 0, neColOff = 0 ' scroll offsets (rows, cols)
' Selection state
visible neSelActive = false
visible neSelAX = 0, neSelAY = 0 ' selection anchor
visible neMouseSelecting = false
' Search state
visible neSearchQuery = ""
visible neSearchRow = -1
visible neSearchCol = -1
visible neSearchActive = false
visible neSearchBuf = ""
' Quit confirmation counter
visible neQuitCount = NE_QUIT_TIMES
' Keyword tables
visible neKw1 = [] ' control flow (orange)
visible neKw2 = [] ' builtins/types (cyan)
' Key repeat
visible krKey = -1
visible krFirstTime = 0
visible krLastTime = 0
visible krFirstByKey = []
visible krLastByKey = []
' Caret blink
visible neCaretBlink = 0
visible neLastAutosave = 0
' Open-file prompt mode
visible neOpenActive = false
visible neOpenBuf = ""
visible neOpenPos = 0
' Save-as prompt mode
visible neSaveActive = false
visible neSaveBuf = ""
visible neSavePos = 0
' Theme state
visible neTheme = 0
visible neThemePickerActive = false
visible neThemePick = 0
visible neThemePickOrig = 0
' Exit confirmation state
visible neExitConfirmActive = false
visible neExitAfterSave = false
visible neRunAfterSave = false
' ============================================================
' KEYWORD SETUP
' ============================================================
function ne_init_keywords()
kw1 = ["if", "then", "else", "elseif", "endif",
"while", "wend", "do", "until",
"for", "to", "step", "next",
"function", "endfunc", "return",
"break", "and", "or", "not",
"true", "false",
"select", "case", "endsel", "foreach", "in",
"visible", "constant", "include"]
for i = 0 to sizeof(kw1) - 1
neKw1[kw1[i]] = true
next
kw2 = ["set", "draw", "cls", "redraw",
"write", "wln", "pln", "rln", "center",
"int", "str", "float", "asc", "chr",
"len", "left", "right", "mid", "instr",
"lower", "upper", "trim", "replace", "split",
"sizeof", "typeof", "key", "copy", "fill", "dim",
"insert", "free", "assert",
"createfont", "fwidth", "fheight",
"keydown", "inkey", "mousex", "mousey", "mousebutton",
"clock", "fwait", "wait", "datetime",
"openfile", "createfile", "frln", "fread", "fwrite",
"sin", "cos", "tan", "atan2", "sqr", "pow",
"abs", "min", "max", "floor", "ceil", "rnd",
"width", "height", "screenw", "screenh", "primary",
"args", "end", "log", "exp", "window"]
for i = 0 to sizeof(kw2) - 1
neKw2[kw2[i]] = true
next
endfunc
' ============================================================
' INITIALISATION
' ============================================================
function ne_init()
neFont = createfont("consolas", 16, false, false, false, true)
set font neFont
neCharW = fwidth(neFont, " ")
neCharH = fheight(neFont)
' Two lines at bottom: status bar + message/prompt bar
neAreaH = height(primary) - neCharH * 2
neScrRows = int(neAreaH / neCharH)
neRows = [[text: ""]]
ne_update_layout()
ne_init_keywords()
ne_set_status("Ctrl-S=Save | Ctrl-R=Build+Run | Ctrl-Q=Safe quit")
endfunc
function ne_theme_name()
if neTheme = 0
return "Midnight"
else
return "Slate Day"
endif
endfunc
function ne_theme_name_by_index(idx)
if idx = 0
return "Midnight"
else
return "Slate Day"
endif
endfunc
function ne_request_exit()
if neDirty
neExitConfirmActive = true
neExitAfterSave = false
else
end
endif
endfunc
function ne_scroll_max_off()
return max(sizeof(neRows) - neScrRows, 0)
endfunc
function ne_set_rowoff(newOff)
maxOff = ne_scroll_max_off()
if newOff < 0 then newOff = 0
if newOff > maxOff then newOff = maxOff
delta = newOff - neRowOff
neRowOff = newOff
if delta <> 0
neCy = neCy + delta
if neCy < 0 then neCy = 0
if neCy >= sizeof(neRows) then neCy = sizeof(neRows) - 1
endif
endfunc
function ne_hit_close_button(mx, my)
statusY = neScrRows * neCharH
bx = width(primary) - 22
by = statusY + 1
bh = neCharH - 2
return mx >= bx and mx < bx + 18 and my >= by and my < by + bh
endfunc
function ne_digits(n)
if n < 10 then return 1
d = 1
v = n
while v >= 10
v = int(v / 10)
d = d + 1
wend
return d
endfunc
function ne_update_layout()
neAreaH = height(primary) - neCharH * 2
neScrRows = int(neAreaH / neCharH)
if neShowLineNumbers
neGutterChars = ne_digits(max(sizeof(neRows), 1)) + 2
neTextX = neGutterChars * neCharW
else
neGutterChars = 0
neTextX = 0
endif
neScrCols = int((width(primary) - neTextX - NE_SCROLL_AREA_W) / neCharW)
if neScrCols < 1 then neScrCols = 1
endfunc
' ============================================================
' ROW / TEXT OPERATIONS (mirrors kilo's erow helpers)
' ============================================================
function ne_row_insert(at, txt)
insert neRows, at, [text: txt]
endfunc
function ne_row_delete(at)
free key neRows, at
endfunc
function ne_row_insert_char(row, at, ch)
t = neRows[row].text
neRows[row].text = left(t, at) + ch + right(t, at)
endfunc
function ne_row_delete_char(row, at)
t = neRows[row].text
neRows[row].text = left(t, at) + right(t, at + 1)
endfunc
function ne_row_append(row, s)
neRows[row].text = neRows[row].text + s
endfunc
function ne_clear_selection()
neSelActive = false
endfunc
function ne_has_selection()
if not neSelActive then return false
return not (neSelAY = neCy and neSelAX = neCx)
endfunc
function ne_get_selection_bounds()
if neSelAY < neCy
return [rs: neSelAY, cs: neSelAX, re: neCy, ce: neCx]
endif
if neSelAY > neCy
return [rs: neCy, cs: neCx, re: neSelAY, ce: neSelAX]
endif
if neSelAX <= neCx
return [rs: neCy, cs: neSelAX, re: neCy, ce: neCx]
else
return [rs: neCy, cs: neCx, re: neCy, ce: neSelAX]
endif
endfunc
function ne_delete_selection()
if not ne_has_selection() then return false
b = ne_get_selection_bounds()
if b.rs = b.re
ln = neRows[b.rs].text
neRows[b.rs].text = left(ln, b.cs) + right(ln, b.ce)
else
firstPart = left(neRows[b.rs].text, b.cs)
lastPart = right(neRows[b.re].text, b.ce)
neRows[b.rs].text = firstPart + lastPart
i = b.rs + 1
while i <= b.re
ne_row_delete(b.rs + 1)
i = i + 1
wend
endif
neCy = b.rs
neCx = b.cs
ne_clear_selection()
neDirty = true
return true
endfunc
function ne_get_selection_text()
if not ne_has_selection() then return ""
b = ne_get_selection_bounds()
if b.rs = b.re
return mid(neRows[b.rs].text, b.cs, b.ce - b.cs)
endif
s = right(neRows[b.rs].text, b.cs)
r = b.rs + 1
while r < b.re
s = s + chr(10) + neRows[r].text
r = r + 1
wend
s = s + chr(10) + left(neRows[b.re].text, b.ce)
return s
endfunc
function ne_select_all()
if sizeof(neRows) = 0 then return
neSelAY = 0
neSelAX = 0
neCy = sizeof(neRows) - 1
neCx = len(neRows[neCy].text)
neSelActive = true
endfunc
function ne_prepare_move_selection()
if keydown(KEY_SHIFT)
if not neSelActive
neSelAY = neCy
neSelAX = neCx
neSelActive = true
endif
return true
endif
if neSelActive then ne_clear_selection()
return false
endfunc
function ne_finalize_move_selection(useShift)
if useShift
if neSelAY = neCy and neSelAX = neCx
neSelActive = false
else
neSelActive = true
endif
endif
endfunc
' ============================================================
' EDITOR OPERATIONS (mirrors kilo's editor operations)
' ============================================================
function ne_insert_char(ch)
if neCy >= sizeof(neRows) then ne_row_insert(sizeof(neRows), "")
if ne_has_selection() then ne_delete_selection()
ne_row_insert_char(neCy, neCx, ch)
neCx = neCx + 1
neDirty = true
endfunc
function ne_insert_newline()
if ne_has_selection() then ne_delete_selection()
curText = neRows[neCy].text
restOfLine = right(curText, neCx)
neRows[neCy].text = left(curText, neCx)
' Auto-indent: copy leading whitespace from current line.
' If none is found, fallback to previous line indentation.
indent = ""
i = 0
while i < len(curText)
ch = mid(curText, i, 1)
ca = asc(ch)
if ca = 32 or ca = 9
indent = indent + ch
i = i + 1
else
break
endif
wend
if len(indent) = 0 and neCy > 0
prev = neRows[neCy - 1].text
i = 0
while i < len(prev)
ch = mid(prev, i, 1)
ca = asc(ch)
if ca = 32 or ca = 9
indent = indent + ch
i = i + 1
else
break
endif
wend
endif
ne_row_insert(neCy + 1, indent + restOfLine)
neCy = neCy + 1
neCx = len(indent)
neDirty = true
endfunc
function ne_delete_char()
' Backspace: delete character BEFORE cursor
if ne_has_selection()
ne_delete_selection()
return
endif
if neCx = 0
if neCy = 0 then return
prevLen = len(neRows[neCy - 1].text)
ne_row_append(neCy - 1, neRows[neCy].text)
ne_row_delete(neCy)
neCy = neCy - 1
neCx = prevLen
else
ne_row_delete_char(neCy, neCx - 1)
neCx = neCx - 1
endif
neDirty = true
endfunc
function ne_delete_forward()
' Delete key: delete character AFTER cursor
if ne_has_selection()
ne_delete_selection()
return
endif
if neCy >= sizeof(neRows) then return
rowLen = len(neRows[neCy].text)
if neCx >= rowLen
if neCy >= sizeof(neRows) - 1 then return
ne_row_append(neCy, neRows[neCy + 1].text)
ne_row_delete(neCy + 1)
else
ne_row_delete_char(neCy, neCx)
endif
neDirty = true
endfunc
function ne_copy_line()
if ne_has_selection()
set clipboard ne_get_selection_text()
ne_set_status("Copied selection")
return
endif
if neCy >= 0 and neCy < sizeof(neRows)
set clipboard neRows[neCy].text
ne_set_status("Copied line " + (neCy + 1))
endif
endfunc
function ne_cut_line()
if ne_has_selection()
set clipboard ne_get_selection_text()
ne_delete_selection()
ne_set_status("Cut selection")
return
endif
if neCy < 0 or neCy >= sizeof(neRows) then return
set clipboard neRows[neCy].text
if sizeof(neRows) = 1
neRows[0].text = ""
neCy = 0
neCx = 0
else
ne_row_delete(neCy)
if neCy >= sizeof(neRows) then neCy = sizeof(neRows) - 1
if neCy < 0 then neCy = 0
if neCx > len(neRows[neCy].text) then neCx = len(neRows[neCy].text)
endif
neDirty = true
ne_set_status("Cut line " + (neCy + 1))
endfunc
function ne_paste_clipboard()
txt = clipboard()
if not typeof(txt) or len(txt) = 0
ne_set_status("Clipboard is empty")
return
endif
if ne_has_selection() then ne_delete_selection()
txt = replace(txt, chr(13), "")
parts = split(txt, chr(10))
if sizeof(parts) <= 1
i = 0
while i < len(txt)
ne_insert_char(mid(txt, i, 1))
i = i + 1
wend
else
cur = neRows[neCy].text
leftPart = left(cur, neCx)
rightPart = right(cur, neCx)
neRows[neCy].text = leftPart + parts[0]
i = 1
while i < sizeof(parts) - 1
ne_row_insert(neCy + i, parts[i])
i = i + 1
wend
ne_row_insert(neCy + sizeof(parts) - 1, parts[sizeof(parts) - 1] + rightPart)
neCy = neCy + sizeof(parts) - 1
neCx = len(parts[sizeof(parts) - 1])
neDirty = true
endif
ne_set_status("Pasted clipboard")
endfunc
' ============================================================
' FILE I/O (mirrors kilo's editorOpen / editorSave)
' ============================================================
function ne_set_filename(filename)
neFilename = filename
' Extract short name for title bar
i = len(filename) - 1
while i >= 0
c = mid(filename, i, 1)
ca = asc(c)
if ca = 47 or ca = 92 then break ' '/' or '\\'
i = i - 1
wend
neDocName = right(filename, i + 1)
endfunc
function ne_open(filename)
f = openfile(filename)
if not typeof(f)
ne_set_status("Error: cannot open '" + filename + "'")
return
endif
neRows = []
ln = frln(f)
while typeof(ln)
ln = replace(ln, chr(13), "") ' strip CR
neRows[sizeof(neRows)] = [text: ln]
ln = frln(f)
wend
free file f
if sizeof(neRows) = 0 then neRows = [[text: ""]]
ne_set_filename(filename)
neCx = 0 ; neCy = 0
neRowOff = 0 ; neColOff = 0
neDirty = false
ne_autosave_clear()
ne_update_layout()
ne_set_status("Loaded: " + neDocName + " (" + sizeof(neRows) + " lines)")
endfunc
function ne_path_dir(filename)
i = len(filename) - 1
while i >= 0
c = mid(filename, i, 1)
ca = asc(c)
if ca = 47 or ca = 92 then break
i = i - 1
wend
if i < 0 then return "."
return left(filename, i + 1)
endfunc
function ne_source_to_exe(filename)
i = len(filename) - 1
lastDot = -1
lastSep = -1
while i >= 0
c = mid(filename, i, 1)
ca = asc(c)
if ca = 47 or ca = 92
lastSep = i
break
endif
if ca = 46 and lastDot < 0
lastDot = i
endif
i = i - 1
wend
if lastDot > lastSep
return left(filename, lastDot) + ".exe"
endif
return filename + ".exe"
endfunc
function ne_compile_current()
if neFilename = ""
ne_set_status("Save the file first, then run Ctrl-R")
return false
endif
q = chr(34)
cmd = q + q + "n7.exe" + q + " " + q + neFilename + q + q
result = system(cmd)
if instr(lower(result), "n7b: success") >= 0
ne_set_status("Build successful")
return true
endif
lines = split(replace(result, chr(13), ""), chr(10))
if sizeof(lines) > 0 and lines[0] <> ""
ne_set_status("Build failed: " + lines[0])
else
ne_set_status("Build failed")
endif
return false
endfunc
function ne_run_compiled_exe()
if neFilename = "" then return false
exeFile = ne_source_to_exe(neFilename)
if not exists(exeFile)
ne_set_status("Executable not found")
return false
endif
q = chr(34)
cmd = "cd /D " + q + ne_path_dir(exeFile) + q + " && start " + q + q + " " + q + exeFile + q
system(cmd)
ne_set_status("Running: " + neDocName)
return true
endfunc
function ne_compile_and_run_saved()
if ne_compile_current()
ne_run_compiled_exe()
endif
endfunc
function ne_compile_and_run()
if neFilename = "" or neDirty
neRunAfterSave = true
ne_save()
return
endif
ne_compile_and_run_saved()
endfunc
function ne_save()
if neFilename = ""
neSaveActive = true
if neDocName = "[New File]"
neSaveBuf = "untitled.n7"
else
neSaveBuf = neDocName
endif
neSavePos = len(neSaveBuf)
return
endif
f = createfile(neFilename)
if not typeof(f)
ne_set_status("Error: cannot save '" + neFilename + "'")
return
endif
for i = 0 to sizeof(neRows) - 1
wln file f, neRows[i].text
next
free file f
neDirty = false
ne_autosave_clear()
ne_set_status("Saved '" + neDocName + "' (" + sizeof(neRows) + " lines)")
if neRunAfterSave
neRunAfterSave = false
ne_compile_and_run_saved()
endif
if neExitAfterSave
end
endif
endfunc
function ne_autosave_clear()
f = createfile(NE_AUTOSAVE_FILE)
if not typeof(f) then return
wln file f, "NOVA_AUTOSAVE_V1"
wln file f, "CLEAN"
free file f
endfunc
function ne_autosave_write()
if not neDirty then return
f = createfile(NE_AUTOSAVE_FILE)
if not typeof(f) then return
wln file f, "NOVA_AUTOSAVE_V1"
wln file f, "DIRTY"
wln file f, neFilename
wln file f, neDocName
for i = 0 to sizeof(neRows) - 1
wln file f, neRows[i].text
next
free file f
neLastAutosave = clock()
endfunc
function ne_tick_autosave()
if not neDirty then return
t = clock()
if t - neLastAutosave >= NE_AUTOSAVE_MS
ne_autosave_write()
endif
endfunc
function ne_try_restore_autosave()
f = openfile(NE_AUTOSAVE_FILE)
if not typeof(f) then return false
sig = frln(f)
state = frln(f)
if sig <> "NOVA_AUTOSAVE_V1" or state <> "DIRTY"
free file f
return false
endif
savedFilename = frln(f)
savedDocName = frln(f)
recRows = []
ln = frln(f)
while typeof(ln)
ln = replace(ln, chr(13), "")
recRows[sizeof(recRows)] = [text: ln]
ln = frln(f)
wend
free file f
if sizeof(recRows) = 0
recRows = [[text: ""]]
endif
neRows = recRows
if savedFilename <> ""
ne_set_filename(savedFilename)
else
neFilename = ""
if savedDocName <> ""
neDocName = savedDocName
else
neDocName = "[Recovered]"
endif
endif
neCx = 0
neCy = 0
neRowOff = 0
neColOff = 0
neDirty = true
ne_update_layout()
ne_set_status("Recovered unsaved changes from autosave")
neLastAutosave = clock()
return true
endfunc
' ============================================================
' SYNTAX HIGHLIGHTING (mirrors kilo's editorUpdateSyntax)
' ============================================================
function is_sep(ch)
if ch = "" then return true
c = asc(ch)
return c = 32 or c = 9 or c = 34 or
instr(",.()+-/*=~%<>[];:{}", ch) >= 0
endfunc
function is_digit_ch(ch)
if ch = "" then return false
c = asc(ch)
return c >= asc("0") and c <= asc("9")
endfunc
function is_alpha_ch(ch)
if ch = "" then return false
c = asc(ch)
return (c >= asc("a") and c <= asc("z")) or
(c >= asc("A") and c <= asc("Z")) or c = asc("_")
endfunc
' Returns a numeric array where hl[i] = highlight type for char i
function ne_compute_hl(text, matchStart, matchEnd)
tlen = len(text)
if tlen = 0 then return []
hl = fill(HL_NORMAL, tlen)
in_string = 0 ' 0=none, 34=double-quote
in_comment = false
i = 0
while i < tlen
ch = mid(text, i, 1)
ca = asc(ch)
if in_comment
hl[i] = HL_COMMENT
i = i + 1
elseif in_string <> 0
hl[i] = HL_STRING
if ca = in_string
in_string = 0 ' close string
endif
i = i + 1
elseif ca = asc("'")
' Single-quote starts a comment in N7
while i < tlen
hl[i] = HL_COMMENT
i = i + 1
wend
elseif ca = 34
' Double-quote string
in_string = 34
hl[i] = HL_STRING
i = i + 1
elseif is_digit_ch(ch) and (i = 0 or is_sep(mid(text, i - 1, 1)))
' Number literal
j = i
while j < tlen
c2 = mid(text, j, 1)
if is_digit_ch(c2) or c2 = "."
hl[j] = HL_NUMBER
j = j + 1
else
break
endif
wend
i = j
elseif is_alpha_ch(ch) and (i = 0 or is_sep(mid(text, i - 1, 1)))
' Identifier or keyword
w = ""
j = i
while j < tlen
c2 = mid(text, j, 1)
if is_alpha_ch(c2) or is_digit_ch(c2)
w = w + c2
j = j + 1
else
break
endif
wend
wl = lower(w)
if key(neKw1, wl)
k2 = i
while k2 < j
hl[k2] = HL_KEYWORD
k2 = k2 + 1
wend
elseif key(neKw2, wl)
k2 = i
while k2 < j
hl[k2] = HL_BUILTIN
k2 = k2 + 1
wend
endif
i = j
else
i = i + 1
endif
wend
' Overlay search match highlight
if matchStart >= 0
k = matchStart
while k < matchEnd and k < tlen
if k >= 0 then hl[k] = HL_MATCH
k = k + 1
wend
endif
return hl
endfunc
function ne_hl_color(hlType)
if hlType = HL_COMMENT
set color 100, 200, 100 ' green
elseif hlType = HL_KEYWORD
set color 220, 160, 80 ' orange
elseif hlType = HL_BUILTIN
set color 80, 180, 220 ' cyan
elseif hlType = HL_STRING
set color 210, 110, 110 ' soft red
elseif hlType = HL_NUMBER
set color 180, 150, 230 ' lavender
elseif hlType = HL_MATCH
set color 0, 0, 0 ' black on yellow bg
else
set color 200, 200, 200 ' light grey (normal)
endif
endfunc
' ============================================================
' SEARCH (mirrors kilo's editorFind)
' ============================================================
function ne_find_next(query, startRow, startCol, dir)
' Search forward (dir=1) or backward (dir=-1)
numRows = sizeof(neRows)
if numRows = 0 then return false
ql = lower(query)
row = startRow
col = startCol + dir
i = 0
while i < numRows
if row < 0 then row = numRows - 1
if row >= numRows then row = 0
text = lower(neRows[row].text)
if dir = 1
pos = instr(text, ql, col)
else
' Reverse: find last occurrence before col
pos = -1
p = 0
while true
p2 = instr(text, ql, p)
if p2 < 0 then break
if col < 0 or p2 < col
pos = p2
p = p2 + 1
else
break
endif
wend
endif
if pos >= 0
neSearchRow = row
neSearchCol = pos
neCy = row
neCx = pos + len(query)
' Centre vertically
neRowOff = row - neScrRows / 2
if neRowOff < 0 then neRowOff = 0
return true
endif
row = row + dir
if dir = 1
col = 0
else
col = 99999
endif
i = i + 1
wend
neSearchRow = -1
neSearchCol = -1
return false
endfunc
' ============================================================
' SCROLLING (mirrors kilo's editorScroll)
' ============================================================
function ne_scroll()
' Clamp cursor to valid range
numRows = sizeof(neRows)
if neCy >= numRows
neCy = numRows - 1
if neCy < 0 then neCy = 0
endif
if neCy < numRows
rowLen = len(neRows[neCy].text)
if neCx > rowLen then neCx = rowLen
endif
' Vertical
if neCy < neRowOff then neRowOff = neCy
if neCy >= neRowOff + neScrRows
neRowOff = neCy - neScrRows + 1
endif
' Horizontal
if neCx < neColOff then neColOff = neCx
if neCx >= neColOff + neScrCols - 1
neColOff = neCx - neScrCols + 2
endif
endfunc
' ============================================================
' RENDERING (mirrors kilo's editorRefreshScreen)
' ============================================================
function ne_draw_rows()
if neTheme = 0
bgR = 24
bgG = 24
bgB = 32
gutterBgR = 16
gutterBgG = 20
gutterBgB = 28
gutterEdgeR = 52
gutterEdgeG = 62
gutterEdgeB = 82
tildeR = 80
tildeG = 80
tildeB = 100
lnActiveR = 170
lnActiveG = 190
lnActiveB = 230
lnNormR = 98
lnNormG = 112
lnNormB = 144
activeGutterR = 24
activeGutterG = 28
activeGutterB = 38
activeLineR = 30
activeLineG = 36
activeLineB = 48
activeEdgeR = 120
activeEdgeG = 150
activeEdgeB = 210
selBgR = 90
selBgG = 120
selBgB = 170
selFgR = 238
selFgG = 244
selFgB = 255
matchBgR = 180
matchBgG = 160
matchBgB = 0
navBtnBgR = 54
navBtnBgG = 66
navBtnBgB = 88
navBtnEdgeR = 90
navBtnEdgeG = 112
navBtnEdgeB = 150
navBtnTextR = 232
navBtnTextG = 238
navBtnTextB = 250
else
bgR = 238
bgG = 242
bgB = 248
gutterBgR = 224
gutterBgG = 231
gutterBgB = 241
gutterEdgeR = 180
gutterEdgeG = 191
gutterEdgeB = 210
tildeR = 130
tildeG = 136
tildeB = 154
lnActiveR = 56
lnActiveG = 76
lnActiveB = 118
lnNormR = 112
lnNormG = 122
lnNormB = 142
activeGutterR = 212
activeGutterG = 222
activeGutterB = 238
activeLineR = 210
activeLineG = 223
activeLineB = 242
activeEdgeR = 76
activeEdgeG = 106
activeEdgeB = 162
selBgR = 120
selBgG = 156
selBgB = 214
selFgR = 8
selFgG = 16
selFgB = 34
matchBgR = 232
matchBgG = 203
matchBgB = 92
navBtnBgR = 198
navBtnBgG = 212
navBtnBgB = 235
navBtnEdgeR = 140
navBtnEdgeG = 156
navBtnEdgeB = 186
navBtnTextR = 24
navBtnTextG = 36
navBtnTextB = 66
endif
' Themed background for text area
set color bgR, bgG, bgB
draw rect 0, 0, width(primary), neAreaH, true
if neShowLineNumbers
set color gutterBgR, gutterBgG, gutterBgB
draw rect 0, 0, neTextX, neAreaH, true
set color gutterEdgeR, gutterEdgeG, gutterEdgeB
draw rect neTextX - 1, 0, 1, neAreaH, true
endif
contentW = width(primary) - neTextX - NE_SCROLL_AREA_W
if contentW < 1 then contentW = 1
contentRight = neTextX + contentW
set font neFont
y = 0
row = neRowOff
while y < neScrRows
screenY = y * neCharH
' Subtle active-line tint to improve cursor tracking
if row = neCy and row < sizeof(neRows)
if neShowLineNumbers
set color activeGutterR, activeGutterG, activeGutterB
draw rect 0, screenY, neTextX, neCharH, true
endif
set color activeLineR, activeLineG, activeLineB
draw rect neTextX, screenY, contentW, neCharH, true
' Selected-line edge markers (left/right) for fast visual tracking
set color activeEdgeR, activeEdgeG, activeEdgeB
draw rect neTextX, screenY, 1, neCharH, true
draw rect contentRight - 2, screenY, 2, neCharH, true
endif
if row >= sizeof(neRows)
' Past end-of-file tilde
set color tildeR, tildeG, tildeB
set caret neTextX, screenY
write "~"
else
if neShowLineNumbers
lnTxt = row + 1
lx = neTextX - neCharW - fwidth(neFont, lnTxt)
if row = neCy
set color lnActiveR, lnActiveG, lnActiveB
else
set color lnNormR, lnNormG, lnNormB
endif
set caret lx, screenY
write lnTxt
endif
text = neRows[row].text
tlen = len(text)
if ne_has_selection()
b = ne_get_selection_bounds()
if row >= b.rs and row <= b.re
selStart = -1
selEnd = -1
if b.rs = b.re
selStart = b.cs
selEnd = b.ce
elseif row = b.rs
selStart = b.cs
selEnd = tlen
elseif row = b.re
selStart = 0
selEnd = b.ce
else
selStart = 0
selEnd = tlen
endif
if selEnd > selStart
vs = max(selStart, neColOff)
ve = min(selEnd, neColOff + neScrCols)
if ve > vs
set color selBgR, selBgG, selBgB
draw rect neTextX + (vs - neColOff) * neCharW,
screenY,
(ve - vs) * neCharW,
neCharH,
true
endif
endif
endif
endif
' Determine match positions for this row
mStart = -1
mEnd = -1
if neSearchQuery <> "" and row = neSearchRow
mStart = neSearchCol
mEnd = neSearchCol + len(neSearchQuery)
endif
hl = ne_compute_hl(text, mStart, mEnd)
' Render segments of consecutive same-highlight characters
col = neColOff
x = 0
while col < tlen and x < neScrCols
segStart = col
segHlType = hl[col]
' Extend segment while same highlight type
endCol = col
while endCol < tlen and endCol < neColOff + neScrCols and hl[endCol] = segHlType
endCol = endCol + 1
wend
segText = mid(text, segStart, endCol - segStart)
' Draw match background rectangle
if segHlType = HL_MATCH
set color matchBgR, matchBgG, matchBgB
draw rect neTextX + x * neCharW, screenY, len(segText) * neCharW, neCharH, true
endif
ne_hl_color(segHlType)
set caret neTextX + x * neCharW, screenY
write segText
x = x + (endCol - segStart)
col = endCol
wend
' Repaint selected text in high-contrast color on top of selection background
if ne_has_selection()
b2 = ne_get_selection_bounds()
if row >= b2.rs and row <= b2.re
selStart2 = -1
selEnd2 = -1
if b2.rs = b2.re
selStart2 = b2.cs
selEnd2 = b2.ce
elseif row = b2.rs
selStart2 = b2.cs
selEnd2 = tlen
elseif row = b2.re
selStart2 = 0
selEnd2 = b2.ce
else
selStart2 = 0
selEnd2 = tlen
endif
if selEnd2 > selStart2
vs2 = max(selStart2, neColOff)
ve2 = min(selEnd2, neColOff + neScrCols)
if ve2 > vs2
set color selFgR, selFgG, selFgB
set caret neTextX + (vs2 - neColOff) * neCharW, screenY
write mid(text, vs2, ve2 - vs2)
endif
endif
endif
endif
endif
y = y + 1
row = row + 1
wend
endfunc
function ne_draw_cursor()
' Draw blinking vertical cursor bar
neCaretBlink = (neCaretBlink + 1) % 60
if neCaretBlink > 30 then return ' blink off
cx = neTextX + (neCx - neColOff) * neCharW
cy = (neCy - neRowOff) * neCharH
if cx >= neTextX and cx < width(primary) - NE_SCROLL_AREA_W and cy >= 0 and cy < neAreaH
if neTheme = 0
set color 220, 220, 220
else
set color 28, 48, 92
endif
draw rect cx, cy, 2, neCharH, true
endif
endfunc
function ne_draw_status()
' Status bar — mimics kilo's two-sided status line
statusY = neScrRows * neCharH
if neTheme = 0
set color 60, 100, 180
else
set color 111, 140, 192
endif
draw rect 0, statusY, width(primary), neCharH, true
' Left: filename + dirty indicator
if neFilename = ""
leftStr = "[New File]"
else
leftStr = neDocName
endif
if neDirty then leftStr = leftStr + " [modified]"
' Right: line / column / total
rightStr = "Ln " + (neCy + 1) + "/" + sizeof(neRows) + " Col " + (neCx + 1)
set font neFont
set color 255, 255, 255
set caret 6, statusY
write leftStr
rx = width(primary) - fwidth(neFont, rightStr) - 30
set caret rx, statusY
write rightStr
' Close button (in-editor exit request)
bx = width(primary) - 22
by = statusY + 1
bh = neCharH - 2
if neTheme = 0
set color 180, 78, 78
else
set color 194, 92, 92
endif
draw rect bx, by, 18, bh, true
set color 245, 245, 245
set caret bx + 5, statusY
write "X"
endfunc
function ne_draw_message()
msgY = (neScrRows + 1) * neCharH
if neTheme = 0
set color 16, 16, 24
else
set color 228, 235, 246
endif
draw rect 0, msgY, width(primary), neCharH, true
set font neFont
if neSaveActive
set color 200, 200, 80
set caret 6, msgY
write "Save as: " + left(neSaveBuf, neSavePos) + "_" + right(neSaveBuf, neSavePos)
elseif neOpenActive
set color 200, 200, 80
set caret 6, msgY
write "Open file: " + left(neOpenBuf, neOpenPos) + "_" + right(neOpenBuf, neOpenPos)
elseif neSearchActive
set color 200, 200, 80
set caret 6, msgY
write "Search: " + neSearchBuf + "_ (ESC cancel | Enter confirm | Arrows next/prev)"
elseif neThemePickerActive
set color 200, 200, 80
set caret 6, msgY
write "Theme [1..2]: 1 Midnight | 2 Slate Day Selected: " + (neThemePick + 1) + " " + ne_theme_name_by_index(neThemePick) + " (Left/Right preview, Enter apply, Esc cancel)"
elseif neExitConfirmActive
set color 240, 210, 100
set caret 6, msgY
write "Unsaved changes: S=Save and exit D=Discard and exit C or ESC=Cancel"
elseif neStatusMsg <> "" and clock() - neStatusMsgTime < 6000
set color 200, 200, 100
set caret 6, msgY
write neStatusMsg
else
if neTheme = 0
set color 100, 100, 120
else
set color 90, 102, 128
endif
set caret 6, msgY
write "Ctrl-S Save Ctrl-R Build+Run Ctrl-Q Safe Quit In-editor X Safe Quit"
endif
endfunc
function ne_process_theme_picker()
c = inkey()
if c
ca = c
if ca = 27
neThemePickerActive = false
neTheme = neThemePickOrig
ne_set_status("Theme selection cancelled")
return
elseif ca = 13
neThemePickerActive = false
ne_set_status("Theme: " + ne_theme_name())
return
elseif ca >= asc("1") and ca <= asc("9")
pick = ca - asc("1")
if pick >= 0 and pick <= 1
neThemePick = pick
neTheme = neThemePick
endif
endif
endif
if keydown(KEY_LEFT, true) or keydown(KEY_UP, true)
neThemePick = neThemePick - 1
if neThemePick < 0 then neThemePick = 1
neTheme = neThemePick
endif
if keydown(KEY_RIGHT, true) or keydown(KEY_DOWN, true)
neThemePick = neThemePick + 1
if neThemePick > 1 then neThemePick = 0
neTheme = neThemePick
endif
endfunc
function ne_process_exit_confirm()
c = inkey()
if c
ca = c
if ca = 27 or ca = asc("c") or ca = asc("C")
neExitConfirmActive = false
neExitAfterSave = false
ne_set_status("Exit cancelled")
return
elseif ca = asc("d") or ca = asc("D")
end
elseif ca = asc("s") or ca = asc("S")
neExitConfirmActive = false
neExitAfterSave = true
ne_save()
return
endif
endif
endfunc
function ne_refresh()
ne_update_layout()
ne_scroll()
cls
ne_draw_rows()
ne_draw_cursor()
ne_draw_status()
ne_draw_message()
redraw
endfunc
function ne_set_status(msg)
neStatusMsg = msg
neStatusMsgTime = clock()
endfunc
' ============================================================
' KEY REPEAT HELPER
' ============================================================
function key_repeat(k)
t = clock()
if keydown(k, true)
krKey = k
krFirstTime = t
krLastTime = t
return true
endif
if krKey = k and keydown(k)
if t - krFirstTime >= KR_DELAY
if t - krLastTime >= KR_RATE
krLastTime = t
return true
endif
endif
else
if krKey = k then krKey = -1
endif
return false
endfunc
function key_repeat_nav(k)
t = clock()
if keydown(k, true)
krFirstByKey[k] = t
krLastByKey[k] = t
return true
endif
if keydown(k)
if krFirstByKey[k] = 0
krFirstByKey[k] = t
krLastByKey[k] = t
return true
endif
if t - krFirstByKey[k] >= KR_DELAY
if t - krLastByKey[k] >= KR_RATE
krLastByKey[k] = t
return true
endif
endif
else
krFirstByKey[k] = 0
krLastByKey[k] = 0
endif
return false
endfunc
' ============================================================
' CURSOR MOVEMENT (mirrors kilo's editorMoveCursor)
' ============================================================
function ne_move_cursor(dir)
' dir: 0=left 1=right 2=up 3=down
numRows = sizeof(neRows)
if numRows <= 0
neCy = 0
neCx = 0
return
endif
if dir = 0
if neCx > 0
neCx = neCx - 1
elseif neCy > 0
neCy = neCy - 1
neCx = len(neRows[neCy].text)
endif
elseif dir = 1
rowLen = len(neRows[neCy].text)
if neCx < rowLen
neCx = neCx + 1
elseif neCy < numRows - 1
neCy = neCy + 1
neCx = 0
endif
elseif dir = 2
if neCy > 0
neCy = neCy - 1
endif
elseif dir = 3
if neCy < numRows - 1
neCy = neCy + 1
endif
endif
if neCy < 0 then neCy = 0
if neCy >= numRows then neCy = numRows - 1
rowLen = len(neRows[neCy].text)
if neCx > rowLen then neCx = rowLen
if neCx < 0 then neCx = 0
endfunc
' ============================================================
' SEARCH INTERACTION
' ============================================================
function ne_process_search()
c = inkey()
if c
ca = c
if ca = 27
' ESC — cancel search
neSearchActive = false
neSearchQuery = ""
neSearchRow = -1
neSearchCol = -1
neSearchBuf = ""
ne_set_status("Search cancelled.")
elseif ca = 13
' Enter — confirm, leave match highlighted
neSearchActive = false
if neSearchRow >= 0
ne_set_status("Found: '" + neSearchQuery + "' (Ctrl-F to search again)")
else
ne_set_status("Not found: '" + neSearchQuery + "'")
endif
elseif ca = 8
' Backspace
if len(neSearchBuf) > 0
neSearchBuf = left(neSearchBuf, len(neSearchBuf) - 1)
neSearchQuery = neSearchBuf
if len(neSearchQuery) > 0
ne_find_next(neSearchQuery, 0, -1, 1)
else
neSearchRow = -1
neSearchCol = -1
endif
endif
elseif ca >= 32 and ca <= 126
neSearchBuf = neSearchBuf + chr(c)
neSearchQuery = neSearchBuf
ne_find_next(neSearchQuery, 0, -1, 1)
endif
endif
' Arrow navigation within search
if key_repeat_nav(KEY_DOWN) or key_repeat_nav(KEY_RIGHT)
if neSearchQuery <> ""
ne_find_next(neSearchQuery, neCy, neCx, 1)
endif
endif
if key_repeat_nav(KEY_UP) or key_repeat_nav(KEY_LEFT)
if neSearchQuery <> ""
ne_find_next(neSearchQuery, neCy, neCx - len(neSearchQuery) - 1, -1)
endif
endif
endfunc
' ============================================================
' OPEN-FILE PROMPT
' ============================================================
function ne_process_open()
c = inkey()
if c
ca = c
if ca = 27
neOpenActive = false
neOpenBuf = ""
neOpenPos = 0
ne_set_status("Open cancelled.")
elseif ca = 13
neOpenActive = false
if neOpenBuf <> ""
ne_open(neOpenBuf)
endif
neOpenBuf = ""
neOpenPos = 0
elseif ca = 8
if neOpenPos > 0
neOpenBuf = left(neOpenBuf, neOpenPos - 1) + right(neOpenBuf, neOpenPos)
neOpenPos = neOpenPos - 1
endif
elseif ca >= 32 and ca <= 126
neOpenBuf = left(neOpenBuf, neOpenPos) + chr(c) + right(neOpenBuf, neOpenPos)
neOpenPos = neOpenPos + 1
endif
endif
if keydown(KEY_LEFT, true)
if neOpenPos > 0 then neOpenPos = neOpenPos - 1
endif
if keydown(KEY_RIGHT, true)
if neOpenPos < len(neOpenBuf) then neOpenPos = neOpenPos + 1
endif
if keydown(KEY_HOME, true)
neOpenPos = 0
endif
if keydown(KEY_END, true)
neOpenPos = len(neOpenBuf)
endif
if keydown(KEY_DELETE, true)
if neOpenPos < len(neOpenBuf)
neOpenBuf = left(neOpenBuf, neOpenPos) + right(neOpenBuf, neOpenPos + 1)
endif
endif
endfunc
' ============================================================
' SAVE-AS PROMPT
' ============================================================
function ne_process_saveas()
c = inkey()
if c
ca = c
if ca = 27
neSaveActive = false
neSaveBuf = ""
neSavePos = 0
neExitAfterSave = false
ne_set_status("Save cancelled.")
elseif ca = 13
neSaveActive = false
if neSaveBuf <> ""
ne_set_filename(neSaveBuf)
ne_save()
endif
neSaveBuf = ""
neSavePos = 0
elseif ca = 8
if neSavePos > 0
neSaveBuf = left(neSaveBuf, neSavePos - 1) + right(neSaveBuf, neSavePos)
neSavePos = neSavePos - 1
endif
elseif ca >= 32 and ca <= 126
neSaveBuf = left(neSaveBuf, neSavePos) + chr(c) + right(neSaveBuf, neSavePos)
neSavePos = neSavePos + 1
endif
endif
if keydown(KEY_LEFT, true)
if neSavePos > 0 then neSavePos = neSavePos - 1
endif
if keydown(KEY_RIGHT, true)
if neSavePos < len(neSaveBuf) then neSavePos = neSavePos + 1
endif
if keydown(KEY_HOME, true)
neSavePos = 0
endif
if keydown(KEY_END, true)
neSavePos = len(neSaveBuf)
endif
if keydown(KEY_DELETE, true)
if neSavePos < len(neSaveBuf)
neSaveBuf = left(neSaveBuf, neSavePos) + right(neSaveBuf, neSavePos + 1)
endif
endif
endfunc
' ============================================================
' MOUSE HANDLING
' ============================================================
function ne_handle_mouse()
wheel = mousebutton(2, true)
if wheel
ne_set_rowoff(neRowOff - wheel * 3)
endif
if mousebutton(0, true)
mx = mousex()
my = mousey()
if ne_hit_close_button(mx, my)
ne_request_exit()
return
endif
if my < neAreaH and mx >= neTextX and mx < width(primary) - NE_SCROLL_AREA_W
clickRow = int(my / neCharH) + neRowOff
clickCol = int((mx - neTextX) / neCharW) + neColOff
if clickRow < sizeof(neRows)
neCy = clickRow
rowLen = len(neRows[neCy].text)
if clickCol > rowLen
neCx = rowLen
else
neCx = clickCol
endif
neSelAY = neCy
neSelAX = neCx
neSelActive = false
neMouseSelecting = true
' Clear search highlight on click
neSearchRow = -1
neSearchCol = -1
endif
endif
endif
if neMouseSelecting and mousebutton(0)
mx = mousex()
my = mousey()
if my < 0 then my = 0
if my >= neAreaH then my = neAreaH - 1
if mx < neTextX then mx = neTextX
dragRow = int(my / neCharH) + neRowOff
if dragRow < 0 then dragRow = 0
if dragRow >= sizeof(neRows) then dragRow = sizeof(neRows) - 1
dragCol = int((mx - neTextX) / neCharW) + neColOff
rowLen = len(neRows[dragRow].text)
if dragCol > rowLen then dragCol = rowLen
if dragCol < 0 then dragCol = 0
neCy = dragRow
neCx = dragCol
if neSelAY = neCy and neSelAX = neCx
neSelActive = false
else
neSelActive = true
endif
endif
if neMouseSelecting and not mousebutton(0)
neMouseSelecting = false
endif
endfunc
' ============================================================
' MAIN KEY PROCESSING (mirrors kilo's editorProcessKeypress)
' ============================================================
function ne_process_key()
' Handle modal states first
if neExitConfirmActive
ne_process_exit_confirm()
return
endif
if neSaveActive
ne_process_saveas()
return
endif
if neSearchActive
ne_process_search()
ne_handle_mouse()
return
endif
if neOpenActive
ne_process_open()
return
endif
if neThemePickerActive
ne_process_theme_picker()
return
endif
ctrl = keydown(KEY_CONTROL)
' --- Ctrl + key combinations ---
if ctrl
if keydown(KEY_S, true)
ne_save()
endif
if keydown(KEY_Q, true)
ne_request_exit()
endif
if keydown(KEY_F, true)
neSearchActive = true
neSearchBuf = ""
neSearchQuery = ""
neSearchRow = -1
neSearchCol = -1
endif
if keydown(KEY_O, true)
neOpenActive = true
neOpenBuf = ""
neOpenPos = 0
endif
if keydown(KEY_C, true)
ne_copy_line()
endif
if keydown(KEY_X, true)
ne_cut_line()
endif
if keydown(KEY_V, true)
ne_paste_clipboard()
endif
if keydown(KEY_R, true)
ne_compile_and_run()
endif
if keydown(KEY_A, true)
ne_select_all()
endif
if keydown(KEY_L, true)
neShowLineNumbers = not neShowLineNumbers
ne_update_layout()
if neShowLineNumbers
ne_set_status("Line numbers: ON")
else
ne_set_status("Line numbers: OFF")
endif
endif
if keydown(KEY_T, true)
neThemePickerActive = true
neThemePick = neTheme
neThemePickOrig = neTheme
ne_set_status("Theme picker: Left/Right preview, Enter apply, Esc cancel")
endif
' Ctrl+Home / Ctrl+End — jump to top/bottom of file
if keydown(KEY_HOME, true)
neCy = 0
neCx = 0
endif
if keydown(KEY_END, true)
neCy = sizeof(neRows) - 1
neCx = len(neRows[neCy].text)
endif
' Ctrl+Right / Ctrl+Left — word jump
if keydown(KEY_RIGHT, true)
if neCy < sizeof(neRows)
t = neRows[neCy].text
cx = neCx
while cx < len(t) and mid(t, cx, 1) <> " "
cx = cx + 1
wend
while cx < len(t) and mid(t, cx, 1) = " "
cx = cx + 1
wend
neCx = cx
endif
endif
if keydown(KEY_LEFT, true)
if neCx > 0
t = neRows[neCy].text
cx = neCx - 1
while cx > 0 and mid(t, cx - 1, 1) = " "
cx = cx - 1
wend
while cx > 0 and mid(t, cx - 1, 1) <> " "
cx = cx - 1
wend
neCx = cx
endif
endif
ne_handle_mouse()
neQuitCount = NE_QUIT_TIMES
return
endif
' Reset quit counter on non-quit key
neQuitCount = NE_QUIT_TIMES
' --- Navigation ---
if key_repeat_nav(KEY_LEFT)
shiftMove = ne_prepare_move_selection()
ne_move_cursor(0)
ne_finalize_move_selection(shiftMove)
endif
if key_repeat_nav(KEY_RIGHT)
shiftMove = ne_prepare_move_selection()
ne_move_cursor(1)
ne_finalize_move_selection(shiftMove)
endif
if key_repeat_nav(KEY_UP)
shiftMove = ne_prepare_move_selection()
ne_move_cursor(2)
ne_finalize_move_selection(shiftMove)
endif
movedDown = false
if keydown(KEY_DOWN)
movedDown = true
shiftMove = ne_prepare_move_selection()
ne_move_cursor(3)
ne_finalize_move_selection(shiftMove)
elseif key_repeat_nav(KEY_DOWN)
movedDown = true
shiftMove = ne_prepare_move_selection()
ne_move_cursor(3)
ne_finalize_move_selection(shiftMove)
endif
if not movedDown and keydown(KEY_DOWN, true)
shiftMove = ne_prepare_move_selection()
ne_move_cursor(3)
ne_finalize_move_selection(shiftMove)
endif
if keydown(KEY_HOME, true)
shiftMove = ne_prepare_move_selection()
' Smart Home: go to first non-space, then column 0
if neCy < sizeof(neRows)
t = neRows[neCy].text
sx = 0
while sx < len(t) and mid(t, sx, 1) = " "
sx = sx + 1
wend
if neCx > sx
neCx = sx
else
neCx = 0
endif
endif
ne_finalize_move_selection(shiftMove)
endif
if keydown(KEY_END, true)
shiftMove = ne_prepare_move_selection()
if neCy < sizeof(neRows)
neCx = len(neRows[neCy].text)
endif
ne_finalize_move_selection(shiftMove)
endif
if key_repeat(KEY_PAGE_UP)
shiftMove = ne_prepare_move_selection()
if neCy > neScrRows
neCy = neCy - neScrRows
else
neCy = 0
endif
ne_finalize_move_selection(shiftMove)
endif
if key_repeat(KEY_PAGE_DOWN)
shiftMove = ne_prepare_move_selection()
neCy = neCy + neScrRows
if neCy >= sizeof(neRows) then neCy = sizeof(neRows) - 1
ne_finalize_move_selection(shiftMove)
endif
' --- Typed characters (inkey handles OS key-repeat for printable keys) ---
c = inkey()
if c
ca = c
if ca = 13
' Enter
ne_insert_newline()
elseif ca = 8
' Backspace
ne_delete_char()
elseif ca = 127
' Delete forward
ne_delete_forward()
elseif ca = 9
' Tab → spaces
if ne_has_selection() then ne_delete_selection()
for i = 1 to NE_TAB_SIZE
ne_insert_char(" ")
next
elseif ca >= 32 and ca <= 126
ne_insert_char(chr(c))
endif
endif
ne_handle_mouse()
endfunc
' ============================================================
' MAIN
' ============================================================
function main()
ne_init()
' Load file from command line if provided
if sizeof(args) > 1
ne_open(args[1])
else
ne_try_restore_autosave()
endif
' Main loop — mirrors kilo's main()
do
ne_refresh()
ne_process_key()
ne_tick_autosave()
fwait 60
until false
endfunc
main()


-> perhaps in a galaxy far, far away 