initial code commit
parent
de2769b9fa
commit
a90b90e711
58
src/Acorn.jl
58
src/Acorn.jl
|
@ -1,5 +1,61 @@
|
||||||
module Acorn
|
module Acorn
|
||||||
|
|
||||||
# package code goes here
|
include("terminal.jl")
|
||||||
|
|
||||||
|
# Configuration submodule
|
||||||
|
include("EditorConfig.jl")
|
||||||
|
using .EditorConfig
|
||||||
|
|
||||||
|
include("row.jl")
|
||||||
|
include("cmds/Command.jl")
|
||||||
|
include("editor.jl")
|
||||||
|
|
||||||
|
|
||||||
|
# Load commands
|
||||||
|
include("cmds/open.jl")
|
||||||
|
include("cmds/save.jl")
|
||||||
|
include("cmds/quit.jl")
|
||||||
|
include("cmds/find.jl")
|
||||||
|
include("cmds/help.jl")
|
||||||
|
include("cmds/bind.jl")
|
||||||
|
include("cmds/set.jl")
|
||||||
|
include("cmds/echo.jl")
|
||||||
|
|
||||||
|
function acorn(filename::String, rel=true)
|
||||||
|
ed = Editor()
|
||||||
|
|
||||||
|
rel && (filename = abspath(filename))
|
||||||
|
|
||||||
|
editorOpen(ed, filename)
|
||||||
|
|
||||||
|
setStatusMessage(ed, "HELP: Ctrl-Q: quit | Ctrl-S: save | Ctrl-F: find")
|
||||||
|
|
||||||
|
Base.Terminals.raw!(ed.term, true)
|
||||||
|
|
||||||
|
try
|
||||||
|
while !ed.quit
|
||||||
|
refreshScreen(ed)
|
||||||
|
processKeypress(ed)
|
||||||
|
end
|
||||||
|
catch ex
|
||||||
|
editorQuit(ed, force=true)
|
||||||
|
throw(ex)
|
||||||
|
end
|
||||||
|
|
||||||
|
Base.Terminals.raw!(ed.term, false)
|
||||||
|
|
||||||
|
return nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
function acorn()
|
||||||
|
# If a file is given, open it
|
||||||
|
if length(ARGS) > 0
|
||||||
|
filename = ARGS[1]
|
||||||
|
acorn(filename, rel=false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
export
|
||||||
|
acorn
|
||||||
|
|
||||||
end # module
|
end # module
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
module EditorConfig
|
||||||
|
|
||||||
|
|
||||||
|
###############
|
||||||
|
## Parameter ##
|
||||||
|
###############
|
||||||
|
|
||||||
|
type Parameter{T}
|
||||||
|
value::T
|
||||||
|
validate::Union{Function, Void}
|
||||||
|
desc::String # Used when calling help <param name>
|
||||||
|
end
|
||||||
|
|
||||||
|
validate(p::Parameter) = p.validate == nothing ? true : p.validate(p.value)
|
||||||
|
|
||||||
|
function set(p::Parameter, x)
|
||||||
|
old_val = p.value
|
||||||
|
|
||||||
|
# Correct type?
|
||||||
|
try
|
||||||
|
p.value = x
|
||||||
|
catch Exception
|
||||||
|
p.value = old_val
|
||||||
|
throw(ArgumentError("Invalid parameter assignment: $sym, $x"))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Valid?
|
||||||
|
if !validate(p)
|
||||||
|
p.value = old_val
|
||||||
|
throw(ArgumentError("Invalid parameter assignmnt: $sym, $x"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
## CONFIG ##
|
||||||
|
############
|
||||||
|
|
||||||
|
const CONFIG = Dict{Symbol, Parameter}()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function configSet(sym::Symbol, x)
|
||||||
|
# Check if parameter exists
|
||||||
|
if !(sym in keys(CONFIG))
|
||||||
|
throw(ArgumentError("No parameter named $sym"))
|
||||||
|
end
|
||||||
|
|
||||||
|
p = CONFIG[sym]
|
||||||
|
set(p, x)
|
||||||
|
|
||||||
|
CONFIG[sym] = p
|
||||||
|
end
|
||||||
|
|
||||||
|
configGet(sym::Symbol) = CONFIG[sym].value
|
||||||
|
configDesc(sym::Symbol) = CONFIG[sym].desc
|
||||||
|
configIsParam(sym::Symbol) = sym in keys(CONFIG)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##################
|
||||||
|
## KEY BINDINGS ##
|
||||||
|
##################
|
||||||
|
|
||||||
|
|
||||||
|
const KEY_BINDINGS = Dict{UInt32, String}()
|
||||||
|
|
||||||
|
function setKeyBinding(c::Char, s::String)
|
||||||
|
KEY_BINDINGS[UInt32(c) & 0x1f] = s
|
||||||
|
end
|
||||||
|
|
||||||
|
function getKeyBinding(c::Char)
|
||||||
|
get(KEY_BINDINGS, UInt32(c) & 0x1f, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
isKeyBound(c::Char) = (UInt32(c) & 0x1f) in keys(KEY_BINDINGS)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
########################
|
||||||
|
## DEFAULT PARAMETERS ##
|
||||||
|
########################
|
||||||
|
|
||||||
|
CONFIG[:tab_stop] = Parameter{Int}(4, n-> n > 0 && n <= 16, "visual size of a tab in number of spaces")
|
||||||
|
CONFIG[:expandtab] = Parameter{Bool}(false, nothing, "if true, use spaces instead of tabs when pressing <tab>")
|
||||||
|
|
||||||
|
|
||||||
|
##########################
|
||||||
|
## DEFAULT KEY BINDINGS ##
|
||||||
|
##########################
|
||||||
|
|
||||||
|
setKeyBinding('s', "save")
|
||||||
|
setKeyBinding('o', "open")
|
||||||
|
setKeyBinding('f', "find")
|
||||||
|
setKeyBinding('q', "quit")
|
||||||
|
|
||||||
|
|
||||||
|
# # In juliarc:
|
||||||
|
# # using Acorn
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# ## KEY_BINDINGS
|
||||||
|
# #Acorn.addKeyBinding('f', "find")
|
||||||
|
# Acorn.addKeyBinding('o', "open")
|
||||||
|
# Acorn.addKeyBinding('s', "save")
|
||||||
|
#
|
||||||
|
# """
|
||||||
|
# bind f find
|
||||||
|
# bind o open
|
||||||
|
# bind s save
|
||||||
|
# set tab_stop 4
|
||||||
|
# set expandtab true
|
||||||
|
# """
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# hard/soft tabs
|
||||||
|
# show hidden characters
|
||||||
|
|
||||||
|
export
|
||||||
|
configGet,
|
||||||
|
configSet,
|
||||||
|
configIsParam,
|
||||||
|
configDesc,
|
||||||
|
setKeyBinding,
|
||||||
|
getKeyBinding,
|
||||||
|
isKeyBound
|
||||||
|
|
||||||
|
end #module
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
||||||
|
type Command
|
||||||
|
name::Symbol
|
||||||
|
help::String
|
||||||
|
cmd::Function
|
||||||
|
end
|
||||||
|
|
||||||
|
const COMMANDS = Dict{Symbol, Command}()
|
||||||
|
|
||||||
|
function addCommand(name::Symbol, cmd::Function; help::String="")
|
||||||
|
COMMANDS[name] = Command(name, help, cmd)
|
||||||
|
end
|
|
@ -0,0 +1,21 @@
|
||||||
|
function commandBind(ed::Editor, args::String)
|
||||||
|
arg_arr = strip.(split(args, ' ', limit=2))
|
||||||
|
|
||||||
|
if length(arg_arr) != 2
|
||||||
|
setStatusMessage(ed, "bind: command requires two arguments")
|
||||||
|
return
|
||||||
|
elseif !( length(arg_arr[1]) == 1 && isalpha(arg_arr[1][1]) )
|
||||||
|
setStatusMessage(ed, "bind: first arg must be a letter")
|
||||||
|
return
|
||||||
|
# elseif !Base.isidentifier(arg_arr[2])
|
||||||
|
# setStatusMessage(ed, "bind: command $(arg_arr[2]) is invalid")
|
||||||
|
# return
|
||||||
|
end
|
||||||
|
|
||||||
|
key = lowercase(arg_arr[1][1])
|
||||||
|
|
||||||
|
setKeyBinding(key, join(arg_arr[2]))
|
||||||
|
end
|
||||||
|
|
||||||
|
addCommand(:bind, commandBind,
|
||||||
|
help="bind <C> <CMD>: bind ctrl-<C> to command <CMD>")
|
|
@ -0,0 +1,6 @@
|
||||||
|
function commandEcho(ed::Editor, args::String)
|
||||||
|
setStatusMessage(ed, args)
|
||||||
|
end
|
||||||
|
|
||||||
|
addCommand(:echo, commandEcho,
|
||||||
|
help="echo <msg>: set the status message to <msg>")
|
|
@ -0,0 +1,86 @@
|
||||||
|
|
||||||
|
function initFind(ed::Editor)
|
||||||
|
ed.params[:find] = Dict{Symbol, Any}()
|
||||||
|
|
||||||
|
# 1 indexed, 0 none
|
||||||
|
ed.params[:find][:last_match] = 0
|
||||||
|
ed.params[:find][:direction] = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function findCallback(ed::Editor, query::String, key::Char)
|
||||||
|
# If the params have not been created, init them
|
||||||
|
if !(:find in keys(ed.params))
|
||||||
|
initFind(ed)
|
||||||
|
end
|
||||||
|
|
||||||
|
last_match = ed.params[:find][:last_match]
|
||||||
|
direction = ed.params[:find][:direction]
|
||||||
|
|
||||||
|
if key == '\r' || key == '\x1b'
|
||||||
|
last_match = 0
|
||||||
|
direction = 1
|
||||||
|
return
|
||||||
|
elseif key == ARROW_RIGHT || key == ARROW_DOWN
|
||||||
|
direction = 1
|
||||||
|
elseif key == ARROW_LEFT || key == ARROW_UP
|
||||||
|
direction = -1
|
||||||
|
else
|
||||||
|
last_match = 0
|
||||||
|
direction = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
last_match == 0 && (direction = 1)
|
||||||
|
current = last_match
|
||||||
|
for i = 1:length(ed.rows)
|
||||||
|
current += direction
|
||||||
|
|
||||||
|
# Bounds check
|
||||||
|
if current == 0
|
||||||
|
# At begenning of document? Go to end
|
||||||
|
current = length(ed.rows)
|
||||||
|
elseif current == length(ed.rows)+1
|
||||||
|
# At end of doc? Got to start
|
||||||
|
current = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
row = ed.rows[current]
|
||||||
|
loc = search(row.chars, query)
|
||||||
|
if loc != 0:-1
|
||||||
|
last_match = current
|
||||||
|
ed.csr.y = current
|
||||||
|
ed.csr.x = first(loc)#charX(row, first(loc))
|
||||||
|
ed.rowoff = length(ed.rows)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update params
|
||||||
|
ed.params[:find][:last_match] = last_match
|
||||||
|
ed.params[:find][:direction] = direction
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function editorFind(ed::Editor, str::String)
|
||||||
|
saved_cx, saved_cy = ed.csr.x, ed.csr.y
|
||||||
|
saved_coloff = ed.coloff
|
||||||
|
saved_rowoff = ed.rowoff
|
||||||
|
|
||||||
|
findCallback(ed, str, ' ')
|
||||||
|
query = editorPrompt(ed, "Search (arrow keys for next/prev): ",
|
||||||
|
callback=findCallback,
|
||||||
|
buf=str,
|
||||||
|
showcursor=false)
|
||||||
|
|
||||||
|
# If the user cancels the search, restore view
|
||||||
|
if query == ""
|
||||||
|
ed.csr.x = saved_cx
|
||||||
|
ed.csr.y = saved_cy
|
||||||
|
ed.coloff = saved_coloff
|
||||||
|
ed.rowoff = saved_rowoff
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
addCommand(:find, editorFind,
|
||||||
|
help="type to find, <enter> to get cursor, arrow keys to next/prev")
|
|
@ -0,0 +1,31 @@
|
||||||
|
function commandHelp(ed::Editor, args::String)
|
||||||
|
if args == ""
|
||||||
|
setStatusMessage(ed, "Type help <command> for command specific help")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if !Base.isidentifier(args)
|
||||||
|
setStatusMessage(ed, "help: '$args' is not a valid command name")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
sym = Symbol(args)
|
||||||
|
|
||||||
|
# Try getting help information from commands and params
|
||||||
|
helptext = ""
|
||||||
|
|
||||||
|
if configIsParam(sym)
|
||||||
|
helptext = configDesc(sym)
|
||||||
|
elseif sym in keys(COMMANDS)
|
||||||
|
helptext = COMMANDS[sym].help
|
||||||
|
end
|
||||||
|
|
||||||
|
if helptext == ""
|
||||||
|
setStatusMessage(ed, "No help documents for '$args'")
|
||||||
|
else
|
||||||
|
setStatusMessage(ed, helptext)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
addCommand(:help, commandHelp,
|
||||||
|
help="Type help <command> for command specific help")
|
|
@ -0,0 +1,10 @@
|
||||||
|
function commandOpen(ed::Editor, args::String)
|
||||||
|
if args == ""
|
||||||
|
editorOpen(ed)
|
||||||
|
else
|
||||||
|
editorOpen(ed, args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
addCommand(:open, commandOpen,
|
||||||
|
help="open: open a file")
|
|
@ -0,0 +1,6 @@
|
||||||
|
function commandQuit(ed::Editor, args::String)
|
||||||
|
editorQuit(ed, force=strip(args) == "!")
|
||||||
|
end
|
||||||
|
|
||||||
|
addCommand(:quit, commandQuit,
|
||||||
|
help="quit [!]: quit acorn. run 'quit !' to force quit")
|
|
@ -0,0 +1,6 @@
|
||||||
|
function commandSave(ed::Editor, args::String)
|
||||||
|
editorSave(ed, args)
|
||||||
|
end
|
||||||
|
|
||||||
|
addCommand(:save, commandSave,
|
||||||
|
help="save: save the file")
|
|
@ -0,0 +1,36 @@
|
||||||
|
function commandSet(ed::Editor, args::String)
|
||||||
|
arg_arr = strip.(split(args, ' '))
|
||||||
|
|
||||||
|
# Initial checks
|
||||||
|
if length(arg_arr) != 2
|
||||||
|
setStatusMessage(ed, "set: command requires two arguments")
|
||||||
|
return
|
||||||
|
elseif !Base.isidentifier(arg_arr[1])
|
||||||
|
setStatusMessage(ed, "set: $(arg_arr[1]) is not a valid command name")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
sym = Symbol(arg_arr[1])
|
||||||
|
|
||||||
|
# Check if it is a valid parameter
|
||||||
|
if !configIsParam(sym)
|
||||||
|
setStatusMessage(ed, "set: '$sym' is not a valid parameter name")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
# Attempt to assign the parameter
|
||||||
|
try
|
||||||
|
val = parse(arg_arr[2])
|
||||||
|
configSet(sym, val)
|
||||||
|
catch Exception
|
||||||
|
setStatusMessage(ed, "set: invalid argument for $sym '$(arg_arr[2])'")
|
||||||
|
end
|
||||||
|
|
||||||
|
for row in ed.rows
|
||||||
|
update!(row)
|
||||||
|
end
|
||||||
|
refreshScreen(ed)
|
||||||
|
end
|
||||||
|
|
||||||
|
addCommand(:set, commandSet,
|
||||||
|
help="set <param> <value>: set the given parameter to a value")
|
|
@ -0,0 +1,536 @@
|
||||||
|
# """ Clear the screen, print an error message, and kill the program """
|
||||||
|
# function die(msg)
|
||||||
|
# write(STDOUT, "\x1b[2J")
|
||||||
|
# write(STDOUT, "\x1b[H")
|
||||||
|
# error(msg)
|
||||||
|
# end
|
||||||
|
|
||||||
|
|
||||||
|
##########
|
||||||
|
# CURSOR #
|
||||||
|
##########
|
||||||
|
|
||||||
|
mutable struct Cursor
|
||||||
|
x::Int
|
||||||
|
y::Int
|
||||||
|
rx::Int
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
##########
|
||||||
|
# EDITOR #
|
||||||
|
##########
|
||||||
|
|
||||||
|
mutable struct Editor
|
||||||
|
rowoff::Int
|
||||||
|
coloff::Int
|
||||||
|
width::Int
|
||||||
|
height::Int
|
||||||
|
filename::String
|
||||||
|
statusmsg::String
|
||||||
|
status_time::Float64
|
||||||
|
dirty::Bool
|
||||||
|
quit::Bool
|
||||||
|
|
||||||
|
csr::Cursor
|
||||||
|
rows::Rows
|
||||||
|
term::Base.Terminals.TTYTerminal
|
||||||
|
|
||||||
|
params::Dict{Symbol, Dict{Symbol, Any}}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Editor()
|
||||||
|
rowoff = 0
|
||||||
|
coloff = 0
|
||||||
|
width = 0
|
||||||
|
height = 0
|
||||||
|
filename = ""
|
||||||
|
statusmsg = ""
|
||||||
|
status_time = 0
|
||||||
|
dirty = false
|
||||||
|
quit = false
|
||||||
|
|
||||||
|
csr = Cursor(1,1,1)
|
||||||
|
rows = Rows()
|
||||||
|
term = Base.Terminals.TTYTerminal(get(ENV, "TERM", @static is_windows() ? "" : "dumb"), STDIN, STDOUT, STDERR)
|
||||||
|
|
||||||
|
params = Dict{Symbol, Dict{Symbol, Any}}()
|
||||||
|
|
||||||
|
Editor(rowoff, coloff, width, height, filename,
|
||||||
|
statusmsg, status_time, dirty, quit,
|
||||||
|
csr, rows, term, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###################
|
||||||
|
# FILE OPERATIONS #
|
||||||
|
###################
|
||||||
|
|
||||||
|
function expand(filename::String)
|
||||||
|
# If the path is something like "~/Desktop/..." expand it
|
||||||
|
if length(filename) > 1 && filename[1] == '~'
|
||||||
|
filename = expanduser(filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
return filename
|
||||||
|
end
|
||||||
|
|
||||||
|
""" Open a file and read the lines into the ed.rows array """
|
||||||
|
function editorOpen(ed::Editor, filename::String)
|
||||||
|
try
|
||||||
|
filename = expand(filename)
|
||||||
|
|
||||||
|
# If no file exists, create it
|
||||||
|
!isfile(filename) && open(filename, "w") do f end
|
||||||
|
|
||||||
|
open(filename, "r") do file
|
||||||
|
clearRows(ed.rows)
|
||||||
|
for line in eachline(file)
|
||||||
|
appendRow(ed.rows, line)
|
||||||
|
end
|
||||||
|
ed.filename = filename
|
||||||
|
ed.csr.x = 1
|
||||||
|
ed.csr.y = 1
|
||||||
|
ed.dirty = false
|
||||||
|
end
|
||||||
|
catch Exception e
|
||||||
|
setStatusMessage(ed, "Cannot open file $filename")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function editorOpen(ed::Editor)
|
||||||
|
if ed.dirty
|
||||||
|
confirm = editorPrompt(ed, "There are unsaved changes. Open another file? [y/n]: ")
|
||||||
|
if confirm != "y"
|
||||||
|
setStatusMessage(ed, "Open aborted")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
filename = editorPrompt(ed, "Open file: ")
|
||||||
|
filename = expand(filename)
|
||||||
|
|
||||||
|
if filename != ""
|
||||||
|
editorOpen(ed, filename)
|
||||||
|
else
|
||||||
|
setStatusMessage(ed, "Open aborted")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function editorSave(ed::Editor)
|
||||||
|
editorSave(ed, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
""" Save the contents of the file buffer to disc """
|
||||||
|
function editorSave(ed::Editor, path::String)
|
||||||
|
prev_filename = ed.filename
|
||||||
|
|
||||||
|
try
|
||||||
|
if path == ""
|
||||||
|
if ed.filename == ""
|
||||||
|
ed.filename = editorPrompt(ed, "Save as: ")
|
||||||
|
if ed.filename == ""
|
||||||
|
setStatusMessage(ed, "Save aborted")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ed.filename = expand(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
open(ed.filename, "w") do f
|
||||||
|
write(f, rowsToString(ed.rows))
|
||||||
|
end
|
||||||
|
setStatusMessage(ed, "File saved: $(ed.filename)")
|
||||||
|
ed.dirty = false
|
||||||
|
catch Exception
|
||||||
|
# There was an error saving, restore original filename
|
||||||
|
ed.filename = prev_filename
|
||||||
|
setStatusMessage(ed, "Unable to save: $(ed.filename)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##########
|
||||||
|
# VISUAL #
|
||||||
|
##########
|
||||||
|
|
||||||
|
""" Given an arrow input, move the cursor """
|
||||||
|
function moveCursor(ed::Editor, key::UInt32)
|
||||||
|
if key == ARROW_LEFT
|
||||||
|
if ed.csr.x > 1
|
||||||
|
ed.csr.x -= 1
|
||||||
|
elseif ed.csr.y > 1
|
||||||
|
# At start of line, move to end of prev line
|
||||||
|
ed.csr.y -= 1
|
||||||
|
ed.csr.x = 1+length(ed.rows[ed.csr.y].chars)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif key == ARROW_RIGHT
|
||||||
|
onrow = ed.csr.y <= length(ed.rows)
|
||||||
|
if onrow && ed.csr.x <= length(ed.rows[ed.csr.y].chars)
|
||||||
|
ed.csr.x += 1
|
||||||
|
elseif ed.csr.y < length(ed.rows) && ed.csr.x == 1 + length(ed.rows[ed.csr.y].chars)
|
||||||
|
# At end of line, move to next line
|
||||||
|
ed.csr.y += 1
|
||||||
|
ed.csr.x = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif key == ARROW_UP
|
||||||
|
ed.csr.y > 1 && (ed.csr.y -= 1)
|
||||||
|
|
||||||
|
elseif key == ARROW_DOWN
|
||||||
|
ed.csr.y < length(ed.rows) && (ed.csr.y += 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Snap to end of line if we are further out
|
||||||
|
rowlen = ed.csr.y < length(ed.rows)+1 ? length(ed.rows[ed.csr.y].chars)+1 : 1
|
||||||
|
ed.csr.x > rowlen && (ed.csr.x = rowlen)
|
||||||
|
end
|
||||||
|
|
||||||
|
moveCursor(ed, key::Key) = moveCursor(ed, UInt32(key))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
""" Scroll the screen based on the cursor position """
|
||||||
|
function scroll(ed::Editor)
|
||||||
|
ed.csr.rx = 1
|
||||||
|
if ed.csr.y <= length(ed.rows)
|
||||||
|
ed.csr.rx = renderX(ed.rows[ed.csr.y], ed.csr.x)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Vertical scrolling
|
||||||
|
if ed.csr.y < ed.rowoff+1
|
||||||
|
ed.rowoff = ed.csr.y-1
|
||||||
|
end
|
||||||
|
if ed.csr.y >= ed.rowoff+1 + ed.height
|
||||||
|
ed.rowoff = ed.csr.y - ed.height
|
||||||
|
end
|
||||||
|
|
||||||
|
# Horizontal scrolling
|
||||||
|
if ed.csr.rx < ed.coloff+1
|
||||||
|
ed.coloff = ed.csr.rx
|
||||||
|
end
|
||||||
|
if ed.csr.rx >= ed.coloff+1 + ed.width
|
||||||
|
ed.coloff = ed.csr.rx - ed.width
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function drawRows(ed::Editor, buf::IOBuffer)
|
||||||
|
for y = 1:ed.height
|
||||||
|
filerow = y + ed.rowoff
|
||||||
|
y != 1 && write(buf, "\r\n")
|
||||||
|
|
||||||
|
write(buf, "\x1b[K") # Clear line
|
||||||
|
if filerow > length(ed.rows)
|
||||||
|
if y == div(ed.height, 3) && ed.width > 40 && length(ed.rows) == 0
|
||||||
|
msg = "Acorn Editor"
|
||||||
|
padding = div(ed.width - length(msg), 2)
|
||||||
|
if padding > 0
|
||||||
|
write(buf, "~")
|
||||||
|
padding -= 1
|
||||||
|
end
|
||||||
|
while (padding -= 1) > 0
|
||||||
|
write(buf, " ")
|
||||||
|
end
|
||||||
|
write(buf, msg)
|
||||||
|
else
|
||||||
|
write(buf, "~");
|
||||||
|
end
|
||||||
|
else
|
||||||
|
len = length(ed.rows[filerow].render) - ed.coloff
|
||||||
|
len = clamp(len, 0, ed.width)
|
||||||
|
write(buf, ed.rows[filerow].render[1+ed.coloff : ed.coloff + len])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# Write a newline to prepare for status bar
|
||||||
|
write(buf, "\r\n");
|
||||||
|
end
|
||||||
|
|
||||||
|
function drawStatusBar(ed::Editor, buf::IOBuffer)
|
||||||
|
write(buf, "\x1b[7m") # invert colors
|
||||||
|
col = 1
|
||||||
|
|
||||||
|
# left padding
|
||||||
|
write(buf, ' ')
|
||||||
|
col += 1
|
||||||
|
|
||||||
|
# filename
|
||||||
|
filestatus = string(ed.filename, ed.dirty ? " *" : "")
|
||||||
|
|
||||||
|
for i = 1:min(div(ed.width,2), length(filestatus))
|
||||||
|
write(buf, filestatus[i])
|
||||||
|
col += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
linenum = string(ed.csr.y)
|
||||||
|
|
||||||
|
while col < ed.width - length(linenum)
|
||||||
|
write(buf, ' ')
|
||||||
|
col += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
write(buf, linenum, ' ')
|
||||||
|
|
||||||
|
write(buf, "\x1b[m") # uninvert colors
|
||||||
|
|
||||||
|
# make line for message bar
|
||||||
|
write(buf, "\r\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
function drawStatusMessage(ed::Editor, buf::IOBuffer)
|
||||||
|
write(buf, "\x1b[K")
|
||||||
|
if time() - ed.status_time < 5.0
|
||||||
|
write(buf, ed.statusmsg[1:min(ed.width, length(ed.statusmsg))])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function setStatusMessage(ed::Editor, msg::String)
|
||||||
|
ed.statusmsg = msg
|
||||||
|
ed.status_time = time()
|
||||||
|
end
|
||||||
|
|
||||||
|
function refreshScreen(ed::Editor)
|
||||||
|
|
||||||
|
# Update terminal size
|
||||||
|
ed.height = Base.Terminals.height(ed.term) - 2 # status + msg bar = 2
|
||||||
|
ed.width = Base.Terminals.width(ed.term)
|
||||||
|
|
||||||
|
scroll(ed)
|
||||||
|
|
||||||
|
buf = IOBuffer()
|
||||||
|
|
||||||
|
write(buf, "\x1b[?25l") # ?25l: Hide cursor
|
||||||
|
write(buf, "\x1b[H") # H: Move cursor to top left
|
||||||
|
|
||||||
|
drawRows(ed, buf)
|
||||||
|
drawStatusBar(ed, buf)
|
||||||
|
drawStatusMessage(ed, buf)
|
||||||
|
|
||||||
|
@printf(buf, "\x1b[%d;%dH", ed.csr.y-ed.rowoff,
|
||||||
|
ed.csr.rx-ed.coloff)
|
||||||
|
|
||||||
|
write(buf, "\x1b[?25h") # ?25h: Show cursor
|
||||||
|
|
||||||
|
write(STDOUT, String(take!(buf)))
|
||||||
|
end
|
||||||
|
|
||||||
|
function editorPrompt(ed::Editor, prompt::String;
|
||||||
|
callback=nothing,
|
||||||
|
buf::String="",
|
||||||
|
showcursor::Bool=true) ::String
|
||||||
|
while true
|
||||||
|
statusmsg = string(prompt, buf)
|
||||||
|
setStatusMessage(ed, string(prompt, buf))
|
||||||
|
refreshScreen(ed)
|
||||||
|
|
||||||
|
if showcursor
|
||||||
|
# Position the cursor at the end of the line
|
||||||
|
@printf(STDOUT, "\x1b[%d;%dH", 999, length(statusmsg)+1)
|
||||||
|
end
|
||||||
|
|
||||||
|
c = Char(readKey())
|
||||||
|
|
||||||
|
if c == '\x1b'
|
||||||
|
setStatusMessage(ed, "")
|
||||||
|
callback != nothing && callback(ed, buf, c)
|
||||||
|
return ""
|
||||||
|
elseif c == '\r'
|
||||||
|
if length(buf) != 0
|
||||||
|
setStatusMessage(ed, "")
|
||||||
|
callback != nothing && callback(ed, buf, c)
|
||||||
|
return buf
|
||||||
|
end
|
||||||
|
elseif UInt32(c) == BACKSPACE && length(buf) > 0
|
||||||
|
buf = buf[1:end-1]
|
||||||
|
elseif !iscntrl(c) && UInt32(c) < 128
|
||||||
|
buf = string(buf, c)
|
||||||
|
end
|
||||||
|
|
||||||
|
callback != nothing && callback(ed, buf, c)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##############
|
||||||
|
## Keyboard ##
|
||||||
|
##############
|
||||||
|
|
||||||
|
|
||||||
|
function processKeypress(ed::Editor)
|
||||||
|
c = readKey();
|
||||||
|
|
||||||
|
if c == ctrl_key('p')
|
||||||
|
runCommand(ed)
|
||||||
|
elseif (c == ARROW_LEFT
|
||||||
|
|| c == ARROW_UP
|
||||||
|
|| c == ARROW_RIGHT
|
||||||
|
|| c == ARROW_DOWN)
|
||||||
|
moveCursor(ed, c)
|
||||||
|
elseif c == PAGE_UP || c == PAGE_DOWN
|
||||||
|
lines = ed.height
|
||||||
|
while (lines-=1) > 0
|
||||||
|
moveCursor(ed, c == PAGE_UP ? ARROW_UP : ARROW_DOWN)
|
||||||
|
end
|
||||||
|
elseif c == HOME_KEY
|
||||||
|
ed.csr.x = 0
|
||||||
|
elseif c == END_KEY
|
||||||
|
ed.csr.y < length(ed.rows) && (ed.csr.x = length(ed.rows[ed.csr.y].chars))
|
||||||
|
elseif c == UInt32('\r')
|
||||||
|
editorInsertNewline(ed)
|
||||||
|
elseif c == BACKSPACE
|
||||||
|
editorDelChar(ed)
|
||||||
|
elseif c == DEL_KEY
|
||||||
|
moveCursor(ed, ARROW_RIGHT)
|
||||||
|
editorDelChar(ed)
|
||||||
|
elseif c == ctrl_key('l')
|
||||||
|
# Refresh screen
|
||||||
|
return
|
||||||
|
elseif c == UInt32('\x1b')
|
||||||
|
return
|
||||||
|
elseif iscntrl(Char(c)) && isKeyBound(Char(c))
|
||||||
|
runCommand(ed, getKeyBinding(Char(c)))
|
||||||
|
elseif c == UInt32('\t')
|
||||||
|
editorInsertTab(ed)
|
||||||
|
elseif !iscntrl(Char(c))
|
||||||
|
editorInsertChar(ed, c)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
#####################
|
||||||
|
# Editor Operations #
|
||||||
|
#####################
|
||||||
|
|
||||||
|
function editorInsertChar(ed::Editor, c::UInt32)
|
||||||
|
# The cursor is able to move beyond the last row
|
||||||
|
ed.csr.y == length(ed.rows)+1 && appendRow(ed.rows, "")
|
||||||
|
insertChar!(ed.rows[ed.csr.y], ed.csr.x, Char(c))
|
||||||
|
ed.csr.x += 1
|
||||||
|
ed.dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function editorInsertTab(ed::Editor)
|
||||||
|
# The cursor is able to move beyond the last row
|
||||||
|
ed.csr.y == length(ed.rows)+1 && appendRow(ed.rows, "")
|
||||||
|
|
||||||
|
# Insert character(s) into the row data
|
||||||
|
mv_fwd = insertTab!(ed.rows[ed.csr.y], ed.csr.x)
|
||||||
|
|
||||||
|
ed.csr.x += mv_fwd
|
||||||
|
|
||||||
|
ed.dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function editorDelChar(ed::Editor)
|
||||||
|
ed.csr.y == length(ed.rows)+1 && return
|
||||||
|
ed.csr.x == 1 && ed.csr.y == 1 && return
|
||||||
|
|
||||||
|
if ed.csr.x > 1
|
||||||
|
deleteChar!(ed.rows[ed.csr.y], ed.csr.x -1)
|
||||||
|
ed.csr.x -= 1
|
||||||
|
ed.dirty = true
|
||||||
|
else
|
||||||
|
# Move cursor to end of prev line
|
||||||
|
ed.csr.x = 1+length(ed.rows[ed.csr.y-1].chars)
|
||||||
|
appendRowString(ed.rows[ed.csr.y-1], ed.rows[ed.csr.y].chars)
|
||||||
|
editorDelRow(ed, ed.csr.y)
|
||||||
|
ed.csr.y -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function editorInsertRow(ed::Editor, i::Int, str::String)
|
||||||
|
row = Row(str)
|
||||||
|
update!(row)
|
||||||
|
insert!(ed.rows, i, row)
|
||||||
|
end
|
||||||
|
|
||||||
|
function editorInsertNewline(ed::Editor)
|
||||||
|
if ed.csr.x == 1
|
||||||
|
editorInsertRow(ed, ed.csr.y, "")
|
||||||
|
else
|
||||||
|
row = ed.rows[ed.csr.y]
|
||||||
|
before = row.chars[1:ed.csr.x-1]
|
||||||
|
after = row.chars[ed.csr.x:end]
|
||||||
|
editorInsertRow(ed, ed.csr.y + 1, after)
|
||||||
|
row.chars = before
|
||||||
|
update!(row)
|
||||||
|
end
|
||||||
|
ed.csr.y += 1
|
||||||
|
ed.csr.x = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function editorDelRow(ed::Editor, i::Int)
|
||||||
|
i < 1 || i > length(ed.rows) && return
|
||||||
|
deleteat!(ed.rows, i)
|
||||||
|
ed.dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function editorQuit(ed::Editor; force::Bool=false)
|
||||||
|
if !force && ed.dirty
|
||||||
|
setStatusMessage(ed,
|
||||||
|
"File has unsaved changes. Save changes of use 'quit !' to quit anyway")
|
||||||
|
else
|
||||||
|
write(STDOUT, "\x1b[2J")
|
||||||
|
write(STDOUT, "\x1b[H")
|
||||||
|
ed.quit = true
|
||||||
|
!isinteractive() && exit(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
# COMMANDS #
|
||||||
|
############
|
||||||
|
|
||||||
|
|
||||||
|
function runCommand(ed::Editor)
|
||||||
|
cmd = editorPrompt(ed, "> ")
|
||||||
|
runCommand(ed, strip(cmd))
|
||||||
|
end
|
||||||
|
|
||||||
|
function runCommand(ed::Editor, command_str::String)
|
||||||
|
cmd_arr = split(command_str, ' ', limit=2)
|
||||||
|
|
||||||
|
# Get the command
|
||||||
|
cmd = strip(cmd_arr[1])
|
||||||
|
|
||||||
|
# Blank, do nothing
|
||||||
|
cmd == "" && return
|
||||||
|
|
||||||
|
# Command must be a valid identifier
|
||||||
|
if !Base.isidentifier(cmd)
|
||||||
|
setStatusMessage(ed, "'$sym' is not a valid command name")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
cmd_sym = Symbol(cmd)
|
||||||
|
|
||||||
|
# Get arguments if there are any
|
||||||
|
args = ""
|
||||||
|
if length(cmd_arr) > 1
|
||||||
|
args = cmd_arr[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
# If the command exists, run it
|
||||||
|
if cmd_sym in keys(COMMANDS)
|
||||||
|
# join(args): convert Substring to String
|
||||||
|
runCommand(COMMANDS[cmd_sym], ed, join(args))
|
||||||
|
else
|
||||||
|
setStatusMessage(ed, "'$sym' is not a valid command")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function runCommand(c::Command, ed::Editor, args::String)
|
||||||
|
c.cmd(ed, args)
|
||||||
|
end
|
|
@ -0,0 +1,160 @@
|
||||||
|
#############
|
||||||
|
# ROW #
|
||||||
|
#############
|
||||||
|
|
||||||
|
""" Holds file true data and render data """
|
||||||
|
mutable struct Row
|
||||||
|
# file data, a row of text
|
||||||
|
chars::String
|
||||||
|
# a row of text as rendered on screen
|
||||||
|
render::String
|
||||||
|
end
|
||||||
|
|
||||||
|
Row(s::String) = Row(s, "")
|
||||||
|
|
||||||
|
""" Update the `render` string using the `chars` string """
|
||||||
|
function update!(row::Row)
|
||||||
|
# Count the number of tabs
|
||||||
|
tabs = 0
|
||||||
|
for c in row.chars
|
||||||
|
c == '\t' && (tabs += 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Allocate an array of characters
|
||||||
|
updated = Array{Char, 1}(length(row.chars) + tabs*(configGet(:tab_stop)-1))
|
||||||
|
|
||||||
|
# copy the characters into the updated array
|
||||||
|
idx = 1
|
||||||
|
for i in 1:length(row.chars)
|
||||||
|
|
||||||
|
# replace tabs with spaces
|
||||||
|
if row.chars[i] == '\t'
|
||||||
|
updated[idx] = ' '
|
||||||
|
idx += 1
|
||||||
|
while idx % configGet(:tab_stop) != 0;
|
||||||
|
updated[idx] = ' ';
|
||||||
|
idx += 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
updated[idx] = row.chars[i]
|
||||||
|
idx += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
row.render = join(updated[1:idx-1])
|
||||||
|
end
|
||||||
|
|
||||||
|
"""Convert a cursor position in the document to a rendered cursor position"""
|
||||||
|
function renderX(row::Row, cx::Int)
|
||||||
|
rx = 1
|
||||||
|
for i in 1:cx-1
|
||||||
|
# If there is a tab, move the cursor forward to the tab stop
|
||||||
|
if row.chars[i] == '\t'
|
||||||
|
rx += (configGet(:tab_stop) - 1) - (rx % configGet(:tab_stop))
|
||||||
|
end
|
||||||
|
rx += 1
|
||||||
|
end
|
||||||
|
rx
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
"""opposite of renderX: Convert a render position to a chars position"""
|
||||||
|
function charX(row::Row, rx::Int)
|
||||||
|
cur_rx = 1
|
||||||
|
for cx = 1:length(row.chars)
|
||||||
|
if row.chars[cx] == '\t'
|
||||||
|
cur_rx += (configGet(:tab_stop) - 1) - (cur_rx % configGet(:tab_stop))
|
||||||
|
end
|
||||||
|
cur_rx += 1
|
||||||
|
|
||||||
|
cur_rx > rx && return cx
|
||||||
|
end
|
||||||
|
cx
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
""" Insert a char into a string """
|
||||||
|
function insert(s::String, i::Int, c::Union{Char,String})
|
||||||
|
if s == ""
|
||||||
|
string(c)
|
||||||
|
else
|
||||||
|
string(s[1:i-1], c, s[i:end])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
""" Delete char from string """
|
||||||
|
function delete(s::String, i::Int)
|
||||||
|
i < 1 || i > length(s) && return s
|
||||||
|
string(s[1:i-1], s[i+1:end])
|
||||||
|
end
|
||||||
|
|
||||||
|
""" Insert a char into the row at a given location """
|
||||||
|
function insertChar!(row::Row, i::Int, c::Char)
|
||||||
|
row.chars = insert(row.chars, i, c)
|
||||||
|
update!(row)
|
||||||
|
end
|
||||||
|
|
||||||
|
""" Insert a tab into the row at a given location. Return the number of chars inserted """
|
||||||
|
function insertTab!(row::Row, i::Int)
|
||||||
|
num_chars = 1
|
||||||
|
t = '\t'
|
||||||
|
|
||||||
|
# If we are using spaces, move ahead more
|
||||||
|
if configGet(:expandtab)
|
||||||
|
num_chars = configGet(:tab_stop) - (i % configGet(:tab_stop))
|
||||||
|
t = repeat(" ", num_chars)
|
||||||
|
end
|
||||||
|
|
||||||
|
row.chars = insert(row.chars, i, t)
|
||||||
|
update!(row)
|
||||||
|
|
||||||
|
# Used for positioning the cursor
|
||||||
|
return num_chars
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
""" Delete char from row """
|
||||||
|
function deleteChar!(row::Row, i::Int)
|
||||||
|
row.chars = delete(row.chars, i)
|
||||||
|
update!(row)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Add a row to the end of the document
|
||||||
|
initialize the row with the given string
|
||||||
|
"""
|
||||||
|
function appendRowString(row::Row, str::String)
|
||||||
|
row.chars = string(row.chars, str)
|
||||||
|
update!(row)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
########
|
||||||
|
# ROWS #
|
||||||
|
########
|
||||||
|
|
||||||
|
""" type alias for Array{Row, 1} """
|
||||||
|
Rows = Array{Row, 1}
|
||||||
|
|
||||||
|
""" delete all rows from a Rows """
|
||||||
|
function clearRows(rows::Rows)
|
||||||
|
while length(rows) > 0
|
||||||
|
pop!(rows)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
""" Add a row to the end of the document """
|
||||||
|
function appendRow(rows::Rows, s::String)
|
||||||
|
row = Row(s)
|
||||||
|
update!(row)
|
||||||
|
push!(rows, row)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
""" Convert all ROW data to a single string """
|
||||||
|
function rowsToString(rows::Rows)
|
||||||
|
join(map(row -> row.chars, rows), '\n')
|
||||||
|
end
|
|
@ -0,0 +1,127 @@
|
||||||
|
import Base.==
|
||||||
|
|
||||||
|
@enum(Key,
|
||||||
|
BACKSPACE = 127,
|
||||||
|
ARROW_LEFT = 1000,
|
||||||
|
ARROW_RIGHT,
|
||||||
|
ARROW_UP,
|
||||||
|
ARROW_DOWN,
|
||||||
|
DEL_KEY,
|
||||||
|
HOME_KEY,
|
||||||
|
END_KEY,
|
||||||
|
PAGE_UP,
|
||||||
|
PAGE_DOWN,
|
||||||
|
S_ARROW_UP,
|
||||||
|
S_ARROW_DOWN,
|
||||||
|
S_ARROW_LEFT,
|
||||||
|
S_ARROW_RIGHT,
|
||||||
|
C_ARROW_UP,
|
||||||
|
C_ARROW_DOWN,
|
||||||
|
C_ARROW_LEFT,
|
||||||
|
C_ARROW_RIGHT)
|
||||||
|
|
||||||
|
==(c::UInt32, k::Key) = c == UInt32(k)
|
||||||
|
==(k::Key, c::UInt32) = c == UInt32(k)
|
||||||
|
==(c::Char, k::Key) = UInt32(c) == UInt32(k)
|
||||||
|
==(k::Key, c::Char) = UInt32(c) == UInt32(k)
|
||||||
|
|
||||||
|
ctrl_key(c::Char)::UInt32 = UInt32(c) & 0x1f
|
||||||
|
|
||||||
|
readNextChar() = Char(read(STDIN,1)[1])
|
||||||
|
|
||||||
|
function readKey() ::UInt32
|
||||||
|
c = readNextChar()
|
||||||
|
|
||||||
|
# Escape characters
|
||||||
|
if c == '\x1b'
|
||||||
|
STDIN.buffer.size < 3 && return '\x1b'
|
||||||
|
esc_a = readNextChar()
|
||||||
|
esc_b = readNextChar()
|
||||||
|
|
||||||
|
if esc_a == '['
|
||||||
|
if esc_b >= '0' && esc_b <= '9'
|
||||||
|
STDIN.buffer.size < 4 && return '\x1b'
|
||||||
|
esc_c = readNextChar()
|
||||||
|
|
||||||
|
if esc_c == '~'
|
||||||
|
if esc_b == '1'
|
||||||
|
return HOME_KEY
|
||||||
|
elseif esc_b == '4'
|
||||||
|
return END_KEY
|
||||||
|
elseif esc_b == '3'
|
||||||
|
return DEL_KEY
|
||||||
|
elseif esc_b == '5'
|
||||||
|
return PAGE_UP
|
||||||
|
elseif esc_b == '6'
|
||||||
|
return PAGE_DOWN
|
||||||
|
elseif esc_b == '7'
|
||||||
|
return HOME_KEY
|
||||||
|
elseif esc_b == '8'
|
||||||
|
return END_KEY
|
||||||
|
else
|
||||||
|
return '\x1b'
|
||||||
|
end
|
||||||
|
elseif esc_c == ';'
|
||||||
|
STDIN.buffer.size < 6 && return '\x1b'
|
||||||
|
esc_d = readNextChar()
|
||||||
|
esc_e = readNextChar()
|
||||||
|
|
||||||
|
if esc_d == '2'
|
||||||
|
# shift + arrorw
|
||||||
|
if esc_e == 'A'
|
||||||
|
return S_ARROW_UP
|
||||||
|
elseif esc_e == 'B'
|
||||||
|
return S_ARROW_DOWN
|
||||||
|
elseif esc_e == 'C'
|
||||||
|
return S_ARROW_RIGHT
|
||||||
|
elseif esc_e == 'D'
|
||||||
|
return S_ARROW_LEFT
|
||||||
|
else
|
||||||
|
return '\x1b'
|
||||||
|
end
|
||||||
|
elseif esc_d == '5'
|
||||||
|
# Ctrl + arrow
|
||||||
|
if esc_e == 'A'
|
||||||
|
return C_ARROW_UP
|
||||||
|
elseif esc_e == 'B'
|
||||||
|
return C_ARROW_DOWN
|
||||||
|
elseif esc_e == 'C'
|
||||||
|
return C_ARROW_RIGHT
|
||||||
|
elseif esc_e == 'D'
|
||||||
|
return C_ARROW_LEFT
|
||||||
|
else
|
||||||
|
return '\x1b'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# Arrow keys
|
||||||
|
if esc_b == 'A'
|
||||||
|
return ARROW_UP
|
||||||
|
elseif esc_b == 'B'
|
||||||
|
return ARROW_DOWN
|
||||||
|
elseif esc_b == 'C'
|
||||||
|
return ARROW_RIGHT
|
||||||
|
elseif esc_b == 'D'
|
||||||
|
return ARROW_LEFT
|
||||||
|
elseif esc_b == 'H'
|
||||||
|
return HOME_KEY
|
||||||
|
elseif esc_b == 'F'
|
||||||
|
return END_KEY
|
||||||
|
else
|
||||||
|
return '\x1b'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif esc_a == 'O'
|
||||||
|
if esc_a == 'H'
|
||||||
|
return HOME_KEY
|
||||||
|
elseif esc_a == 'F'
|
||||||
|
return END_KEY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return '\x1b'
|
||||||
|
else
|
||||||
|
return c;
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue