Thread Rating:
  • 1 Vote(s) - 4 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Nova Editor
#1
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
'
'  
AuthorNelson 

Features:
'   - Multi-line text editing with syntax highlighting
'
   N7/NaaLaa keyword highlighting
'   - File open / save
'
   Incremental search with Ctrl+(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
screenw()*2/3
screenh()*2/3
set window 
"Nova Editor 0.1"whfalse0min(w640), min(h480)
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 colrow (0-based)
visible neRowOff 0neColOff ' scroll offsets (rows, cols)
Selection state
visible neSelActive 
false
visible neSelAX 
0neSelAY 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"16falsefalsefalsetrue)
    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-indentcopy 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()
    ' 
Backspacedelete 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 1neRows[neCy].text)
        ne_row_delete(neCy)
        neCy neCy 1
        neCx 
prevLen
    
else
        ne_row_delete_char(neCyneCx 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 >= 0
        c 
mid(filenamei1)
        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)] = [textln]
        ln frln(f)
    wend
    free file f
    
if sizeof(neRows) = 0 then neRows = [[text""]]
    ne_set_filename(filename)
    neCx neCy 0
    neRowOff 
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 >= 0
        c 
mid(filenamei1)
        ca asc(c)
        if ca 47 or ca 92 then break
        i 1
    wend
    
if 0 then return "."
    return left(filename1)
endfunc
function ne_source_to_exe(filename)
    i len(filename) - 1
    lastDot 
= -1
    lastSep 
= -1
    
while >= 0
        c 
mid(filenamei1)
        ca asc(c)
        if ca 47 or ca 92
            lastSep 
i
            
break
        endif
        if ca 46 and lastDot 0
            lastDot 
i
        
endif
        i 1
    wend
    
if lastDot lastSep
        
return left(filenamelastDot) + ".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 "n7.exe" " " neFilename q
    result 
system(cmd)
    if instr(lower(result), "n7b: success") >= 0
        ne_set_status
("Build successful")
        return true
    
endif
    lines split(replace(resultchr(13), ""), chr(10))
    if sizeof(lines) > 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 " ne_path_dir(exeFile) + " && start " " " 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 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(fthen 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(fthen return
    wln file f"NOVA_AUTOSAVE_V1"
    wln file f"DIRTY"
    wln file fneFilename
    wln file f
neDocName
    
for 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 neLastAutosave >= NE_AUTOSAVE_MS
        ne_autosave_write
()
    endif
endfunc
function ne_try_restore_autosave()
    f openfile(NE_AUTOSAVE_FILE)
    if not typeof(fthen 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(lnchr(13), "")
        recRows[sizeof(recRows)] = [textln]
        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 32 or or 34 or
            instr(",.()+-/*=~%<>[];:{}"ch) >= 0
endfunc
function is_digit_ch(ch)
    if ch "" then return false
    c 
asc(ch)
    return >= asc("0") and <= asc("9")
endfunc
function is_alpha_ch(ch)
    if ch "" then return false
    c 
asc(ch)
    return (>= asc("a") and <= asc("z")) or
            (>= asc("A") and <= asc("Z")) or 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=none34=double-quote
    in_comment 
false
    i 
0
    
while tlen
        ch 
mid(texti1)
        ca asc(ch)
        if in_comment
            hl
[i] = HL_COMMENT
            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 previewEnter applyEsc cancel)"
    elseif neExitConfirmActive
        set color 240, 210, 100
        set caret 6, msgY
        write "
Unsaved changesS=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 numbersON")
            else
                ne_set_status("
Line numbersOFF")
            endif
        endif
        if keydown(KEY_T, true)
            neThemePickerActive = true
            neThemePick = neTheme
            neThemePickOrig = neTheme
            ne_set_status("
Theme pickerLeft/Right previewEnter applyEsc 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() 


   
Reply
#2
Thank you for sharing it.
   
Reply
#3
Cool editor!

Two important questions:

1. Themes. How to add/create themes?
2. Is there a Klingon version? (I am okay with a Vulcan version.)
Logic is the beginning of wisdom.

Live long and prosper.
Reply
#4
(06-20-2026, 03:46 AM)johnno56 Wrote: Cool editor!

Two important questions:

1. Themes. How to add/create themes?
2. Is there a Klingon version? (I am okay with a Vulcan version.)

1. Theme -> You can toggle between themes using Ctrl-T. It seems... creating or adding custom themes isn't available in this version.
2. Klingon / Vulcan Big Grin  -> perhaps in a galaxy far, far away Big Grin

Btw, thanks to Nelson for sharing this Nova Editor code !
Reply
#5
Themes... Oh well, worth a shot...
(I'm going to assume that the two themes are "hard coded")
Klingon/Vulcan... "Galaxy far far away" is from Star wars, not Star Trek... but I did get the point... lol

maj ram (marzh rarm) - Klingon: Good night.
Rom mu-yor - Vulcan: Good night.
Logic is the beginning of wisdom.

Live long and prosper.
Reply
#6
Wow! That's very impressive! And I love using minimalistic editors Smile

Undo/redo would be a good addition, but it can be pretty quirky to implement. You can have a look at the TextEditor implementation, used for NED, in n7/lib/ngui.n7 if you want inspiration.

Thanks for posting this!
Reply
#7
Hey Guys, working on Undo / Redo using a stack for that with a snap of the doc so i can used and a time based auto saving kind of, thanks for the comments. I also will add 3 tabs at maximum to handle 3 docs not just one but for now WYSIWYG and fot Themes we have ctrl + T so we can chose between 2 white and dark, will try to see if we can add a config so we can load from there. Also had a config to run and compile from there with output for the status bar. ctrl + R.

Any comments request are welcome.
Reply
#8
... a neural interface? (for those of us that do not like keyboards).... lol
Logic is the beginning of wisdom.

Live long and prosper.
Reply
#9
oh no no neural that can demage our small brains   Big Grin

ohhh it is to HOT here to think ...over 35 C

air...air ....fresh air ... .... Rolleyes
Reply
#10
Damage? Easily fixed... We can use politicians... Why? Because there are some tests that rats refuse to do... lol

35C... Hot? For we Aussies, 35C is a warm day... When it gets to 45+C, then we start to complain... lol

Air? Totally agree Big Grin
Logic is the beginning of wisdom.

Live long and prosper.
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)