From 02f5553e344767e71677ae380af090d22965cdc6 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 22 Mar 2014 15:25:31 -0400 Subject: [PATCH 01/11] import REPL.jl code Fixes #1773 (Repl autocompletion for composite type fields). Fixes #3652 (Segfault during TAB-completion on OSX). Fixes #4477 (Tab completion for `using` should look up installed packages). --- base/REPL.jl | 661 +++++++++++++++++++++++ base/REPLCompletions.jl | 187 +++++++ base/Readline.jl | 1137 +++++++++++++++++++++++++++++++++++++++ base/Terminals.jl | 221 ++++++++ test/readline.jl | 46 ++ test/replcompletions.jl | 98 ++++ 6 files changed, 2350 insertions(+) create mode 100644 base/REPL.jl create mode 100644 base/REPLCompletions.jl create mode 100644 base/Readline.jl create mode 100644 base/Terminals.jl create mode 100644 test/readline.jl create mode 100644 test/replcompletions.jl diff --git a/base/REPL.jl b/base/REPL.jl new file mode 100644 index 0000000000000..6829ade7ae890 --- /dev/null +++ b/base/REPL.jl @@ -0,0 +1,661 @@ +module REPL + + # compatibility for julia version 0.2 + if !isdefined(:put!) + const put! = put + const take! = take + end + + export StreamREPL, BasicREPL + + abstract AbstractREPL + + type REPLBackend + repl_channel::RemoteRef + response_channel::RemoteRef + ans + end + + using Base.Meta + + function eval_user_input(ast::ANY, backend) + iserr, lasterr, bt = false, (), nothing + while true + try + if iserr + put!(backend.response_channel,(lasterr,bt)) + iserr, lasterr = false, () + else + ast = expand(ast) + ans = Base.Meta.quot(backend.ans) + eval(Main,:(ans=$(ans))) + value = eval(Main,ast) + backend.ans = value + put!(backend.response_channel,(value,nothing)) + end + break + catch err + if iserr + println("SYSTEM ERROR: Failed to report error to REPL frontend") + println(err) + end + iserr, lasterr = true, err + bt = catch_backtrace() + end + end + end + + function parse_input_line(s::String) + # s = bytestring(s) + # (expr, pos) = parse(s, 1) + # (ex, pos) = ccall(:jl_parse_string, Any, + # (Ptr{Uint8},Int32,Int32), + # s, int32(pos)-1, 1) + # if !is(ex,()) + # throw(ParseError("extra input after end of expression")) + # end + # expr + ccall(:jl_parse_input_line, Any, (Ptr{Uint8},), s) + end + + function start_repl_backend(repl_channel, response_channel) + backend = REPLBackend(repl_channel,response_channel,nothing) + @async begin + # include looks at this to determine the relative include path + # nothing means cwd + while true + tls = task_local_storage() + tls[:SOURCE_PATH] = nothing + (ast,show_value) = take!(backend.repl_channel) + if show_value == -1 + # exit flag + break + end + eval_user_input(ast, backend) + end + + end + end + + function display_error(io::IO, er, bt) + Base.with_output_color(:red, io) do io + print(io, "ERROR: ") + Base.showerror(io, er, bt) + end + end + + import Base: Display, display, writemime + + immutable REPLDisplay <: Display + repl::AbstractREPL + end + function display(d::REPLDisplay, ::MIME"text/plain", x) + io = outstream(d.repl) + write(io,answer_color(d.repl)) + writemime(io, MIME("text/plain"), x) + println(io) + end + display(d::REPLDisplay, x) = display(d, MIME("text/plain"), x) + + function print_response(d::REPLDisplay,errio::IO,r::AbstractREPL,val::ANY, bt, show_value, have_color) + while true + try + if !is(bt,nothing) + display_error(errio,val,bt) + println(errio) + iserr, lasterr = false, () + else + if !is(val,nothing) && show_value + try display(d,val) + catch err + println(errio,"Error showing value of type ", typeof(val), ":") + rethrow(err) + end + end + end + break + catch err + if !is(bt,nothing) + println(errio,"SYSTEM: show(lasterr) caused an error") + break + end + val = err + bt = catch_backtrace() + end + end + end + + using Terminals + using Readline + + import Readline: char_move_left, char_move_word_left, CompletionProvider, completeLine + + type ReadlineREPL <: AbstractREPL + t::TextTerminal + prompt_color::String + input_color::String + answer_color::String + shell_color::String + help_color::String + in_shell::Bool + in_help::Bool + consecutive_returns + end + outstream(r::ReadlineREPL) = r.t + + ReadlineREPL(t::TextTerminal) = ReadlineREPL(t,julia_green,Base.input_color(),Base.answer_color(),Base.text_colors[:red],Base.text_colors[:yellow],false,false,0) + + type REPLCompletionProvider <: CompletionProvider + r::ReadlineREPL + end + + type ShellCompletionProvider <: CompletionProvider + r::ReadlineREPL + end + + using REPLCompletions + + function completeLine(c::REPLCompletionProvider,s) + partial = bytestring(s.input_buffer.data[1:position(s.input_buffer)]) + ret, range = completions(partial,endof(partial)) + return (ret,partial[range]) + end + + function completeLine(c::ShellCompletionProvider,s) + # First parse everything up to the current position + partial = bytestring(s.input_buffer.data[1:position(s.input_buffer)]) + ret, range = shell_completions(partial,endof(partial)) + return (ret, partial[range]) + end + + import Readline: HistoryProvider, add_history, history_prev, history_next, history_search + + type REPLHistoryProvider <: HistoryProvider + history::Array{String,1} + history_file + cur_idx::Int + last_buffer::IOBuffer + last_mode + mode_mapping + modes::Array{Uint8,1} + end + + function hist_from_file(file,mode_mapping) + hp = REPLHistoryProvider(String[],file,0,IOBuffer(),nothing,mode_mapping,Uint8[]) + seek(file,0) + while !eof(file) + b = readuntil(file,'\0') + if uint8(b[1]) in keys(mode_mapping) + push!(hp.modes,uint8(b[1])) + push!(hp.history,b[2:(end-1)]) # Strip trailing \0 + else # For history backward compatibility + push!(hp.modes,0) + push!(hp.history,b[1:(end-1)]) # Strip trailing \0 + end + end + seekend(file) + hp + end + + function mode_idx(hist::REPLHistoryProvider,mode) + c::Uint8 = 0 + for (k,v) in hist.mode_mapping + if k == uint8('\0') + continue + elseif v == mode + c = k + break + end + end + @assert c != 0 + return c + end + + function add_history(hist::REPLHistoryProvider,s) + # bytestring copies + str = bytestring(pointer(s.input_buffer.data),s.input_buffer.size) + if isempty(strip(str)) # Do not add empty strings to the history + return + end + push!(hist.history,str) + c = mode_idx(hist,Readline.mode(s)) + push!(hist.modes,c) + write(hist.history_file,c) + write(hist.history_file,str) + write(hist.history_file,'\0') + flush(hist.history_file) + end + + function history_adjust(hist::REPLHistoryProvider,s) + if 0 < hist.cur_idx <= length(hist.history) + hist.history[hist.cur_idx] = Readline.input_string(s) + hist.modes[hist.cur_idx] = mode_idx(hist,Readline.mode(s)) + end + end + + function history_prev(s::Readline.MIState,hist::REPLHistoryProvider) + if hist.cur_idx > 1 + if hist.cur_idx == length(hist.history)+1 + hist.last_mode = Readline.mode(s) + hist.last_buffer = copy(Readline.buffer(s)) + else + history_adjust(hist,s) + end + hist.cur_idx-=1 + Readline.transition(s,hist.mode_mapping[hist.modes[hist.cur_idx]]) + Readline.replace_line(s,hist.history[hist.cur_idx]) + Readline.refresh_line(s) + else + Terminals.beep(Readline.terminal(s)) + end + end + + function history_next(s::Readline.MIState,hist::REPLHistoryProvider) + if hist.cur_idx < length(hist.history) + history_adjust(hist,s) + hist.cur_idx+=1 + Readline.transition(s,hist.mode_mapping[hist.modes[hist.cur_idx]]) + Readline.replace_line(s,hist.history[hist.cur_idx]) + Readline.refresh_line(s) + elseif hist.cur_idx == length(hist.history) + hist.cur_idx+=1 + buf = hist.last_buffer + hist.last_buffer = IOBuffer() + Readline.transition(s,hist.last_mode) + Readline.replace_line(s,buf) + Readline.refresh_line(s) + else + Terminals.beep(Readline.terminal(s)) + end + end + + function history_search(hist::REPLHistoryProvider,query_buffer::IOBuffer,response_buffer::IOBuffer,backwards::Bool=false, skip_current::Bool=false) + if !(query_buffer.ptr > 1) + #truncate(response_buffer,0) + return true + end + + # Alright, first try to see if the current match still works + searchdata = bytestring(query_buffer.data[1:(query_buffer.ptr-1)]) + pos = position(response_buffer) + if !skip_current && !((response_buffer.size == 0) || (pos+query_buffer.ptr-2 == 0)) && + (response_buffer.size >= (pos+query_buffer.ptr-2)) && (pos != 0) && + (searchdata == bytestring(response_buffer.data[pos:(pos+query_buffer.ptr-2)])) + return true + end + + # Start searching + # First the current response buffer + match = backwards ? + rsearch(bytestring(response_buffer.data[1:response_buffer.size]),searchdata,response_buffer.ptr - 1): + response_buffer.ptr + 1 < response_buffer.size ? + search(bytestring(response_buffer.data[1:response_buffer.size]),searchdata,response_buffer.ptr + 1): 0:-1 + + if match != 0:-1 + seek(response_buffer,first(match)-1) + return true + end + + # Now search all the other buffers + idx = hist.cur_idx + found = false + while true + idx += backwards ? -1 : 1 + if !(0 < idx <= length(hist.history)) + break + end + match = backwards ? rsearch(hist.history[idx],searchdata): + search(hist.history[idx],searchdata); + if match != 0:-1 + found = true + truncate(response_buffer,0) + write(response_buffer,hist.history[idx]) + seek(response_buffer,first(match)-1) + break + end + end + if found + #if hist.cur_idx == length(hist.history)+1 + # hist.last_buffer = copy(s.input_buffer) + #end + hist.cur_idx = idx + end + return found + end + + function history_reset_state(hist::REPLHistoryProvider) + hist.cur_idx = length(hist.history)+1 + end + Readline.reset_state(hist::REPLHistoryProvider) = history_reset_state(hist) + + const julia_green = "\033[1m\033[32m" + const color_normal = Base.color_normal + + function return_callback(repl,s) + if position(s.input_buffer) != 0 && eof(s.input_buffer) && + (seek(s.input_buffer,position(s.input_buffer)-1);read(s.input_buffer,Uint8)=='\n') + repl.consecutive_returns += 1 + else + repl.consecutive_returns = 0 + end + ast = parse_input_line(bytestring(copy(s.input_buffer.data))) + if repl.consecutive_returns > 1 || !isa(ast,Expr) || (ast.head != :continue && ast.head != :incomplete) + return true + else + return false + end + end + + function find_hist_file() + if isfile(".julia_history2") + return ".julia_history2" + elseif haskey(ENV,"JULIA_HISTORY") + return ENV["JULIA_HISTORY"] + else + @windows_only return ENV["AppData"]*"/julia/history2" + @unix_only return ENV["HOME"]*"/.julia_history2" + end + end + + function send_to_backend(ast,req,rep) + put!(req, (ast,1)) + (val, bt) = take!(rep) + end + + have_color(s) = true + + function respond(f,d,main,req,rep) + (s,buf,ok)->begin + if !ok + return transition(s,:abort) + end + line = takebuf_string(buf) + if !isempty(line) + reset(d) + (val,bt) = send_to_backend(f(line),req,rep) + if !ends_with_semicolon(line) + print_response(d,val,bt,true,have_color(s)) + end + end + println(d.repl.t) + reset_state(s) + transition(s,main) + end + end + + import Terminals: raw! + + function reset(d::REPLDisplay) + raw!(d.repl.t,false) + print(Base.text_colors[:normal]) + end + + + + function setup_interface(d::REPLDisplay,req,rep;extra_repl_keymap=Dict{Any,Any}[]) + ### + # + # This function returns the main interface that describes the REPL + # functionality, it is called internally by functions that setup a + # Terminal-based REPL frontend, but if you want to customize your REPL + # or embed the REPL in another interface, you may call this function + # directly and append it to your interface. + # + # Usage: + # + # repl_channel,response_channel = RemoteRef(),RemoteRef() + # start_repl_backend(repl_channel, response_channel) + # setup_interface(REPLDisplay(t),repl_channel,response_channel) + # + ### + + ### + # We setup the interface in two stages. + # First, we set up all components (prompt,rsearch,shell,help) + # Second, we create keymaps with appropriate transitions between them + # and assign them to the components + # + ### + + ############################### Stage I ################################ + + repl = d.repl + + # This will provide completions for REPL and help mode + replc = REPLCompletionProvider(repl) + finalizer(replc,(replc)->close(f)) + + # Set up the main Julia prompt + main_prompt = Prompt("julia> "; + # Copy colors from the prompt object + prompt_color=repl.prompt_color, + input_color=repl.input_color, + keymap_func_data = repl, + complete=replc, + on_enter=s->return_callback(repl,s)) + + main_prompt.on_done = respond(Base.parse_input_line,d,main_prompt,req,rep) + + # Setup help mode + help_mode = Prompt("help> ", + prompt_color = repl.help_color, + input_color=repl.input_color, + keymap_func_data = repl, + complete = replc, + # When we're done transform the entered line into a call to help("$line") + on_done = respond(d,main_prompt,req,rep) do line + parse("Base.@help $line") + end) + + # Set up shell mode + shell_mode = Prompt("shell> "; + prompt_color = repl.shell_color, + input_color=repl.input_color, + keymap_func_data = repl, + complete = ShellCompletionProvider(repl), + # Transform "foo bar baz" into `foo bar baz` (shell quoting) + # and pass into Base.repl_cmd for processing (handles `ls` and `cd` + # special) + on_done = respond(d,main_prompt,req,rep) do line + Expr(:call, :(Base.repl_cmd), macroexpand(Expr(:macrocall,symbol("@cmd"),line))) + end) + + ################################# Stage II ############################# + + # Setup history + # We will have a unified history for all REPL modes + f = open(find_hist_file(),true,true,true,false,false) + hp = hist_from_file(f,(Uint8=>Any)[uint8('\0') => main_prompt, uint8(';') => shell_mode, uint8('?') => help_mode, uint8('>') => main_prompt]) + history_reset_state(hp) + main_prompt.hist = hp + shell_mode.hist = hp + help_mode.hist = hp + + (hkp,hkeymap) = Readline.setup_search_keymap(hp) + + # Canoniczlize user keymap input + if isa(extra_repl_keymap,Dict) + extra_repl_keymap = [extra_repl_keymap] + end + + + const repl_keymap = { + ';' => function (s) + if isempty(s) || position(Readline.buffer(s)) == 0 + buf = copy(Readline.buffer(s)) + transition(s,shell_mode) + Readline.state(s,shell_mode).input_buffer = buf + Readline.refresh_line(s) + else + edit_insert(s,';') + end + end, + '?' => function (s) + if isempty(s) || position(Readline.buffer(s)) == 0 + buf = copy(Readline.buffer(s)) + transition(s,help_mode) + Readline.state(s,help_mode).input_buffer = buf + Readline.refresh_line(s) + else + edit_insert(s,'?') + end + end, + + # Bracketed Paste Mode + "\e[200~" => s->begin + ps = Readline.state(s,Readline.mode(s)) + input = readuntil(ps.terminal,"\e[201~")[1:(end-6)] + input = replace(input,'\r','\n') + if position(Readline.buffer(s)) == 0 + indent = Base.indentation(input)[1] + input = Base.unindent(input[(indent+1):end],indent) + end + buf = copy(Readline.buffer(s)) + edit_insert(buf,input) + string = takebuf_string(buf) + pos = 0 + while pos <= length(string) + oldpos = pos + ast, pos = Base.parse(string,pos; ) + # Get the line and strip leading and trailing whitespace + line = strip(string[max(oldpos,1):min(pos-1,length(string))]) + Readline.replace_line(s,line) + Readline.refresh_line(s) + if !isa(ast,Expr) || (ast.head != :continue && ast.head != :incomplete) + Readline.commit_line(s) + # This is slightly ugly but ok for now + terminal = Readline.terminal(s) + stop_reading(terminal) + raw!(terminal,false) && Terminals.Unix.disable_bracketed_paste(terminal) + Readline.mode(s).on_done(s,Readline.buffer(s),true) + raw!(terminal,true) && Terminals.Unix.enable_bracketed_paste(terminal) + start_reading(terminal) + else + break + end + end + end, + } + + a = Dict{Any,Any}[hkeymap, repl_keymap, Readline.history_keymap(hp), Readline.default_keymap,Readline.escape_defaults] + prepend!(a,extra_repl_keymap) + @eval @Readline.keymap repl_keymap_func $(a) + + main_prompt.keymap_func = repl_keymap_func + + const mode_keymap = { + '\b' => s->(isempty(s) ? transition(s,main_prompt) : Readline.edit_backspace(s) ) + } + + b = Dict{Any,Any}[hkeymap, mode_keymap, Readline.history_keymap(hp), Readline.default_keymap,Readline.escape_defaults] + + @eval @Readline.keymap mode_keymap_func $(b) + + shell_mode.keymap_func = help_mode.keymap_func = mode_keymap_func + + ModalInterface([main_prompt,shell_mode,help_mode,hkp]) + end + + run_frontend(repl::ReadlineREPL,repl_channel,response_channel) = run_interface(repl.t,setup_interface(REPLDisplay(repl),repl_channel,response_channel)) + + if isdefined(Base,:banner_color) + banner(io,t) = banner(io,hascolor(t)) + banner(io,x::Bool) = print(io,x ? Base.banner_color : Base.banner_plain) + else + banner(io,t) = Base.banner(io) + end + + function run_repl(t::TextTerminal) + repl_channel = RemoteRef() + response_channel = RemoteRef() + start_repl_backend(repl_channel, response_channel) + banner(t,t) + run_frontend(ReadlineREPL(t),repl_channel,response_channel) + end + + type BasicREPL <: AbstractREPL + end + + outstream(::BasicREPL) = STDOUT + + type StreamREPL <: AbstractREPL + stream::IO + prompt_color::String + input_color::String + answer_color::String + end + + import Base.AsyncStream + + outstream(s::StreamREPL) = s.stream + + StreamREPL(stream::AsyncStream) = StreamREPL(stream,julia_green,Base.text_colors[:white],Base.answer_color()) + + answer_color(r::ReadlineREPL) = r.answer_color + answer_color(r::StreamREPL) = r.answer_color + answer_color(::BasicREPL) = Base.text_colors[:white] + + print_response(d::REPLDisplay,r::StreamREPL,args...) = print_response(d, r.stream,r, args...) + print_response(d::REPLDisplay,r::ReadlineREPL,args...) = print_response(d, r.t, r, args...) + print_response(d::REPLDisplay,args...) = print_response(d,d.repl,args...) + + function run_repl(stream::AsyncStream) + repl = + @async begin + repl_channel = RemoteRef() + response_channel = RemoteRef() + start_repl_backend(repl_channel,response_channel) + StreamREPL_frontend(repl,repl_channel,response_channel) + end + repl + end + + function ends_with_semicolon(line) + match = rsearch(line,';') + if match != 0 + for c in line[(match+1):end] + if !isspace(c) + return c == '#' + end + end + return true + end + return false + end + + function run_frontend(repl::StreamREPL,repl_channel,response_channel) + have_color = true + banner(repl.stream,have_color) + d = REPLDisplay(repl) + while repl.stream.open + if have_color + print(repl.stream,repl.prompt_color) + end + print(repl.stream,"julia> ") + if have_color + print(repl.stream,repl.input_color) + end + line = readline(repl.stream) + if !isempty(line) + ast = Base.parse_input_line(line) + if have_color + print(repl.stream,color_normal) + end + put!(repl_channel, (ast,1)) + (val, bt) = take!(response_channel) + if !ends_with_semicolon(line) + print_response(d,val,bt,true,have_color) + end + end + end + # Terminate Backend + put!(repl_channel,(nothing,-1)) + end + + function start_repl_server(port) + listen(port) do server, status + client = accept(server) + run_repl(client) + end + end +end diff --git a/base/REPLCompletions.jl b/base/REPLCompletions.jl new file mode 100644 index 0000000000000..b368dc4c2d332 --- /dev/null +++ b/base/REPLCompletions.jl @@ -0,0 +1,187 @@ +module REPLCompletions + + export completions, shell_completions + + using Base.Meta + + function completes_global(x, name) + return beginswith(x, name) && !in('#', x) + end + + # REPL Symbol Completions + function complete_symbol(sym,ffunc) + # Find module + strs = split(sym,".") + # Maybe be smarter in the future + context_module = Main + + mod = context_module + lookup_module = true + t = None + for name in strs[1:(end-1)] + s = symbol(name) + if lookup_module + if isdefined(mod,s) + b = mod.(s) + if isa(b,Module) + mod = b + elseif Base.isstructtype(typeof(b)) + lookup_module = false + t = typeof(b) + else + # A.B.C where B is neither a type nor a + # module. Will have to be revisited if + # overloading is allowed + return ASCIIString[] + end + else + # A.B.C where B doesn't exist in A. Give up + return ASCIIString[] + end + else + # We're now looking for a type + fields = t.names + found = false + for i in 1:length(fields) + if s == fields[i] + t = t.types[i] + if !Base.isstructtype(t) + return ASCIIString[] + end + found = true + break + end + end + if !found + #Same issue as above, but with types instead of modules + return ASCIIString[] + end + end + end + + name = strs[end] + + suggestions = String[] + if lookup_module + # We will exlcude the results that the user does not want, as well + # as excluding Main.Main.Main, etc., because that's most likely not what + # the user wants + p = s->(ffunc(mod,s) && s != module_name(mod)) + # Looking for a binding in a module + if mod == context_module + # Also look in modules we got through `using` + mods = ccall(:jl_module_usings,Any,(Any,),Main) + for m in mods + ssyms = names(m) + filter!(p,ssyms) + syms = map!(string,Array(UTF8String,length(ssyms)),ssyms) + append!(suggestions,syms[map((x)->completes_global(x,name),syms)]) + end + ssyms = names(mod,true,true) + filter!(p,ssyms) + syms = map!(string,Array(UTF8String,length(ssyms)),ssyms) + else + ssyms = names(mod,true,false) + filter!(p,ssyms) + syms = map!(string,Array(UTF8String,length(ssyms)),ssyms) + end + append!(suggestions,syms[map((x)->completes_global(x,name),syms)]) + else + # Looking for a member of a type + fields = t.names + for field in fields + s = string(field) + if beginswith(s,name) && + push!(suggestions,s) + end + end + end + suggestions + end + + const non_word_chars = " \t\n\"\\'`@\$><=:;|&{}()[].,+-*/?%^~" + + function completions(string,pos) + startpos = pos + dotpos = 0 + while startpos >= 1 + c = string[startpos] + if c < 0x80 && in(char(c), non_word_chars) + if c != '.' + startpos = nextind(string,startpos) + break + elseif dotpos == 0 + dotpos = startpos + end + end + if startpos == 1 + break + end + startpos = prevind(string,startpos) + end + ffunc = (mod,x)->true + suggestions = UTF8String[] + r = rsearch(string,"using",startpos) + if !isempty(r) && all(isspace,string[nextind(string,last(r)):prevind(string,startpos)]) + # We're right after using. Let's look only for packages + # and modules we can reach form here + + # If there's no dot, we're in toplevel, so we should + # also search for packages + s = string[startpos:pos] + if dotpos == 0 + append!(suggestions,filter(pname->begin + pname[1] != '.' && + pname != "METADATA" && + beginswith(pname,s) + end,readdir(Pkg.dir()))) + end + ffunc = (mod,x)->(isdefined(mod,x) && isa(mod.(x),Module)) + end + if startpos == 0 + pos = -1 + end + if dotpos == 0 + dotpos = startpos-1 + end + append!(suggestions,complete_symbol(string[startpos:pos],ffunc)) + sort(unique(suggestions)), (dotpos+1):pos + end + + function shell_completions(string,pos) + # First parse everything up to the current position + scs = string[1:pos] + args, last_parse = Base.shell_parse(scs,true) + # Now look at the last this we parsed + arg = args.args[end].args[end] + if isa(arg,String) + # Treat this as a path (perhaps give a list of comands in the future as well?) + dir,name = splitdir(arg) + if isempty(dir) + files = readdir() + else + if !isdir(dir) + return ([],0:-1) + end + files = readdir(dir) + end + # Filter out files and directories that do not begin with the partial name we were + # completiong and append "/" to directories to simplify further completion + ret = map(filter(x->beginswith(x,name),files)) do x + if !isdir(joinpath(dir,x)) + return x + else + return x*"/" + end + end + r = (nextind(string,pos-sizeof(name))):pos + return (ret,r,string[r]) + elseif isexpr(arg,:escape) && (isexpr(arg.args[1],VERSION >= v"0.3-" ? :incomplete : :continue) || isexpr(arg.args[1],:error)) + r = first(last_parse):prevind(last_parse,last(last_parse)) + partial = scs[r] + ret, range = completions(partial,endof(partial)) + range += first(r)-1 + return (ret,range) + end + end +end \ No newline at end of file diff --git a/base/Readline.jl b/base/Readline.jl new file mode 100644 index 0000000000000..cdc72b198feac --- /dev/null +++ b/base/Readline.jl @@ -0,0 +1,1137 @@ + module Readline + using Terminals + + import Terminals: raw!, width, height, cmove, Rect, Size, getX, + getY, clear_line, beep + + import Base: ensureroom, peek + + abstract TextInterface + + export run_interface, Prompt, ModalInterface, transition, reset_state, edit_insert + + immutable ModalInterface <: TextInterface + modes + end + + type MIState + interface::ModalInterface + current_mode + aborted::Bool + mode_state + end + + type Mode <: TextInterface + + end + + type Prompt <: TextInterface + prompt + first_prompt + prompt_color::ASCIIString + keymap_func + keymap_func_data + input_color + complete + on_enter + on_done + hist + end + + immutable InputAreaState + num_rows::Int64 + curs_row::Int64 + end + + type PromptState + terminal::TextTerminal + p::Prompt + input_buffer::IOBuffer + ias::InputAreaState + indent::Int + end + + input_string(s::PromptState) = bytestring(pointer(s.input_buffer.data),s.input_buffer.size) + + abstract HistoryProvider + abstract CompletionProvider + + type EmptyCompletionProvider <: CompletionProvider + end + + type EmptyHistoryProvider <: HistoryProvider + end + + reset_state(::EmptyHistoryProvider) = nothing + + completeLine(c::EmptyCompletionProvider,s) = [] + + terminal(s::IO) = s + terminal(s::PromptState) = s.terminal + + for f in [:terminal,:edit_insert,:on_enter,:add_history,:buffer,:edit_backspace,:(Base.isempty), + :replace_line,:refreshMultiLine,:input_string,:completeLine,:edit_move_left,:edit_move_right, + :update_display_buffer] + @eval ($f)(s::MIState,args...) = $(f)(s.mode_state[s.current_mode],args...) + end + + function common_prefix(completions) + ret = "" + i = nexti = 1 + cc,nexti = next(completions[1],1) + while true + for c in completions + if i > length(c) || c[i] != cc + return ret + end + end + ret = ret*string(cc) + if i >= length(completions[1]) + return ret + end + i = nexti + cc,nexti = next(completions[1],i) + end + end + + function completeLine(s::PromptState) + (completions,partial) = completeLine(s.p.complete,s) + if length(completions) == 0 + beep(Readline.terminal(s)) + elseif length(completions) == 1 + # Replace word by completion + prev_pos = position(s.input_buffer) + seek(s.input_buffer,prev_pos-sizeof(partial)) + edit_replace(s,position(s.input_buffer),prev_pos,completions[1]) + else + p = common_prefix(completions) + if length(p) > 0 && p != partial + # All possible completions share the same prefix, so we might as + # well complete that + prev_pos = position(s.input_buffer) + seek(s.input_buffer,prev_pos-sizeof(partial)) + edit_replace(s,position(s.input_buffer),prev_pos,p) + else + # Show available completions + colmax = maximum(map(length,completions)) + num_cols = div(width(Readline.terminal(s)),colmax+2) + entries_per_col = div(length(completions),num_cols)+1 + println(Readline.terminal(s)) + for row = 1:entries_per_col + for col = 0:num_cols + idx = row + col*entries_per_col + if idx <= length(completions) + cmove_col(Readline.terminal(s),(colmax+2)*col) + print(Readline.terminal(s),completions[idx]) + end + end + println(Readline.terminal(s)) + end + end + end + end + + clear_input_area(s) = (clear_input_area(s.terminal,s.ias); s.ias = InputAreaState(0,0)) + function clear_input_area(terminal,state::InputAreaState) + #println(s.curs_row) + #println(s.num_rows) + + # Go to the last line + if state.curs_row < state.num_rows + cmove_down(terminal,state.num_rows-state.curs_row) + end + + # Clear lines one by one going up + for j=0:(state.num_rows - 2) + clear_line(terminal) + cmove_up(terminal) + end + + # Clear top line + clear_line(terminal) + end + + prompt_string(s::PromptState) = s.p.prompt + prompt_string(s::String) = s + + refreshMultiLine(s::PromptState) = s.ias = refreshMultiLine(s.terminal,buffer(s),s.ias,s,indent=s.indent) + + function refreshMultiLine(terminal,buf,state::InputAreaState,prompt = "";indent = 0) + cols = width(terminal) + + clear_input_area(terminal,state) + + curs_row = -1 #relative to prompt + curs_col = -1 #absolute + curs_pos = -1 # 1 - based column position of the cursor + cur_row = 0 + buf_pos = position(buf) + line_pos = buf_pos + # Write out the prompt string + write_prompt(terminal,prompt) + prompt = prompt_string(prompt) + + seek(buf,0) + + llength = 0 + + l="" + + plength = length(prompt) + pslength = length(prompt.data) + # Now go through the buffer line by line + while cur_row == 0 || (!isempty(l) && l[end] == '\n') + l = readline(buf) + cur_row += 1 + # We need to deal with UTF8 characters. Since the IOBuffer is a bytearray, we just count bytes + llength = length(l) + slength = length(l.data) + if cur_row == 1 #First line + if line_pos < slength + num_chars = length(l[1:line_pos]) + curs_row = div(plength+num_chars-1,cols)+1 + curs_pos = (plength+num_chars-1)%cols+1 + end + cur_row += div(plength+llength-1,cols) + line_pos -= slength + write(terminal,l) + else + # We expect to be line after the last valid output line (due to + # the '\n' at the end of the previous line) + if curs_row == -1 + if line_pos < slength + num_chars = length(l[1:line_pos]) + curs_row = cur_row+div(indent+num_chars-1,cols) + curs_pos = (indent+num_chars-1)%cols+1 + end + line_pos -= slength #'\n' gets an extra pos + cur_row += div(llength+indent-1,cols) + cmove_col(terminal,indent+1) + write(terminal,l) + # There's an issue if the last character we wrote was at the very right end of the screen. In that case we need to + # emit a new line and move the cursor there. + if curs_pos == cols + write(terminal,"\n") + cmove_col(terminal,1) + curs_row+=1 + curs_pos=0 + cur_row+=1 + end + else + cur_row += div(llength+indent-1,cols) + cmove_col(terminal,indent+1) + write(terminal,l) + end + + end + + + end + + seek(buf,buf_pos) + + # If we are at the end of the buffer, we need to put the cursor one past the + # last character we have written + + if curs_row == -1 + curs_pos = ((cur_row == 1 ? plength : indent)+llength-1)%cols+1 + curs_row = cur_row + end + + # Same issue as above. TODO: We should figure out + # how to refactor this to avoid duplcating functionality. + if curs_pos == cols + write(terminal,"\n") + cmove_col(terminal,1) + curs_row+=1 + curs_pos=0 + cur_row+=1 + end + + + # Let's move the cursor to the right position + # The line first + n = cur_row-curs_row + if n>0 + cmove_up(terminal,n) + end + + #columns are 1 based + cmove_col(terminal,curs_pos+1) + + flush(terminal) + + # Updated cur_row,curs_row + return InputAreaState(cur_row,curs_row) + end + + + # Edit functionality + + char_move_left(s::PromptState) = char_move_left(s.input_buffer) + function char_move_left(buf::IOBuffer) + while position(buf)>0 + seek(buf,position(buf)-1) + c = peek(buf) + if ((c&0x80) == 0) || ((c&0xc0) == 0xc0) + break + end + end + end + + function edit_move_left(s::PromptState) + if position(s.input_buffer)>0 + #move t=o the next UTF8 character to the left + char_move_left(s.input_buffer) + refresh_line(s) + end + end + + char_move_right(s) = char_move_right(buffer(s)) + function char_move_right(buf::IOBuffer) + while position(buf) != buf.size + seek(buf,position(buf)+1) + if position(buf)==buf.size + break + end + c = peek(buf) + if ((c&0x80) == 0) || ((c&0xc0) == 0xc0) + break + end + end + end + + const non_word_chars = " \t\n\"\\'`@\$><=:;|&{}()[].,+-*/?%^~" + + function char_move_word_right(s) + while !eof(s.input_buffer) && !in(read(s.input_buffer,Char),non_word_chars) + end + end + + function char_move_word_left(s) + while position(s.input_buffer) > 0 + char_move_left(s) + c = peek(s.input_buffer) + if c < 0x80 && in(char(c),non_word_chars) + read(s.input_buffer,Uint8) + break + end + end + end + + function edit_move_right(s) + if position(s.input_buffer)!=s.input_buffer.size + #move to the next UTF8 character to the right + char_move_right(s) + refresh_line(s) + end + end + + ## Move line up/down + # Querying the terminal is expensive, memory access is cheap + # so to find the current column, we find the offset for the start + # of the line. + function edit_move_up(s) + buf = buffer(s) + npos = rsearch(buf.data,'\n',position(buf)) + if npos == 0 #we're in the first line + return false + end + # We're interested in character count, not byte count + offset = length(bytestring(buf.data[(npos+1):(position(buf))]))-1 + npos2 = rsearch(buf.data,'\n',npos-1) + seek(buf,npos2+1) + for _ = 1:offset + pos = position(buf) + if read(buf,Char) == '\n' + seek(buf,pos) + break + end + end + refresh_line(s) + return true + end + + function edit_move_down(s) + buf = buffer(s) + npos = rsearch(buf.data[1:buf.size],'\n',position(buf)) + # We're interested in character count, not byte count + offset = length(bytestring(buf.data[(npos+1):(position(buf))]))-1 + npos2 = search(buf.data[1:buf.size],'\n',position(buf)+1) + if npos2 == 0 #we're in the last line + return false + end + seek(buf,npos2+1) + for _ = 1:offset + pos = position(buf) + if eof(buf) || read(buf,Char) == '\n' + seek(buf,pos) + break + end + end + refresh_line(s) + return true + end + + function charlen(ch::Char) + if ch < 0x80 + return 1 + elseif ch < 0x800 + return 2 + elseif ch < 0x10000 + return 3 + elseif ch < 0x110000 + return 4 + end + error("Corrupt UTF8") + end + + + function edit_replace(s,from,to,str) + room = length(str.data)-(to-from) + ensureroom(s.input_buffer,s.input_buffer.size-to+room) + ccall(:memmove, Void, (Ptr{Void},Ptr{Void},Int), pointer(s.input_buffer.data,to+room+1),pointer(s.input_buffer.data,to+1),s.input_buffer.size-to) + s.input_buffer.size += room + seek(s.input_buffer,from) + write(s.input_buffer,str) + end + + function edit_insert(s::PromptState,c) + str = string(c) + edit_insert(s.input_buffer,str) + if !('\n' in str) && eof(s.input_buffer) && + ((position(s.input_buffer) + length(s.p.prompt) + sizeof(str) - 1) < width(Readline.terminal(s))) + #Avoid full update + write(Readline.terminal(s),str) + else + refresh_line(s) + end + end + + # TODO: Don't use memmove + function edit_insert(buf::IOBuffer,c) + if eof(buf) + write(buf,c) + else + s = string(c) + ensureroom(buf,buf.size-position(buf)+sizeof(s)) + oldpos = position(buf) + ccall(:memmove, Void, (Ptr{Void},Ptr{Void},Int), pointer(buf.data,position(buf)+1+sizeof(s)), pointer(buf.data,position(buf)+1), + buf.size-position(buf)) + buf.size += sizeof(s) + write(buf,c) + end + end + + function edit_backspace(s::PromptState) + if edit_backspace(s.input_buffer) + refresh_line(s) + else + beep(Readline.terminal(s)) + end + + end + function edit_backspace(buf::IOBuffer) + if position(buf) > 0 && buf.size>0 + oldpos = position(buf) + char_move_left(buf) + ccall(:memmove, Void, (Ptr{Void},Ptr{Void},Int), pointer(buf.data,position(buf)+1), pointer(buf.data,oldpos+1), + buf.size-oldpos) + buf.size -= oldpos-position(buf) + return true + else + return false + end + end + + function edit_delete(s) + buf = buffer(s) + if buf.size>0 && position(buf) < buf.size + oldpos = position(buf) + char_move_right(s) + ccall(:memmove, Void, (Ptr{Void},Ptr{Void},Int), pointer(buf.data,oldpos+1), pointer(buf.data,position(buf)+1), + buf.size-position(buf)) + buf.size -= position(buf)-oldpos + seek(buf,oldpos) + refresh_line(s) + else + beep(Readline.terminal(s)) + end + end + + function replace_line(s::PromptState,l::IOBuffer) + s.input_buffer = l + end + + function replace_line(s::PromptState,l) + s.input_buffer.ptr = 1 + s.input_buffer.size = 0 + write(s.input_buffer,l) + end + + history_prev(::EmptyHistoryProvider) = ("",false) + history_next(::EmptyHistoryProvider) = ("",false) + history_search(::EmptyHistoryProvider,args...) = false + add_history(::EmptyHistoryProvider,s) = nothing + add_history(s::PromptState) = add_history(mode(s).hist,s) + + + function history_prev(s,hist) + (l,ok) = history_prev(mode(s).hist) + if ok + replace_line(s,l) + refresh_line(s) + else + beep(Readline.terminal(s)) + end + end + function history_next(s,hist) + (l,ok) = history_next(mode(s).hist) + if ok + replace_line(s,l) + refresh_line(s) + else + beep(Readline.terminal(s)) + end + end + + refresh_line(s) = refreshMultiLine(s) + + default_completion_cb(::IOBuffer) = [] + default_enter_cb(_) = true + + write_prompt(terminal,s::PromptState) = write_prompt(terminal,s,s.p.prompt) + function write_prompt(terminal,s::PromptState,prompt) + @assert terminal == Readline.terminal(s) + write(terminal,s.p.prompt_color) + write(terminal,prompt) + write(terminal,Base.text_colors[:normal]) + write(terminal,s.p.input_color) + end + write_prompt(terminal,s::ASCIIString) = write(terminal,s) + + function normalize_key(key) + if isa(key,Char) + return string(key) + elseif isa(key,Integer) + return string(char(key)) + elseif isa(key,String) + if in('\0',key) + error("Matching \\0 not currently supported.") + end + buf = IOBuffer() + i = start(key) + while !done(key,i) + (c,i) = next(key,i) + if c == '*' + write(buf,'\0') + elseif c == '^' + (c,i) = next(key,i) + write(buf,uppercase(c)-64) + elseif c == '\\' + (c,i) == next(key,i) + if c == 'C' + (c,i) == next(key,i) + @assert c == '-' + (c,i) == next(key,i) + write(buf,uppercase(c)-64) + elseif c == 'M' + (c,i) == next(key,i) + @assert c == '-' + (c,i) == next(key,i) + write(buf,'\e') + write(buf,c) + end + else + write(buf,c) + end + end + return takebuf_string(buf) + end + end + + # Turn an Dict{Any,Any} into a Dict{'Char',Any} + # For now we use \0 to represent unknown chars so that they are sorted before everything else + # If we ever actually want to mach \0 in input, this will have to be + # reworked + function normalize_keymap(keymap) + ret = Dict{Char,Any}() + for key in keys(keymap) + newkey = normalize_key(key) + current = ret + i = start(newkey) + while !done(newkey,i) + (c,i) = next(newkey,i) + if haskey(current,c) + if !isa(current[c],Dict) + println(ret) + error("Conflicting Definitions for keyseq "*escape_string(newkey)*" within one keymap") + end + elseif done(newkey,i) + if isa(keymap[key],String) + current[c] = normalize_key(keymap[key]) + else + current[c] = keymap[key] + end + break + else + current[c] = Dict{Char,Any}() + end + current = current[c] + end + end + ret + end + + keymap_gen_body(keymaps,body::Expr,level) = body + keymap_gen_body(keymaps,body::Function,level) = keymap_gen_body(keymaps,:($(body)(s))) + keymap_gen_body(keymaps,body::Char,level) = keymap_gen_body(keymaps,keymaps[body]) + keymap_gen_body(keymaps,body::Nothing,level) = nothing + function keymap_gen_body(keymaps,body::String,level) + if length(body) == 1 + return keymap_gen_body(keymaps,body[1],level) + end + current = keymaps + for c in body + if haskey(current,c) + if isa(current[c],Dict) + current = current[c] + else + return keymap_gen_body(keymaps,current[c],level) + end + elseif haskey(current,'\0') + return keymap_gen_body(keymaps,current['\0'],level) + else + error("No match for redirected key $body") + end + end + error("No exact match for redirected key $body") + end + + keymap_gen_body(a,b) = keymap_gen_body(a,b,1) + function keymap_gen_body(dict,subdict::Dict,level) + block = Expr(:block) + bc = symbol("c"*string(level)) + push!(block.args,:($bc=read(Readline.terminal(s),Char))) + + if haskey(subdict,'\0') + last_if = keymap_gen_body(dict,subdict['\0'],level+1) + else + last_if = nothing + end + + for c in keys(subdict) + if c == '\0' + continue + end + cblock = Expr(:if,:($bc==$c)) + push!(cblock.args,keymap_gen_body(dict,subdict[c],level+1)) + if isa(cblock,Expr) + push!(cblock.args,last_if) + end + last_if = cblock + end + + push!(block.args,last_if) + return block + end + + export @keymap + + # deep merge where target has higher precedence + function keymap_merge!(target::Dict,source::Dict) + for k in keys(source) + if !haskey(target,k) + target[k] = source[k] + elseif isa(target[k],Dict) + keymap_merge!(target[k],source[k]) + else + # Ignore, target has higher precedence + end + end + end + + fixup_keymaps!(d,l,s,sk) = nothing + function fixup_keymaps!(dict::Dict, level, s, subkeymap) + if level > 1 + for d in dict + fixup_keymaps!(d[2],level-1,s,subkeymap) + end + else + if haskey(dict,s) + if isa(dict[s],Dict) && isa(subkeymap,Dict) + keymap_merge!(dict[s],subkeymap) + end + else + dict[s] = deepcopy(subkeymap) + end + end + end + + function add_specialisations(dict,subdict,level) + default_branch = subdict['\0'] + if isa(default_branch,Dict) + for s in keys(default_branch) + if s == '\0' + add_specialisations(dict,default_branch,level+1) + end + fixup_keymaps!(dict,level,s,default_branch[s]) + end + end + end + + fix_conflicts!(x) = fix_conflicts!(x,1) + fix_conflicts!(others,level) = nothing + function fix_conflicts!(dict::Dict,level) + # needs to be done first for every branch + if haskey(dict,'\0') + add_specialisations(dict,dict,level) + end + for d in dict + if d[1] == '\0' + continue + end + fix_conflicts!(d[2],level+1) + end + end + + function keymap_prepare(keymaps) + if isa(keymaps,Dict) + keymaps = [keymaps] + end + push!(keymaps,{"*"=>:(error("Unrecognized input"))}) + @assert isa(keymaps,Array) && eltype(keymaps) <: Dict + keymaps = map(normalize_keymap,keymaps) + map(fix_conflicts!,keymaps) + keymaps + end + + function keymap_unify(keymaps) + if length(keymaps) == 1 + return keymaps[1] + else + ret = Dict{Char,Any}() + for keymap in keymaps + keymap_merge!(ret,keymap) + end + fix_conflicts!(ret) + return ret + end + end + + macro keymap(func, keymaps) + dict = keymap_unify(keymap_prepare(isa(keymaps,Expr)?eval(keymaps):keymaps)) + body = keymap_gen_body(dict,dict) + esc(quote + function $(func)(s,data) + $body + return :ok + end + end) + end + + const escape_defaults = { + # Ignore other escape sequences by default + "\e*" => nothing, + "\e[*" => nothing, + # Also ignore extended escape sequences + # TODO: Support tanges of characters + "\e[1**" => nothing, + "\e[2**" => nothing, + "\e[3**" => nothing, + "\e[4**" => nothing, + "\e[5**" => nothing, + "\e[6**" => nothing, + "\e[1~" => "\e[H", + "\e[4~" => "\e[F", + "\e[7~" => "\e[H", + "\e[8~" => "\e[F", + "\eOH" => "\e[H", + "\eOF" => "\e[F", + } + + function write_response_buffer(s::PromptState,data) + offset = s.input_buffer.ptr + ptr = data.respose_buffer.ptr + seek(data.respose_buffer,0) + write(s.input_buffer,readall(data.respose_buffer)) + s.input_buffer.ptr = offset+ptr-2 + data.respose_buffer.ptr = ptr + refresh_line(s) + end + + type SearchState + terminal + histprompt + #rsearch (true) or ssearch (false) + backward::Bool + query_buffer::IOBuffer + respose_buffer::IOBuffer + ias::InputAreaState + #The prompt whose input will be replaced by the matched history + parent + SearchState(a,b,c,d,e) = new(a,b,c,d,e,InputAreaState(0,0)) + end + + terminal(s::SearchState) = s.terminal + + function update_display_buffer(s::SearchState,data) + history_search(data.histprompt.hp,data.query_buffer,data.respose_buffer,data.backward,false) || beep(Readline.terminal(s)) + refresh_line(s) + end + + function history_next_result(s::MIState,data::SearchState) + #truncate(data.query_buffer,s.input_buffer.size - data.respose_buffer.size) + history_search(data.histprompt.hp,data.query_buffer,data.respose_buffer,data.backward,true) || beep(Readline.terminal(s)) + refresh_line(data) + end + + function history_set_backward(s::SearchState,backward) + s.backward = backward + end + + function refreshMultiLine(s::SearchState) + buf = IOBuffer() + write(buf,pointer(s.query_buffer.data),s.query_buffer.ptr-1) + write(buf,"': ") + offset = buf.ptr + ptr = s.respose_buffer.ptr + seek(s.respose_buffer,0) + write(buf,readall(s.respose_buffer)) + buf.ptr = offset+ptr-1 + s.respose_buffer.ptr = ptr + refreshMultiLine(s.terminal,buf,s.ias,s.backward ? "(reverse-i-search)`" : "(i-search)`") + end + + function reset_state(s::SearchState) + if s.query_buffer.size != 0 + s.query_buffer.size = 0 + s.query_buffer.ptr = 1 + end + if s.respose_buffer.size != 0 + s.respose_buffer.size = 0 + s.query_buffer.ptr = 1 + end + reset_state(s.histprompt.hp) + end + + type HistoryPrompt <: TextInterface + hp::HistoryProvider + keymap_func::Function + HistoryPrompt(hp) = new(hp) + end + + init_state(terminal,p::HistoryPrompt) = SearchState(terminal,p,true,IOBuffer(),IOBuffer()) + + state(s::MIState,p) = s.mode_state[p] + state(s::PromptState,p) = (@assert s.p == p; s) + mode(s::MIState) = s.current_mode + mode(s::PromptState) = s.p + mode(s::SearchState) = @assert false + + function accept_result(s,p) + parent = state(s,p).parent + replace_line(state(s,parent),state(s,p).respose_buffer) + transition(s,parent) + end + + function setup_search_keymap(hp) + p = HistoryPrompt(hp) + pkeymap = { + "^R" => :( Readline.history_set_backward(data,true); Readline.history_next_result(s,data) ), + "^S" => :( Readline.history_set_backward(data,false); Readline.history_next_result(s,data) ), + "\r" => s->accept_result(s,p), + "\t" => nothing, #TODO: Maybe allow tab completion in R-Search? + + # Backspace/^H + '\b' => :(Readline.edit_backspace(data.query_buffer)?Readline.update_display_buffer(s,data):beep(Readline.terminal(s))), + 127 => '\b', + "^C" => s->transition(s,state(s,p).parent), + "^D" => s->transition(s,state(s,p).parent), + # ^A + 1 => s->(accept_result(s,p); move_line_start(s)), + # ^E + 5 => s->(accept_result(s,p); move_line_end(s)), + # Try to catch all Home/End keys + "\e[H" => s->(accept_result(s,p); move_input_start(s)), + "\e[F" => s->(accept_result(s,p); move_input_end(s)), + "*" => :(Readline.edit_insert(data.query_buffer,c1);Readline.update_display_buffer(s,data)) + } + @eval @Readline.keymap keymap_func $([pkeymap, escape_defaults]) + p.keymap_func = keymap_func + keymap = { + "^R" => s->( state(s,p).parent = mode(s); state(s,p).backward = true; transition(s,p) ), + "^S" => s->( state(s,p).parent = mode(s); state(s,p).backward = false; transition(s,p) ), + } + (p,keymap) + end + + keymap(state,p::HistoryPrompt) = p.keymap_func + keymap_data(state,::HistoryPrompt) = state + + Base.isempty(s::PromptState) = s.input_buffer.size == 0 + + on_enter(s::PromptState) = s.p.on_enter(s) + + move_input_start(s) = (seek(buffer(s),0); refresh_line(s)) + move_input_end(s) = (seekend(buffer(s)); refresh_line(s)) + function move_line_start(s) + buf = buffer(s) + curpos = position(buf) + if curpos == 0 + return + end + if buf.data[curpos] == '\n' + move_input_start(s) + else + seek(buf,rsearch(buf.data,'\n',curpos-1)) + end + refresh_line(s) + end + function move_line_end(s) + buf = buffer(s) + curpos = position(buf) + if eof(buf) + return + end + c = read(buf,Char) + if c == '\n' + move_input_end(s) + return + end + seek(buf,curpos) + pos = search(buffer(s).data,'\n',curpos+1) + if pos == 0 + move_input_end(s) + return + end + seek(buf,pos-1) + refresh_line(s) + end + + function commit_line(s) + println(Readline.terminal(s)) + Readline.add_history(s) + Readline.state(s,Readline.mode(s)).ias = + Readline.InputAreaState(0,0) + end + + const default_keymap = + { + # Tab + '\t' => s->begin + buf = buffer(s) + # Yes, we are ignoring the possiblity + # the we could be in the middle of a multi-byte + # sequence, here but that's ok, since any + # whitespace we're interested in is only one byte + if position(buf) != 0 + c = buf.data[position(buf)] + else + c = '\n' + end + if c == ' ' || c == '\n' || c == '\t' + edit_insert(s," "^4) + return + end + Readline.completeLine(s) + Readline.refresh_line(s) + end, + # Enter + '\r' => quote + if Readline.on_enter(s) + Readline.commit_line(s) + return :done + else + Readline.edit_insert(s,'\n') + end + end, + '\n' => '\r', + # Backspace/^H + '\b' => edit_backspace, + 127 => '\b', + # ^D + 4 => quote + if Readline.buffer(s).size > 0 + Readline.edit_delete(s) + else + println(Readline.terminal(s)) + return :abort + end + end, + # ^B + 2 => edit_move_left, + # ^F + 6 => edit_move_right, + # Meta Enter + "\e\r" => :(Readline.edit_insert(s,'\n')), + # Simply insert it into the buffer by default + "*" => :( Readline.edit_insert(s,c1) ), + # ^U + 21 => :( truncate(Readline.buffer(s),0); Readline.refresh_line(s) ), + # ^K + 11 => :( truncate(Readline.buffer(s),position(Readline.buffer(s))); Readline.refresh_line(s) ), + # ^A + 1 => move_line_start, + # ^E + 5 => move_line_end, + # Try to catch all Home/End keys + "\e[H" => move_input_start, + "\e[F" => move_input_end, + # ^L + 12 => :( Terminals.clear(Readline.terminal(s)); Readline.refresh_line(s) ), + # ^W (#edit_delte_prev_word(s)) + 23 => :( error("Unimplemented") ), + # ^C + "^C" => s->begin + move_input_end(s); + Readline.refresh_line(s); + print(Readline.terminal(s), "^C\n\n"); + transition(s,:reset); + Readline.refresh_line(s) + end, + # Right Arrow + "\e[C" => edit_move_right, + # Left Arrow + "\e[D" => edit_move_left, + # Up Arrow + "\e[A" => edit_move_up, + # Down Arrow + "\e[B" => edit_move_down, + # Bracketed Paste Mode + "\e[200~" => s->begin + ps = state(s,mode(s)) + input = readuntil(ps.terminal,"\e[201~")[1:(end-6)] + input = replace(input,'\r','\n') + if position(buffer(s)) == 0 + indent = Base.indentation(input)[1] + input = Base.unindent(input[(indent+1):end],indent) + end + edit_insert(s,input) + end, + } + + function history_keymap(hist) + return { + # ^P + 16 => :( Readline.history_prev(s,$hist) ), + # ^N + 14 => :( Readline.history_next(s,$hist) ), + # Up Arrow + "\e[A" => :( Readline.edit_move_up(s) || Readline.history_prev(s,$hist) ), + # Down Arrow + "\e[B" => :( Readline.edit_move_down(s) || Readline.history_next(s,$hist) ) + } + end + + function deactivate(p::Union(Prompt,HistoryPrompt),s::Union(SearchState,PromptState)) + clear_input_area(s) + s + end + + function activate(p::Union(Prompt,HistoryPrompt),s::Union(SearchState,PromptState)) + s.ias = InputAreaState(0,0) + refresh_line(s) + end + + function activate(p::Union(Prompt,HistoryPrompt),s::MIState) + @assert p == s.current_mode + activate(p,s.mode_state[s.current_mode]) + end + activate(m::ModalInterface,s::MIState) = activate(s.current_mode,s) + + function transition(s::MIState,mode) + if mode == :abort + s.aborted = true + return + end + if mode == :reset + reset_state(s) + return + end + s.mode_state[s.current_mode] = deactivate(s.current_mode,s.mode_state[s.current_mode]) + s.current_mode = mode + activate(mode,s.mode_state[mode]) + end + + function reset_state(s::PromptState) + if s.input_buffer.size != 0 + s.input_buffer.size = 0 + s.input_buffer.ptr = 1 + end + s.ias = InputAreaState(0,0) + end + + function reset_state(s::MIState) + for (mode,state) in s.mode_state + reset_state(state) + end + end + + @Readline.keymap default_keymap_func [Readline.default_keymap,Readline.escape_defaults] + + function Prompt(prompt; + first_prompt = prompt, + prompt_color="", + keymap_func = default_keymap_func, + keymap_func_data = nothing, + input_color="", + complete=EmptyCompletionProvider(), + on_enter=default_enter_cb,on_done=()->nothing,hist=EmptyHistoryProvider()) + Prompt(prompt,first_prompt,prompt_color,keymap_func,keymap_func_data,input_color,complete,on_enter,on_done,hist) + end + + function run_interface(::Prompt) + + end + + init_state(terminal,prompt::Prompt) = PromptState(terminal,prompt,IOBuffer(),InputAreaState(1,1),length(prompt.prompt)) + + function init_state(terminal,m::ModalInterface) + s = MIState(m,m.modes[1],false,Dict{Any,Any}()) + for mode in m.modes + s.mode_state[mode] = init_state(terminal,mode) + end + s + end + + function run_interface(terminal,m::ModalInterface) + s = init_state(terminal,m) + while !s.aborted + p = s.current_mode + buf,ok = prompt!(terminal,m,s) + s.mode_state[s.current_mode].p.on_done(s,buf,ok) + end + end + + buffer(s::PromptState) = s.input_buffer + buffer(s::SearchState) = s.query_buffer + + keymap(s::PromptState,prompt::Prompt) = prompt.keymap_func + keymap_data(s::PromptState,prompt::Prompt) = prompt.keymap_func_data + keymap(ms::MIState,m::ModalInterface) = keymap(ms.mode_state[ms.current_mode],ms.current_mode) + keymap_data(ms::MIState,m::ModalInterface) = keymap_data(ms.mode_state[ms.current_mode],ms.current_mode) + + function prompt!(terminal,prompt,s=init_state(terminal,prompt)) + raw!(terminal,true) + isa(terminal,UnixTerminal) && Terminals.Unix.enable_bracketed_paste(terminal) + try + start_reading(terminal) + activate(prompt,s) + while true + state = keymap(s,prompt)(s,keymap_data(s,prompt)) + if state == :abort + stop_reading(terminal) + return (buffer(s),false) + elseif state == :done + stop_reading(terminal) + return (buffer(s),true) + else + @assert state == :ok + end + end + finally + raw!(terminal,false) && Terminals.Unix.disable_bracketed_paste(terminal) + end + end +end \ No newline at end of file diff --git a/base/Terminals.jl b/base/Terminals.jl new file mode 100644 index 0000000000000..1e67acb8c12cd --- /dev/null +++ b/base/Terminals.jl @@ -0,0 +1,221 @@ +module Terminals + import Base.size, Base.write, Base.flush + abstract TextTerminal <: Base.IO + export TextTerminal, NCurses, writepos, cmove, pos, getX, getY, hascolor + + # Stuff that really should be in a Geometry package + immutable Rect + top + left + width + height + end + + immutable Size + width + height + end + + + # INTERFACE + size(::TextTerminal) = error("Unimplemented") + writepos(t::TextTerminal,x,y,s::Array{Uint8,1}) = error("Unimplemented") + cmove(t::TextTerminal,x,y) = error("Unimplemented") + getX(t::TextTerminal) = error("Unimplemented") + getY(t::TextTerminal) = error("Unimplemented") + pos(t::TextTerminal) = (getX(t),getY(t)) + + # Relative moves (Absolute position fallbacks) + export cmove_up, cmove_down, cmove_left, cmove_right, cmove_line_up, cmove_line_down, cmove_col + + cmove_up(t::TextTerminal,n) = cmove(getX(),max(1,getY()-n)) + cmove_up(t) = cmove_up(t,1) + + cmove_down(t::TextTerminal,n) = cmove(getX(),max(height(t),getY()+n)) + cmove_down(t) = cmove_down(t,1) + + cmove_left(t::TextTerminal,n) = cmove(max(1,getX()-n),getY()) + cmove_left(t) = cmove_left(t,1) + + cmove_right(t::TextTerminal,n) = cmove(max(width(t),getX()+n),getY()) + cmove_right(t) = cmove_right(t,1) + + cmove_line_up(t::TextTerminal,n) = cmove(1,max(1,getY()-n)) + cmove_line_up(t) = cmove_line_up(t,1) + + cmove_line_down(t::TextTerminal,n) = cmove(1,max(height(t),getY()+n)) + cmove_line_down(t) = cmove_line_down(t,1) + + cmove_col(t::TextTerminal,c) = comve(c,getY()) + + # Defaults + hascolor(::TextTerminal) = false + + # Utility Functions + function writepos{T}(t::TextTerminal, x, y, b::Array{T}) + if isbits(T) + writepos(t,x,y,reinterpret(Uint8,b)) + else + cmove(t,x,y) + invoke(write, (IO, Array), s, a) + end + end + function writepos(t::TextTerminal,x,y,args...) + cmove(t,x,y) + write(t,args...) + end + width(t::TextTerminal) = size(t).width + height(t::TextTerminal) = size(t).height + + # For terminals with buffers + flush(t::TextTerminal) = nothing + + clear(t::TextTerminal) = error("Unimplemented") + clear_line(t::TextTerminal,row) = error("Unimplemented") + clear_line(t::TextTerminal) = error("Unimplemented") + + raw!(t::TextTerminal,raw::Bool) = error("Unimplemented") + + beep(t::TextTerminal) = nothing + + abstract TextAttribute + + module Attributes + # This is just to get started and will have to be revised + + import Terminals.TextAttribute, Terminals.TextTerminal + + export Standout, Underline, Reverse, Blink, Dim, Bold, AltCharset, Invisible, Protect, Left, Right, Top, + Vertical, Horizontal, Low + + macro flag_attribute(name) + quote + immutable $name <: TextAttribute + end + end + end + + @flag_attribute Standout + @flag_attribute Underline + @flag_attribute Reverse + @flag_attribute Blink + @flag_attribute Dim + @flag_attribute Bold + @flag_attribute AltCharset + @flag_attribute Invisible + @flag_attribute Protect + @flag_attribute Left + @flag_attribute Right + @flag_attribute Top + @flag_attribute Vertical + @flag_attribute Horizontal + @flag_attribute Low + + attr_simplify(::TextTerminal, x::TextAttribute) = x + attr_simplify{T<:TextAttribute}(::TextTerminal, ::Type{T}) = T() + function attr_simplify(::TextTerminal, s::Symbol) + if s == :standout + return Standout() + elseif s == :underline + return Underline() + elseif s == :reverse + return Reverse() + elseif s == :blink + return Blink() + end + end + + + end + + module Colors + import Terminals.TextAttribute, Terminals.TextTerminal, Terminals.Attributes.attr_simplify + using Color + + export TerminalColor, TextColor, BackgroundColor, ForegroundColor, approximate, + lookup_color, terminal_color, maxcolors, maxcolorpairs, palette, numcolors + + # Represents a color actually displayable by the current terminal + abstract TerminalColor + + immutable TextColor <: TextAttribute + c::TerminalColor + end + immutable BackgroundColor <: TextAttribute + c::TerminalColor + end + + function approximate(t::TextTerminal, c::ColorValue) + x = keys(palette(t)) + lookup_color(t,x[indmin(map(x->colordiff(c,x),x))]) + end + + attr_simplify(t::TextTerminal, c::ColorValue) = TextColor(lookup_color(t,c)) + + # Terminals should implement this + lookup_color(t::TextTerminal) = error("Unimplemented") + maxcolors(t::TextTerminal) = error("Unimplemented") + maxcolorpairs(t::TextTerminal) = error("Unimplemented") + palette(t::TextTerminal) = error("Unimplemented") + numcolors(t::TextTerminal) = error("Unimplemented") + end + + module Unix + importall Terminals + + import Terminals: width, height, cmove, Rect, Size, getX, + getY, raw!, clear, clear_line, beep, hascolor + import Base: size, read, write, flush, TTY, writemime, readuntil, start_reading, stop_reading + + export UnixTerminal + + type UnixTerminal <: TextTerminal + term_type + in_stream::TTY + out_stream::TTY + err_stream::TTY + end + + const CSI = "\x1b[" + + cmove_up(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)A") + cmove_down(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)B") + cmove_right(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)C") + cmove_left(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)D") + cmove_line_up(t::UnixTerminal,n) = (cmove_up(t,n);cmove_col(t,0)) + cmove_line_down(t::UnixTerminal,n) = (cmove_down(t,n);cmove_col(t,0)) + cmove_col(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)G") + + raw!(t::UnixTerminal,raw::Bool) = ccall(:uv_tty_set_mode,Int32,(Ptr{Void},Int32),t.in_stream.handle,raw?1:0)!=-1 + enable_bracketed_paste(t::UnixTerminal) = write(t.out_stream,"$(CSI)?2004h") + disable_bracketed_paste(t::UnixTerminal) = write(t.out_stream,"$(CSI)?2004l") + + function size(t::UnixTerminal) + s = Array(Int32,2) + Base.uv_error("size (TTY)",ccall(:uv_tty_get_winsize,Int32,(Ptr{Void},Ptr{Int32},Ptr{Int32}),t.out_stream.handle,pointer(s,1),pointer(s,2))!=0) + Size(s[1],s[2]) + end + + clear(t::UnixTerminal) = write(t.out_stream,"\x1b[H\x1b[2J") + clear_line(t::UnixTerminal) = write(t.out_stream,"\x1b[0G\x1b[0K") + beep(t::UnixTerminal) = write(t.err_stream,"\x7") + + write{T,N}(t::UnixTerminal,a::Array{T,N}) = write(t.out_stream,a) + write(t::UnixTerminal,p::Ptr{Uint8}) = write(t.out_stream,p) + write(t::UnixTerminal,p::Ptr{Uint8},x::Integer) = write(t.out_stream,p,x) + write(t::UnixTerminal,x::Uint8) = write(t.out_stream,x) + read{T,N}(t::UnixTerminal,x::Array{T,N}) = read(t.in_stream,x) + readuntil(t::UnixTerminal,s::String) = readuntil(t.in_stream,s) + readuntil(t::UnixTerminal,c::Char) = readuntil(t.in_stream,c) + readuntil(t::UnixTerminal,s) = readuntil(t.in_stream,s) + read(t::UnixTerminal,::Type{Uint8}) = read(t.in_stream,Uint8) + start_reading(t::UnixTerminal) = start_reading(t.in_stream) + stop_reading(t::UnixTerminal) = stop_reading(t.in_stream) + + + hascolor(t::UnixTerminal) = (beginswith(t.term_type,"xterm") || success(`tput setaf 0`)) + #writemime(t::UnixTerminal, ::MIME"text/plain", x) = writemime(t.out_stream, MIME("text/plain"), x) + end + importall .Unix + export UnixTerminal +end \ No newline at end of file diff --git a/test/readline.jl b/test/readline.jl new file mode 100644 index 0000000000000..24ed6928cb504 --- /dev/null +++ b/test/readline.jl @@ -0,0 +1,46 @@ +using Base.Test + +a_foo = 0 + +const foo_keymap = { + 'a' => :( global a_foo; a_foo += 1) +} + +b_foo = 0 + +const foo2_keymap = { + 'b' => :( global b_foo; b_foo += 1) +} + +a_bar = 0 +b_bar = 0 + +const bar_keymap = { + 'a' => :( global a_bar; a_bar += 1), + 'b' => :( global b_bar; b_bar += 1) +} + +@eval @Readline.keymap test1_func $foo_keymap + +function run_test(f,buf) + global a_foo, a_bar, b_bar + a_foo = a_bar = b_bar = 0 + while !eof(buf) + f(buf,nothing) + end +end + +run_test(test1_func,IOBuffer("aa")) +@test a_foo == 2 + +@eval @Readline.keymap test2_func $([foo2_keymap, foo_keymap]) + +run_test(test2_func,IOBuffer("aaabb")) +@test a_foo == 3 +@test b_foo == 2 + +@eval @Readline.keymap test3_func $([bar_keymap, foo_keymap]) + +run_test(test3_func,IOBuffer("aab")) +@test a_bar == 2 +@test b_bar == 1 \ No newline at end of file diff --git a/test/replcompletions.jl b/test/replcompletions.jl new file mode 100644 index 0000000000000..0f7461e84ae93 --- /dev/null +++ b/test/replcompletions.jl @@ -0,0 +1,98 @@ +using REPLCompletions +using Base.Test + +module CompletionFoo + module CompletionFoo2 + + end + const bar = 1 + foo() = bar +end + +test_complete(s) = completions(s,endof(s)) +test_scomplete(s) = shell_completions(s,endof(s)) + +s = "" +c,r = test_complete(s) +@test in("CompletionFoo",c) +@test r == 0:-1 +@test s[r] == "" + +s = "Comp" +c,r = test_complete(s) +@test in("CompletionFoo",c) +@test r == 1:4 +@test s[r] == "Comp" + +s = "Main.Comp" +c,r = test_complete(s) +@test in("CompletionFoo",c) +@test r == 6:9 +@test s[r] == "Comp" + +s = "Main.CompletionFoo." +c,r = test_complete(s) +@test in("bar",c) +@test r == 20:19 +@test s[r] == "" + +s = "Main.CompletionFoo.f" +c,r = test_complete(s) +@test in("foo",c) +@test r == 20:20 +@test s[r] == "f" + +# Test completion of packages +mkp(p) = ((@assert !isdir(p)); mkdir(p)) +mkp(Pkg.dir("MyAwesomePackage")) +mkp(Pkg.dir("CompletionFooPackage")) + +s = "using MyAwesome" +c,r = test_complete(s) +@test in("MyAwesomePackage",c) +@test s[r] == "MyAwesome" + +s = "using Completion" +c,r = test_complete(s) +@test in("CompletionFoo",c) #The module +@test in("CompletionFooPackage",c) #The package +@test s[r] == "Completion" + +s = "using CompletionFoo.Completion" +c,r = test_complete(s) +@test in("CompletionFoo2",c) +@test s[r] == "Completion" + +rmdir(Pkg.dir("MyAwesomePackage")) +rmdir(Pkg.dir("CompletionFooPackage")) + + +@unix_only begin + #Assume that we can rely on the existence and accessibility of /tmp + s = "/t" + c,r = test_scomplete("/t") + @test in("tmp/",c) + @test r == 2:2 + @test s[r] == "t" + + s = "/tmp" + c,r = test_scomplete(s) + @test in("tmp/",c) + @test r == 2:4 + @test s[r] == "tmp" + + # This should match things that are inside the tmp directory + if !isdir("/tmp/tmp") + s = "/tmp/" + c,r = test_scomplete(s) + @test !in("tmp/",c) + @test r == 6:5 + @test s[r] == "" + end + + s = "cd \$(Pk" + c,r = test_scomplete(s) + @test in("Pkg",c) + @test r == 6:7 + @test s[r] == "Pk" +end From edfbda44d76c85b0750eeb910c5406952d7af891 Mon Sep 17 00:00:00 2001 From: Mike Nolta Date: Sat, 22 Mar 2014 18:12:25 -0400 Subject: [PATCH 02/11] integrate REPL.jl code into Base --- base/REPL.jl | 18 ++++----- base/Readline.jl | 10 ++--- base/Terminals.jl | 25 ++++-------- base/client.jl | 75 +++++------------------------------ base/precompile.jl | 1 - base/{repl.jl => replutil.jl} | 0 base/sysimg.jl | 12 ++++-- test/Makefile | 5 ++- test/readline.jl | 4 +- test/replcompletions.jl | 52 ++++++++++++------------ test/runtests.jl | 3 +- 11 files changed, 72 insertions(+), 133 deletions(-) rename base/{repl.jl => replutil.jl} (100%) diff --git a/base/REPL.jl b/base/REPL.jl index 6829ade7ae890..d2dc73da5b10c 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -125,10 +125,10 @@ module REPL end end - using Terminals - using Readline + using Base.Terminals + using Base.Readline - import Readline: char_move_left, char_move_word_left, CompletionProvider, completeLine + import Base.Readline: char_move_left, char_move_word_left, CompletionProvider, completeLine type ReadlineREPL <: AbstractREPL t::TextTerminal @@ -153,7 +153,7 @@ module REPL r::ReadlineREPL end - using REPLCompletions + using Base.REPLCompletions function completeLine(c::REPLCompletionProvider,s) partial = bytestring(s.input_buffer.data[1:position(s.input_buffer)]) @@ -168,7 +168,7 @@ module REPL return (ret, partial[range]) end - import Readline: HistoryProvider, add_history, history_prev, history_next, history_search + import Base.Readline: HistoryProvider, add_history, history_prev, history_next, history_search type REPLHistoryProvider <: HistoryProvider history::Array{String,1} @@ -329,7 +329,6 @@ module REPL Readline.reset_state(hist::REPLHistoryProvider) = history_reset_state(hist) const julia_green = "\033[1m\033[32m" - const color_normal = Base.color_normal function return_callback(repl,s) if position(s.input_buffer) != 0 && eof(s.input_buffer) && @@ -383,7 +382,7 @@ module REPL end end - import Terminals: raw! + import Base.Terminals: raw! function reset(d::REPLDisplay) raw!(d.repl.t,false) @@ -473,7 +472,7 @@ module REPL (hkp,hkeymap) = Readline.setup_search_keymap(hp) - # Canoniczlize user keymap input + # Canonicalize user keymap input if isa(extra_repl_keymap,Dict) extra_repl_keymap = [extra_repl_keymap] end @@ -569,7 +568,6 @@ module REPL repl_channel = RemoteRef() response_channel = RemoteRef() start_repl_backend(repl_channel, response_channel) - banner(t,t) run_frontend(ReadlineREPL(t),repl_channel,response_channel) end @@ -639,7 +637,7 @@ module REPL if !isempty(line) ast = Base.parse_input_line(line) if have_color - print(repl.stream,color_normal) + print(repl.stream, Base.color_normal) end put!(repl_channel, (ast,1)) (val, bt) = take!(response_channel) diff --git a/base/Readline.jl b/base/Readline.jl index cdc72b198feac..92bf62546bc72 100644 --- a/base/Readline.jl +++ b/base/Readline.jl @@ -1,8 +1,8 @@ - module Readline - using Terminals +module Readline + using Base.Terminals - import Terminals: raw!, width, height, cmove, Rect, Size, getX, - getY, clear_line, beep + import Base.Terminals: raw!, width, height, cmove, Rect, Size, getX, + getY, clear_line, beep import Base: ensureroom, peek @@ -1134,4 +1134,4 @@ raw!(terminal,false) && Terminals.Unix.disable_bracketed_paste(terminal) end end -end \ No newline at end of file +end diff --git a/base/Terminals.jl b/base/Terminals.jl index 1e67acb8c12cd..b6a6f2daa3462 100644 --- a/base/Terminals.jl +++ b/base/Terminals.jl @@ -1,7 +1,7 @@ module Terminals import Base.size, Base.write, Base.flush abstract TextTerminal <: Base.IO - export TextTerminal, NCurses, writepos, cmove, pos, getX, getY, hascolor + export TextTerminal, writepos, cmove, pos, getX, getY, hascolor # Stuff that really should be in a Geometry package immutable Rect @@ -16,7 +16,6 @@ module Terminals height end - # INTERFACE size(::TextTerminal) = error("Unimplemented") writepos(t::TextTerminal,x,y,s::Array{Uint8,1}) = error("Unimplemented") @@ -83,7 +82,7 @@ module Terminals module Attributes # This is just to get started and will have to be revised - import Terminals.TextAttribute, Terminals.TextTerminal + import ..Terminals: TextAttribute, TextTerminal export Standout, Underline, Reverse, Blink, Dim, Bold, AltCharset, Invisible, Protect, Left, Right, Top, Vertical, Horizontal, Low @@ -129,10 +128,9 @@ module Terminals end module Colors - import Terminals.TextAttribute, Terminals.TextTerminal, Terminals.Attributes.attr_simplify - using Color + import ..Terminals: TextAttribute, TextTerminal - export TerminalColor, TextColor, BackgroundColor, ForegroundColor, approximate, + export TerminalColor, TextColor, BackgroundColor, ForegroundColor, lookup_color, terminal_color, maxcolors, maxcolorpairs, palette, numcolors # Represents a color actually displayable by the current terminal @@ -145,13 +143,6 @@ module Terminals c::TerminalColor end - function approximate(t::TextTerminal, c::ColorValue) - x = keys(palette(t)) - lookup_color(t,x[indmin(map(x->colordiff(c,x),x))]) - end - - attr_simplify(t::TextTerminal, c::ColorValue) = TextColor(lookup_color(t,c)) - # Terminals should implement this lookup_color(t::TextTerminal) = error("Unimplemented") maxcolors(t::TextTerminal) = error("Unimplemented") @@ -161,10 +152,10 @@ module Terminals end module Unix - importall Terminals + importall ..Terminals - import Terminals: width, height, cmove, Rect, Size, getX, - getY, raw!, clear, clear_line, beep, hascolor + import ..Terminals: width, height, cmove, Rect, Size, getX, + getY, raw!, clear, clear_line, beep, hascolor import Base: size, read, write, flush, TTY, writemime, readuntil, start_reading, stop_reading export UnixTerminal @@ -218,4 +209,4 @@ module Terminals end importall .Unix export UnixTerminal -end \ No newline at end of file +end diff --git a/base/client.jl b/base/client.jl index 15e80c88d78c3..fa79b07a6fff5 100644 --- a/base/client.jl +++ b/base/client.jl @@ -145,65 +145,6 @@ end _repl_start = Condition() -function run_repl() - global const repl_channel = RemoteRef() - - ccall(:jl_init_repl, Void, (Cint,), _use_history) - - # install Ctrl-C interrupt handler (InterruptException) - ccall(:jl_install_sigint_handler, Void, ()) - buf = Array(Uint8) - global _repl_enough_stdin = true - input = @async begin - try - while true - if _repl_enough_stdin - wait(_repl_start) - end - try - if eof(STDIN) # if TTY, can throw InterruptException, must be in try/catch block - return - end - read!(STDIN, buf) - ccall(:jl_read_buffer,Void,(Ptr{Void},Cssize_t),buf,length(buf)) - catch ex - if isa(ex,InterruptException) - println(STDOUT, "^C") - ccall(:jl_reset_input,Void,()) - repl_callback(nothing, 0) - else - rethrow(ex) - end - end - end - finally - put!(repl_channel,(nothing,-1)) - end - end - - while !istaskdone(input) - if have_color - prompt_string = "\01\033[1m\033[32m\02julia> \01\033[0m"*input_color()*"\02" - else - prompt_string = "julia> " - end - ccall(:repl_callback_enable, Void, (Ptr{Uint8},), prompt_string) - global _repl_enough_stdin = false - notify(_repl_start) - start_reading(STDIN) - (ast, show_value) = take!(repl_channel) - if show_value == -1 - # exit flag - break - end - eval_user_input(ast, show_value!=0) - end - - if have_color - print(color_normal) - end -end - function parse_input_line(s::String) # s = bytestring(s) # (expr, pos) = parse(s, 1) @@ -373,6 +314,9 @@ function early_init() start_gc_msgs_task() end +import .Terminals +import .REPL + function _start() early_init() @@ -382,17 +326,15 @@ function _start() (quiet,repl,startup,color_set,history) = process_options(copy(ARGS)) global _use_history = history + local term if repl + term = Terminals.UnixTerminal(get(ENV,"TERM",""),STDIN,STDOUT,STDERR) if !isa(STDIN,TTY) global is_interactive = !isa(STDIN,Union(File,IOStream)) color_set || (global have_color = false) else global is_interactive = true - if !color_set - @windows_only global have_color = true - @unix_only global have_color = - (beginswith(get(ENV,"TERM",""),"xterm") || success(`tput setaf 0`)) - end + color_set || (global have_color = Terminals.hascolor(term)) end end @@ -415,8 +357,9 @@ function _start() end quit() end - quiet || banner() - run_repl() + quiet || REPL.banner(term,term) + ccall(:jl_install_sigint_handler, Void, ()) + REPL.run_repl(term) end catch err display_error(err,catch_backtrace()) diff --git a/base/precompile.jl b/base/precompile.jl index c83700dd2fcc5..c341e65a67307 100644 --- a/base/precompile.jl +++ b/base/precompile.jl @@ -8,7 +8,6 @@ precompile(isempty, (Array{Any,1},)) precompile(getindex, (Dict{Any,Any}, Int32)) precompile(_start, ()) precompile(process_options, (Array{Any,1},)) -precompile(run_repl, ()) precompile(any, (Function, Array{Any,1})) precompile(Dict{Any,Any}, (Int,)) precompile(Set, ()) diff --git a/base/repl.jl b/base/replutil.jl similarity index 100% rename from base/repl.jl rename to base/replutil.jl diff --git a/base/sysimg.jl b/base/sysimg.jl index 33c990b58dbbb..471ad54d9dd4e 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -132,9 +132,7 @@ include("multi.jl") # Polling (requires multi.jl) include("poll.jl") -# front end & code loading -include("repl.jl") -include("client.jl") +# code loading include("loading.jl") begin @@ -185,6 +183,14 @@ using .I18n using .Help push!(I18n.CALLBACKS, Help.clear_cache) +# frontend +include("Terminals.jl") +include("Readline.jl") +include("REPLCompletions.jl") +include("REPL.jl") +include("replutil.jl") +include("client.jl") + # sparse matrices and linear algebra include("sparse.jl") importall .SparseMatrix diff --git a/test/Makefile b/test/Makefile index 9ca4f50a859d0..1ddaeae43bcb0 100644 --- a/test/Makefile +++ b/test/Makefile @@ -2,11 +2,12 @@ JULIAHOME = $(abspath ..) include ../Make.inc TESTS = all core keywordargs numbers strings unicode collections hashing \ - remote iobuffer arrayops linalg blas fft dsp sparse bitarray random \ + remote iobuffer arrayops linalg blas fft dsp sparse bitarray random \ math functional bigint sorting statistics spawn parallel arpack file \ git pkg resolve suitesparse complex version pollfd mpfr broadcast \ socket floatapprox priorityqueue readdlm regex float16 combinatorics \ - sysinfo rounding ranges mod2pi euler show linalg1 linalg2 + sysinfo rounding ranges mod2pi euler show linalg1 linalg2 readline \ + replcompletions default: all diff --git a/test/readline.jl b/test/readline.jl index 24ed6928cb504..849aba478d02d 100644 --- a/test/readline.jl +++ b/test/readline.jl @@ -1,4 +1,4 @@ -using Base.Test +using Base.Readline a_foo = 0 @@ -43,4 +43,4 @@ run_test(test2_func,IOBuffer("aaabb")) run_test(test3_func,IOBuffer("aab")) @test a_bar == 2 -@test b_bar == 1 \ No newline at end of file +@test b_bar == 1 diff --git a/test/replcompletions.jl b/test/replcompletions.jl index 0f7461e84ae93..a3692de07a57a 100644 --- a/test/replcompletions.jl +++ b/test/replcompletions.jl @@ -1,5 +1,4 @@ -using REPLCompletions -using Base.Test +using Base.REPLCompletions module CompletionFoo module CompletionFoo2 @@ -42,30 +41,31 @@ c,r = test_complete(s) @test r == 20:20 @test s[r] == "f" -# Test completion of packages -mkp(p) = ((@assert !isdir(p)); mkdir(p)) -mkp(Pkg.dir("MyAwesomePackage")) -mkp(Pkg.dir("CompletionFooPackage")) - -s = "using MyAwesome" -c,r = test_complete(s) -@test in("MyAwesomePackage",c) -@test s[r] == "MyAwesome" - -s = "using Completion" -c,r = test_complete(s) -@test in("CompletionFoo",c) #The module -@test in("CompletionFooPackage",c) #The package -@test s[r] == "Completion" - -s = "using CompletionFoo.Completion" -c,r = test_complete(s) -@test in("CompletionFoo2",c) -@test s[r] == "Completion" - -rmdir(Pkg.dir("MyAwesomePackage")) -rmdir(Pkg.dir("CompletionFooPackage")) - +## Test completion of packages +#mkp(p) = ((@assert !isdir(p)); mkdir(p)) +#temp_pkg_dir() do +# mkp(Pkg.dir("MyAwesomePackage")) +# mkp(Pkg.dir("CompletionFooPackage")) +# +# s = "using MyAwesome" +# c,r = test_complete(s) +# @test in("MyAwesomePackage",c) +# @test s[r] == "MyAwesome" +# +# s = "using Completion" +# c,r = test_complete(s) +# @test in("CompletionFoo",c) #The module +# @test in("CompletionFooPackage",c) #The package +# @test s[r] == "Completion" +# +# s = "using CompletionFoo.Completion" +# c,r = test_complete(s) +# @test in("CompletionFoo2",c) +# @test s[r] == "Completion" +# +# rmdir(Pkg.dir("MyAwesomePackage")) +# rmdir(Pkg.dir("CompletionFooPackage")) +#end @unix_only begin #Assume that we can rely on the existence and accessibility of /tmp diff --git a/test/runtests.jl b/test/runtests.jl index 1462b6cb508b6..4584c1b691a07 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,7 +7,8 @@ testnames = [ "priorityqueue", "arpack", "file", "suitesparse", "version", "resolve", "pollfd", "mpfr", "broadcast", "complex", "socket", "floatapprox", "readdlm", "regex", "float16", "combinatorics", - "sysinfo", "rounding", "ranges", "mod2pi", "euler", "show" + "sysinfo", "rounding", "ranges", "mod2pi", "euler", "show", + "readline", "replcompletions" ] @unix_only push!(testnames, "unicode") From e38577be0c47c1d6fb1ee9fe0207a73aa9f2ce13 Mon Sep 17 00:00:00 2001 From: Mike Nolta Date: Sat, 22 Mar 2014 18:55:55 -0400 Subject: [PATCH 03/11] expunge readline --- .travis.yml | 6 +- CONTRIBUTING.md | 2 +- LICENSE.md | 22 +- Make.inc | 19 +- README.md | 4 - README.windows.md | 5 +- contrib/julia.lang | 1 - contrib/mac/macports.make | 1 - contrib/vagrant/README.md | 2 +- contrib/vagrant/Vagrantfile | 4 +- contrib/windows/build-installer.nsi | 2 +- contrib/windows/prepare-julia-env.bat | 2 +- contrib/windows/test-julia.bat | 2 +- deps/.gitignore | 4 +- deps/Makefile | 92 +-- deps/Versions.make | 1 - deps/readline-win.h | 14 - deps/readline62-001 | 46 -- deps/readline62-002 | 57 -- deps/readline62-003 | 76 --- deps/readline62-004 | 108 ---- doc/man/julia-readline.1 | 1 - doc/man/julia.1 | 10 +- ui/.gitignore | 2 - ui/Makefile | 15 +- ui/repl-readline.c | 882 -------------------------- 26 files changed, 22 insertions(+), 1358 deletions(-) delete mode 100644 deps/readline-win.h delete mode 100644 deps/readline62-001 delete mode 100644 deps/readline62-002 delete mode 100644 deps/readline62-003 delete mode 100644 deps/readline62-004 delete mode 100644 doc/man/julia-readline.1 delete mode 100644 ui/repl-readline.c diff --git a/.travis.yml b/.travis.yml index 9a2a119f500d2..2f3b172b948c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,15 +15,15 @@ notifications: - http://criid.ee.washington.edu:8000/travis-hook - http://julia.mit.edu:8000/travis-hook before_install: - - BUILDOPTS="LLVM_CONFIG=llvm-config-3.3 LLVM_LLC=llc-3.3 VERBOSE=1 USE_BLAS64=0"; for lib in LLVM ZLIB SUITESPARSE ARPACK BLAS FFTW LAPACK GMP MPFR PCRE LIBUNWIND READLINE GRISU OPENLIBM RMATH; do export BUILDOPTS="$BUILDOPTS USE_SYSTEM_$lib=1"; done + - BUILDOPTS="LLVM_CONFIG=llvm-config-3.3 LLVM_LLC=llc-3.3 VERBOSE=1 USE_BLAS64=0"; for lib in LLVM ZLIB SUITESPARSE ARPACK BLAS FFTW LAPACK GMP MPFR PCRE LIBUNWIND GRISU OPENLIBM RMATH; do export BUILDOPTS="$BUILDOPTS USE_SYSTEM_$lib=1"; done - sudo apt-get update -qq -y - sudo apt-get install zlib1g-dev - sudo add-apt-repository ppa:staticfloat/julia-deps -y - sudo apt-get update -qq -y - - sudo apt-get install patchelf gfortran llvm-3.3-dev libsuitesparse-dev libncurses5-dev libopenblas-dev liblapack-dev libarpack2-dev libfftw3-dev libgmp-dev libpcre3-dev libunwind7-dev libreadline-dev libdouble-conversion-dev libopenlibm-dev librmath-dev libmpfr-dev -y + - sudo apt-get install patchelf gfortran llvm-3.3-dev libsuitesparse-dev libopenblas-dev liblapack-dev libarpack2-dev libfftw3-dev libgmp-dev libpcre3-dev libunwind7-dev libdouble-conversion-dev libopenlibm-dev librmath-dev libmpfr-dev -y script: - make $BUILDOPTS prefix=/tmp/julia install - cd .. && mv julia julia2 - - cd /tmp/julia/share/julia/test && /tmp/julia/bin/julia-debug-readline runtests.jl all + - cd /tmp/julia/share/julia/test && /tmp/julia/bin/julia-debug-basic runtests.jl all - cd - && mv julia2 julia - echo "Ready for packaging..." diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 917f54cd9673f..6b06e4ecf2d0a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ A useful bug report filed as a Github issue provides information about how to re 1. Before opening a new [Github issue](https://github.com/JuliaLang/julia/issues): - Try searching the existing issues or the [`julia-users` mailing list](https://groups.google.com/forum/#!forum/julia-users) to see if someone else has already noticed the same problem. - Try some simple debugging techniques to help isolate the problem. - - Try running the code with the debug REPL. If you have a source distribution of Julia, you can build your own debug REPL with `make debug`, which produces the `usr/bin/julia-debug-basic` and `usr/bin/julia-debug-readline` REPLs. + - Try running the code with the debug REPL. If you have a source distribution of Julia, you can build your own debug REPL with `make debug`, which produces the `usr/bin/julia-debug-basic` REPL. - Consider running the debug REPL in a debugger such as `gdb` or `lldb`. Obtaining even a simple [backtrace](http://www.unknownroad.com/rtfm/gdbtut/gdbsegfault.html) is very useful. - If Julia segfaults, try following [these debugging tips](https://gist.github.com/staticfloat/6188418#segfaults-during-bootstrap-sysimgjl) to help track down the specific origin of the bug. diff --git a/LICENSE.md b/LICENSE.md index 335bd1042d471..f056c82a3961d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ Julia is licensed under the MIT License: -> Copyright (c) 2009-2013: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, +> Copyright (c) 2009-2014: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, > and other contributors: > > https://github.com/JuliaLang/julia/contributors @@ -24,25 +24,6 @@ Julia is licensed under the MIT License: > OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION > WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -[`repl-readline.c`](https://github.com/JuliaLang/julia/blob/master/ui/repl-readline.c) -is licensed under the GNU General Public License Version 2: - -> Copyright (c) 2009-2013: Jeff Bezanson, Stefan Karpinski, Viral B. Shah. -> -> This program is free software; you can redistribute it and/or modify -> it under the terms of the GNU General Public License as published by -> the Free Software Foundation; either version 2 of the License, or -> (at your option) any later version. -> -> This program is distributed in the hope that it will be useful, -> but WITHOUT ANY WARRANTY; without even the implied warranty of -> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -> GNU General Public License for more details. -> -> You should have received a copy of the GNU General Public License along -> with this program; if not, write to the Free Software Foundation, Inc., -> 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - External libraries, if used, include their own licenses: - [7-Zip](http://www.7-zip.org/license.txt) @@ -67,7 +48,6 @@ External libraries, if used, include their own licenses: - [OPENBLAS](https://raw.github.com/xianyi/OpenBLAS/master/LICENSE) - [PATCHELF](http://hydra.nixos.org/build/1524660/download/1/README) - [PCRE](http://www.pcre.org/licence.txt) -- [READLINE](http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html) - [RMATH](http://www.r-project.org/Licenses/) - [SUITESPARSE](http://www.cise.ufl.edu/research/sparse/SuiteSparse/current/SuiteSparse/) - [ZLIB](http://zlib.net/zlib_license.html) diff --git a/Make.inc b/Make.inc index 421ef097d9aca..e6d4ad20b0f0a 100644 --- a/Make.inc +++ b/Make.inc @@ -12,7 +12,6 @@ OPENBLAS_TARGET_ARCH= # Use libraries available on the system instead of building them USE_SYSTEM_LLVM=0 USE_SYSTEM_LIBUNWIND=0 -USE_SYSTEM_READLINE=0 USE_SYSTEM_PCRE=0 USE_SYSTEM_LIBM=0 USE_SYSTEM_OPENLIBM=0 @@ -163,7 +162,7 @@ fPIC = -fPIC EXE = endif -DEFAULT_REPL = readline +DEFAULT_REPL = basic JULIAGC = MARKSWEEP USE_COPY_STACKS = 1 @@ -350,22 +349,6 @@ LLVM_CONFIG=$(LLVMROOT)/bin/llvm-config$(EXE) LLVM_LLC=$(LLVMROOT)/bin/llc$(EXE) endif -ifeq ($(USE_SYSTEM_READLINE), 1) -READLINE = -lreadline -else -ifeq ($(OS),WINNT) -READLINE = $(build_libdir)/libreadline.a -else -READLINE = $(build_libdir)/libreadline.a -endif -endif - -ifeq ($(OS),WINNT) -READLINE += $(build_libdir)/libhistory.a -else -READLINE += -lncurses -endif - ifeq ($(USE_SYSTEM_PCRE), 1) PCRE_CONFIG = pcre-config else diff --git a/README.md b/README.md index 88e565ef23b61..617a033908d52 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,6 @@ Otherwise, install or contact your systems administrator to install a more recen Problem | Possible Solution ------------------------|--------------------- OpenBLAS build failure | Set one of the following build options in `Make.user` and build again:
  • `OPENBLAS_TARGET_ARCH=BARCELONA` (AMD CPUs) or `OPENBLAS_TARGET_ARCH=NEHALEM` (Intel CPUs)
      Set `OPENBLAS_DYNAMIC_ARCH = 0` to disable compiling multiple architectures in a single binary.
  • `USE_SYSTEM_BLAS=1` uses the system provided `libblas`
    • Set `LIBBLAS=-lopenblas` and `LIBBLASNAME=libopenblas` to force the use of the system provided OpenBLAS when multiple BLAS versions are installed
- readline build error | Set `USE_SYSTEM_READLINE=1` in `Make.user` - ncurses build error | Install the `libncurses5` development package
  • Debian/Ubuntu: `apt-get install libncurses5-dev`
  • RPM-based systems: `yum install libncurses5-devel`
Illegal Instruction error | Check if your CPU supports AVX while your OS does not (e.g. through virtualization, as described in [this issue](https://github.com/JuliaLang/julia/issues/3263)), and try installing LLVM 3.3 instead of LLVM 3.2. ### OS X @@ -168,7 +166,6 @@ Julia uses the following external libraries, which are automatically downloaded - **[LLVM]** — compiler infrastructure. - **[FemtoLisp]** — packaged with Julia source, and used to implement the compiler front-end. -- **[readline]** — library allowing shell-like line editing in the terminal, with history and familiar key bindings. - **[libuv]** — portable, high-performance event-based I/O library - **[OpenLibm]** — a portable libm library containing elementary math functions. - **[OpenSpecFun]** — a library containing Bessel and error functions of complex arguments. @@ -208,7 +205,6 @@ Julia uses the following external libraries, which are automatically downloaded [PCRE]: http://www.pcre.org/ [LLVM]: http://www.llvm.org/ [FemtoLisp]: https://github.com/JeffBezanson/femtolisp -[readline]: http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html [GMP]: http://gmplib.org/ [MPFR]: http://www.mpfr.org/ [double-conversion]: http://double-conversion.googlecode.com/ diff --git a/README.windows.md b/README.windows.md index d187ebb00c367..caeca18f27126 100644 --- a/README.windows.md +++ b/README.windows.md @@ -165,13 +165,12 @@ Julia runs on Windows XP SP2 or later (including Windows Vista, Windows 7, and W ``` make run-julia ``` - (the full syntax is `make run-julia[-release|-debug] [DEFAULT_REPL=(basic|readline)]`) + (the full syntax is `make run-julia[-release|-debug]`) - Using the Julia executables directly ``` - usr/bin/julia-readline + usr/bin/julia-basic ``` - (or `julia-basic` if you prefer) ## Cross-compiling diff --git a/contrib/julia.lang b/contrib/julia.lang index fa06126e8c433..cd0730d86a281 100644 --- a/contrib/julia.lang +++ b/contrib/julia.lang @@ -148,7 +148,6 @@ - begin diff --git a/contrib/mac/macports.make b/contrib/mac/macports.make index bccebe41be0e0..eb1c621a588e1 100644 --- a/contrib/mac/macports.make +++ b/contrib/mac/macports.make @@ -17,7 +17,6 @@ USEGCC=1 USECLANG=0 SUITESPARSE_VER_MAJOR=4 JMAKEFLAGS = USE_SYSTEM_LLVM=1 LLVM_CONFIG=${LLVM_CONFIG} \ - USE_SYSTEM_READLINE=1 \ USE_SYSTEM_PCRE=1 PCRE_CONFIG=${PCRE_CONFIG} \ USE_SYSTEM_LIBM=1 \ USE_SYSTEM_OPENLIBM=0 \ diff --git a/contrib/vagrant/README.md b/contrib/vagrant/README.md index 8ef05f3a6ba71..e37c335127818 100644 --- a/contrib/vagrant/README.md +++ b/contrib/vagrant/README.md @@ -40,4 +40,4 @@ downloaded during provisioning. To take advantage of these dependencies, use the `jlmake` alias in place of `make`. When the build is complete, you can run -`~/julia/usr/bin/julia-readline` to start Julia. +`~/julia/usr/bin/julia-basic` to start Julia. diff --git a/contrib/vagrant/Vagrantfile b/contrib/vagrant/Vagrantfile index c406fc826c8eb..ca2fac7482c04 100644 --- a/contrib/vagrant/Vagrantfile +++ b/contrib/vagrant/Vagrantfile @@ -7,7 +7,7 @@ rm /home/vagrant/postinstall.sh cat - > /home/vagrant/.bash_aliases <<"EOF" export JULIA_VAGRANT_BUILD=1 BUILDOPTS="LLVM_CONFIG=llvm-config-3.3 USE_BLAS64=0" -for lib in LLVM ZLIB SUITESPARSE ARPACK BLAS FFTW LAPACK GMP MPFR PCRE LIBUNWIND READLINE GRISU OPENLIBM RMATH; do +for lib in LLVM ZLIB SUITESPARSE ARPACK BLAS FFTW LAPACK GMP MPFR PCRE LIBUNWIND GRISU OPENLIBM RMATH; do export BUILDOPTS="$BUILDOPTS USE_SYSTEM_$lib=1" done @@ -18,7 +18,7 @@ apt-get update -qq -y apt-get install python-software-properties -y add-apt-repository ppa:staticfloat/julia-deps -y apt-get update -qq -y -apt-get install g++ git make patchelf gfortran llvm-3.3 libsuitesparse-dev libncurses5-dev libopenblas-dev liblapack-dev libarpack2-dev libfftw3-dev libgmp-dev libpcre3-dev libunwind7-dev libreadline-dev libdouble-conversion-dev libopenlibm-dev librmath-dev libmpfr-dev -y +apt-get install g++ git make patchelf gfortran llvm-3.3 libsuitesparse-dev libopenblas-dev liblapack-dev libarpack2-dev libfftw3-dev libgmp-dev libpcre3-dev libunwind7-dev libdouble-conversion-dev libopenlibm-dev librmath-dev libmpfr-dev -y SCRIPT Vagrant.configure("2") do |config| diff --git a/contrib/windows/build-installer.nsi b/contrib/windows/build-installer.nsi index 8af56e14a1551..20372ab07761f 100644 --- a/contrib/windows/build-installer.nsi +++ b/contrib/windows/build-installer.nsi @@ -38,7 +38,7 @@ Section "Dummy Section" SecDummy SetOutPath $INSTDIR File /a /r "julia-${Commit}\*" WriteUninstaller "$INSTDIR\Uninstall.exe" - CreateShortcut "$INSTDIR\julia.lnk" "$INSTDIR\bin\julia-readline.exe" + CreateShortcut "$INSTDIR\julia.lnk" "$INSTDIR\bin\julia-basic.exe" SectionEnd Section "uninstall" diff --git a/contrib/windows/prepare-julia-env.bat b/contrib/windows/prepare-julia-env.bat index e8dbd805077c8..9f7652fbf4bde 100644 --- a/contrib/windows/prepare-julia-env.bat +++ b/contrib/windows/prepare-julia-env.bat @@ -8,7 +8,7 @@ set SYS_PATH=%PATH% set PATH=%~dp0;%~dp0bin;%~dp0usr\bin;%~dp0..\usr\bin;%~dp0..\..\usr\bin;%SYS_PATH% -set JULIA_EXE=julia-readline.exe +set JULIA_EXE=julia-basic.exe for %%A in (%JULIA_EXE%) do set JULIA_HOME=%%~dp$PATH:A set JULIA=%JULIA_HOME%%JULIA_EXE% set PATH=%SYS_PATH% diff --git a/contrib/windows/test-julia.bat b/contrib/windows/test-julia.bat index 09afe937afabe..f51050e822dc6 100644 --- a/contrib/windows/test-julia.bat +++ b/contrib/windows/test-julia.bat @@ -13,7 +13,7 @@ pushd %cd% setlocal enableextensions enabledelayedexpansion call "%~dp0prepare-julia-env.bat" %tests% cd "%JULIA_HOME%..\share\julia\test" -call "%JULIA_HOME%julia-readline.exe" runtests.jl %tests% +call "%JULIA_HOME%julia-basic.exe" runtests.jl %tests% endlocal popd pause diff --git a/deps/.gitignore b/deps/.gitignore index aba15f6131584..9c0c103e80835 100644 --- a/deps/.gitignore +++ b/deps/.gitignore @@ -22,9 +22,7 @@ /openblas-* /patchelf-* /pcre-* -/readline-* -!readline-win.h /root /SuiteSparse-* /zlib-* -/utf8proc-* \ No newline at end of file +/utf8proc-* diff --git a/deps/Makefile b/deps/Makefile index 9898d385b5b1d..f9734570c442c 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -16,7 +16,7 @@ CONFIGURE_COMMON += F77="$(FC)" CC="$(CC)" CXX="$(CXX)" # they will override the values passed above to ./configure MAKE_COMMON = DESTDIR="" prefix=$(build_prefix) bindir=$(build_bindir) libdir=$(build_libdir) libexecdir=$(build_libexecdir) datarootdir=$(build_datarootdir) includedir=$(build_includedir) sysconfdir=$(build_sysconfdir) -#autoconf configure-driven scripts: llvm readline pcre arpack fftw unwind gmp mpfr patchelf uv +#autoconf configure-driven scripts: llvm pcre arpack fftw unwind gmp mpfr patchelf uv #custom Makefile rules: openlibm Rmath double-conversion random suitesparse-wrapper suitesparse lapack openblas utf8proc # prevent installing libs into usr/lib64 on opensuse @@ -65,14 +65,6 @@ ifeq ($(USE_SYSTEM_LLVM), 0) STAGE1_DEPS += llvm endif -ifeq ($(OS),WINNT) -READLINE_VER = 5.0 -endif - -ifeq ($(USE_SYSTEM_READLINE), 0) -STAGE1_DEPS += readline -endif - ifeq ($(USE_SYSTEM_PCRE), 0) STAGE1_DEPS += pcre endif @@ -146,7 +138,7 @@ install: $(addprefix install-, $(LIBS)) cleanall: $(addprefix clean-, $(LIBS)) distclean: $(addprefix distclean-, $(LIBS)) rm -rf $(build_prefix) -getall: get-llvm get-readline get-uv get-pcre get-double-conversion get-openlibm get-openspecfun get-random get-openblas get-lapack get-fftw get-suitesparse get-arpack get-unwind get-osxunwind get-gmp get-mpfr get-zlib get-patchelf get-utf8proc +getall: get-llvm get-uv get-pcre get-double-conversion get-openlibm get-openspecfun get-random get-openblas get-lapack get-fftw get-suitesparse get-arpack get-unwind get-osxunwind get-gmp get-mpfr get-zlib get-patchelf get-utf8proc ## PATHS ## # sort is used to remove potential duplicates @@ -407,86 +399,6 @@ install-llvm: $(LLVM_OBJ_TARGET) #todo: LLVM make check target is broken on julia.mit.edu (and really slow elsewhere) -## GNU readline ## - - -ifeq ($(OS),WINNT) -READLINE_OBJ_TARGET = $(build_libdir)/libreadline.a -READLINE_OBJ_SOURCE = readline-$(READLINE_VER)/libreadline.a -READLINE_URL = https://github.com/JuliaLang/readline/tarball/master -READLINE_OPTS = --disable-shared --enable-static --with-curses -READLINE_CFLAGS = LOCAL_DEFS="-include $(abspath .)/readline-win.h" -readline-$(READLINE_VER).tar.gz: - $(JLDOWNLOAD) $@ $(READLINE_URL) - touch -c $@ -readline-$(READLINE_VER)/configure: readline-$(READLINE_VER).tar.gz - mkdir readline-$(READLINE_VER) - $(TAR) -C readline-$(READLINE_VER) --strip-components 1 -xf $< - touch -c $@ -$(READLINE_OBJ_TARGET): $(READLINE_OBJ_SOURCE) readline-$(READLINE_VER)/checked - $(MAKE) -C readline-$(READLINE_VER) $(READLINE_CFLAGS) install $(MAKE_COMMON) - touch -c $@ -else -READLINE_OBJ_TARGET = $(build_libdir)/libreadline.$(SHLIB_EXT) -ifneq ($(OS),Darwin) -READLINE_OBJ_SOURCE = readline-$(READLINE_VER)/shlib/libreadline.$(SHLIB_EXT).$(READLINE_VER) -else -READLINE_OBJ_SOURCE = readline-$(READLINE_VER)/shlib/libreadline.$(READLINE_VER).$(SHLIB_EXT) -endif -READLINE_URL = ftp://ftp.gnu.org/gnu/readline/readline-$(READLINE_VER).tar.gz -READLINE_OPTS = --enable-shared --enable-static --with-curses -READLINE_CFLAGS = -readline-$(READLINE_VER).tar.gz: - $(JLDOWNLOAD) $@ http://ftp.gnu.org/gnu/readline/$@ - touch -c $@ -readline-$(READLINE_VER)/configure: readline-$(READLINE_VER).tar.gz - $(TAR) zxf $< - cd readline-$(READLINE_VER) && patch -p0 < ../readline62-001 - cd readline-$(READLINE_VER) && patch -p0 < ../readline62-002 - cd readline-$(READLINE_VER) && patch -p0 < ../readline62-003 - cd readline-$(READLINE_VER) && patch -p0 < ../readline62-004 - touch -c $@ -$(READLINE_OBJ_TARGET): $(READLINE_OBJ_SOURCE) readline-$(READLINE_VER)/checked - $(MAKE) -C readline-$(READLINE_VER) install $(MAKE_COMMON) - chmod +w $(build_libdir)/libreadline.* $(build_libdir)/libhistory.* -ifeq ($(OS), Darwin) - $(INSTALL_NAME_CMD)libreadline.$(SHLIB_EXT) $(build_libdir)/libreadline.$(SHLIB_EXT) - $(INSTALL_NAME_CMD)libhistory.dylib $(build_libdir)/libhistory.dylib -else ifeq ($(OS), Linux) - for filename in $(build_libdir)/libhistory.so* $(build_libdir)/libreadline.so* ; do \ - $(PATCHELF) --set-rpath '$$ORIGIN' $$filename ;\ - done -endif - touch -c $@ -endif - -readline-$(READLINE_VER)/config.status: readline-$(READLINE_VER)/configure - cd readline-$(READLINE_VER) && \ - chmod a+x ./configure && \ - ./configure $(CONFIGURE_COMMON) $(READLINE_OPTS) - touch -c $@ -$(READLINE_OBJ_SOURCE): readline-$(READLINE_VER)/config.status - cd readline-$(READLINE_VER) && \ - $(MAKE) $(READLINE_CFLAGS) - touch -c $@ -readline-$(READLINE_VER)/checked: $(READLINE_OBJ_SOURCE) - cd readline-$(READLINE_VER) && \ - $(MAKE) check $(READLINE_CFLAGS) - echo 1 > $@ - -clean-readline: - -$(MAKE) -C readline-$(READLINE_VER) clean - -rm -f $(READLINE_OBJ_TARGET) -distclean-readline: - -rm -rf readline-$(READLINE_VER).tar.gz readline-$(READLINE_VER) - -get-readline: readline-$(READLINE_VER).tar.gz -configure-readline: readline-$(READLINE_VER)/config.status -compile-readline: $(READLINE_OBJ_SOURCE) -check-readline: readline-$(READLINE_VER)/checked -install-readline: $(READLINE_OBJ_TARGET) - - ## LIBUV ## UV_SRC_TARGET = libuv/.libs/libuv.a diff --git a/deps/Versions.make b/deps/Versions.make index 81e401a496e79..3662ae8db64ea 100644 --- a/deps/Versions.make +++ b/deps/Versions.make @@ -1,6 +1,5 @@ LLVM_VER = 3.3 LLVM_LIB_SUFFIX = -READLINE_VER = 6.2 PCRE_VER = 8.31 GRISU_VER = 1.1.1 DSFMT_VER = 2.2 diff --git a/deps/readline-win.h b/deps/readline-win.h deleted file mode 100644 index 604c2ae979cbc..0000000000000 --- a/deps/readline-win.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef READLINE_WIN_H -#define READLINE_WIN_H - -extern void *jl_uv_stderr; -#include -#define fprintf jl_printf -#undef stderr -#define stderr jl_uv_stderr -#define fflush (void) -#define putc jl_putc -#define fwrite(ptr,size,count,stream) jl_write(stream,ptr,size*count) -//#define fileno -1// - -#endif // WIN_H diff --git a/deps/readline62-001 b/deps/readline62-001 deleted file mode 100644 index d4563c3b1130d..0000000000000 --- a/deps/readline62-001 +++ /dev/null @@ -1,46 +0,0 @@ - READLINE PATCH REPORT - ===================== - -Readline-Release: 6.2 -Patch-ID: readline62-001 - -Bug-Reported-by: Clark J. Wang -Bug-Reference-ID: -Bug-Reference-URL: http://lists.gnu.org/archive/html/bug-bash/2011-02/msg00157.html - -Bug-Description: - -The readline vi-mode `cc', `dd', and `yy' commands failed to modify the -entire line. - -[This patch intentionally does not modify patchlevel] - -Patch (apply with `patch -p0'): - -*** ../readline-6.2-patched/vi_mode.c 2010-11-20 19:51:39.000000000 -0500 ---- vi_mode.c 2011-02-17 20:24:25.000000000 -0500 -*************** -*** 1115,1119 **** - _rl_vi_last_motion = c; - RL_UNSETSTATE (RL_STATE_VIMOTION); -! return (0); - } - #if defined (READLINE_CALLBACKS) ---- 1115,1119 ---- - _rl_vi_last_motion = c; - RL_UNSETSTATE (RL_STATE_VIMOTION); -! return (vidomove_dispatch (m)); - } - #if defined (READLINE_CALLBACKS) -*** ../readline-6.2-patched/callback.c 2010-06-06 12:18:58.000000000 -0400 ---- callback.c 2011-02-17 20:43:28.000000000 -0500 -*************** -*** 149,152 **** ---- 149,155 ---- - /* Should handle everything, including cleanup, numeric arguments, - and turning off RL_STATE_VIMOTION */ -+ if (RL_ISSTATE (RL_STATE_NUMERICARG) == 0) -+ _rl_internal_char_cleanup (); -+ - return; - } diff --git a/deps/readline62-002 b/deps/readline62-002 deleted file mode 100644 index c663a5313b70a..0000000000000 --- a/deps/readline62-002 +++ /dev/null @@ -1,57 +0,0 @@ - READLINE PATCH REPORT - ===================== - -Readline-Release: 6.2 -Patch-ID: readline62-002 - -Bug-Reported-by: Vincent Sheffer -Bug-Reference-ID: -Bug-Reference-URL: https://lists.gnu.org/archive/html/bug-readline/2011-08/msg00000.html - -Bug-Description: - -The readline shared library helper script needs to be updated for Mac OS X -10.7 (Lion, darwin11). - -Patch (apply with `patch -p0'): - -*** ../readline-6.2-patched/support/shobj-conf 2009-10-28 09:20:21.000000000 -0400 ---- support/shobj-conf 2011-08-27 13:25:23.000000000 -0400 -*************** -*** 158,162 **** - - # Darwin/MacOS X -! darwin[89]*|darwin10*) - SHOBJ_STATUS=supported - SHLIB_STATUS=supported ---- 172,176 ---- - - # Darwin/MacOS X -! darwin[89]*|darwin1[012]*) - SHOBJ_STATUS=supported - SHLIB_STATUS=supported -*************** -*** 187,191 **** - - case "${host_os}" in -! darwin[789]*|darwin10*) SHOBJ_LDFLAGS='' - SHLIB_XLDFLAGS='-dynamiclib -arch_only `/usr/bin/arch` -install_name $(libdir)/$@ -current_version $(SHLIB_MAJOR)$(SHLIB_MINOR) -compatibility_version $(SHLIB_MAJOR) -v' - ;; ---- 201,205 ---- - - case "${host_os}" in -! darwin[789]*|darwin1[0123]*) SHOBJ_LDFLAGS='' - SHLIB_XLDFLAGS='-dynamiclib -arch_only `/usr/bin/arch` -install_name $(libdir)/$@ -current_version $(SHLIB_MAJOR)$(SHLIB_MINOR) -compatibility_version $(SHLIB_MAJOR) -v' - ;; - -*** ../readline-6.2-patched/patchlevel 2010-01-14 10:15:52.000000000 -0500 ---- patchlevel 2011-11-17 11:09:35.000000000 -0500 -*************** -*** 1,3 **** - # Do not edit -- exists only for use by patch - -! 1 ---- 1,3 ---- - # Do not edit -- exists only for use by patch - -! 2 diff --git a/deps/readline62-003 b/deps/readline62-003 deleted file mode 100644 index 0462242e0c0d5..0000000000000 --- a/deps/readline62-003 +++ /dev/null @@ -1,76 +0,0 @@ - READLINE PATCH REPORT - ===================== - -Readline-Release: 6.2 -Patch-ID: readline62-003 - -Bug-Reported-by: Max Horn -Bug-Reference-ID: <20CC5C60-07C3-4E41-9817-741E48D407C5@quendi.de> -Bug-Reference-URL: http://lists.gnu.org/archive/html/bug-readline/2012-06/msg00005.html - -Bug-Description: - -A change between readline-6.1 and readline-6.2 to prevent the readline input -hook from being called too frequently had the side effect of causing delays -when reading pasted input on systems such as Mac OS X. This patch fixes -those delays while retaining the readline-6.2 behavior. - -Patch (apply with `patch -p0'): - -*** ../readline-6.2-patched/input.c 2010-05-30 18:33:01.000000000 -0400 ---- input.c 2012-06-25 21:08:42.000000000 -0400 -*************** -*** 410,414 **** - rl_read_key () - { -! int c; - - rl_key_sequence_length++; ---- 412,416 ---- - rl_read_key () - { -! int c, r; - - rl_key_sequence_length++; -*************** -*** 430,441 **** - while (rl_event_hook) - { -! if (rl_gather_tyi () < 0) /* XXX - EIO */ - { - rl_done = 1; - return ('\n'); - } - RL_CHECK_SIGNALS (); -- if (rl_get_char (&c) != 0) -- break; - if (rl_done) /* XXX - experimental */ - return ('\n'); ---- 432,447 ---- - while (rl_event_hook) - { -! if (rl_get_char (&c) != 0) -! break; -! -! if ((r = rl_gather_tyi ()) < 0) /* XXX - EIO */ - { - rl_done = 1; - return ('\n'); - } -+ else if (r == 1) /* read something */ -+ continue; -+ - RL_CHECK_SIGNALS (); - if (rl_done) /* XXX - experimental */ - return ('\n'); -*** ../readline-6.2-patched/patchlevel 2010-01-14 10:15:52.000000000 -0500 ---- patchlevel 2011-11-17 11:09:35.000000000 -0500 -*************** -*** 1,3 **** - # Do not edit -- exists only for use by patch - -! 2 ---- 1,3 ---- - # Do not edit -- exists only for use by patch - -! 3 diff --git a/deps/readline62-004 b/deps/readline62-004 deleted file mode 100644 index 5f3ba9bb322f1..0000000000000 --- a/deps/readline62-004 +++ /dev/null @@ -1,108 +0,0 @@ - READLINE PATCH REPORT - ===================== - -Readline-Release: 6.2 -Patch-ID: readline62-004 - -Bug-Reported-by: Jakub Filak -Bug-Reference-ID: -Bug-Reference-URL: https://bugzilla.redhat.com/show_bug.cgi?id=813289 - -Bug-Description: - -Attempting to redo (using `.') the vi editing mode `cc', `dd', or `yy' -commands leads to an infinite loop. - -Patch (apply with `patch -p0'): - -*** ../readline-6.2-patched/vi_mode.c 2011-02-25 11:17:02.000000000 -0500 ---- vi_mode.c 2012-06-02 12:24:47.000000000 -0400 -*************** -*** 1235,1243 **** - r = rl_domove_motion_callback (_rl_vimvcxt); - } -! else if (vi_redoing) - { - _rl_vimvcxt->motion = _rl_vi_last_motion; - r = rl_domove_motion_callback (_rl_vimvcxt); - } - #if defined (READLINE_CALLBACKS) - else if (RL_ISSTATE (RL_STATE_CALLBACK)) ---- 1297,1313 ---- - r = rl_domove_motion_callback (_rl_vimvcxt); - } -! else if (vi_redoing && _rl_vi_last_motion != 'd') /* `dd' is special */ - { - _rl_vimvcxt->motion = _rl_vi_last_motion; - r = rl_domove_motion_callback (_rl_vimvcxt); - } -+ else if (vi_redoing) /* handle redoing `dd' here */ -+ { -+ _rl_vimvcxt->motion = _rl_vi_last_motion; -+ rl_mark = rl_end; -+ rl_beg_of_line (1, key); -+ RL_UNSETSTATE (RL_STATE_VIMOTION); -+ r = vidomove_dispatch (_rl_vimvcxt); -+ } - #if defined (READLINE_CALLBACKS) - else if (RL_ISSTATE (RL_STATE_CALLBACK)) -*************** -*** 1317,1325 **** - r = rl_domove_motion_callback (_rl_vimvcxt); - } -! else if (vi_redoing) - { - _rl_vimvcxt->motion = _rl_vi_last_motion; - r = rl_domove_motion_callback (_rl_vimvcxt); - } - #if defined (READLINE_CALLBACKS) - else if (RL_ISSTATE (RL_STATE_CALLBACK)) ---- 1387,1403 ---- - r = rl_domove_motion_callback (_rl_vimvcxt); - } -! else if (vi_redoing && _rl_vi_last_motion != 'c') /* `cc' is special */ - { - _rl_vimvcxt->motion = _rl_vi_last_motion; - r = rl_domove_motion_callback (_rl_vimvcxt); - } -+ else if (vi_redoing) /* handle redoing `cc' here */ -+ { -+ _rl_vimvcxt->motion = _rl_vi_last_motion; -+ rl_mark = rl_end; -+ rl_beg_of_line (1, key); -+ RL_UNSETSTATE (RL_STATE_VIMOTION); -+ r = vidomove_dispatch (_rl_vimvcxt); -+ } - #if defined (READLINE_CALLBACKS) - else if (RL_ISSTATE (RL_STATE_CALLBACK)) -*************** -*** 1378,1381 **** ---- 1456,1472 ---- - r = rl_domove_motion_callback (_rl_vimvcxt); - } -+ else if (vi_redoing && _rl_vi_last_motion != 'y') /* `yy' is special */ -+ { -+ _rl_vimvcxt->motion = _rl_vi_last_motion; -+ r = rl_domove_motion_callback (_rl_vimvcxt); -+ } -+ else if (vi_redoing) /* handle redoing `yy' here */ -+ { -+ _rl_vimvcxt->motion = _rl_vi_last_motion; -+ rl_mark = rl_end; -+ rl_beg_of_line (1, key); -+ RL_UNSETSTATE (RL_STATE_VIMOTION); -+ r = vidomove_dispatch (_rl_vimvcxt); -+ } - #if defined (READLINE_CALLBACKS) - else if (RL_ISSTATE (RL_STATE_CALLBACK)) -*** ../readline-6.2-patched/patchlevel 2010-01-14 10:15:52.000000000 -0500 ---- patchlevel 2011-11-17 11:09:35.000000000 -0500 -*************** -*** 1,3 **** - # Do not edit -- exists only for use by patch - -! 3 ---- 1,3 ---- - # Do not edit -- exists only for use by patch - -! 4 diff --git a/doc/man/julia-readline.1 b/doc/man/julia-readline.1 deleted file mode 100644 index 96e9d01d3938f..0000000000000 --- a/doc/man/julia-readline.1 +++ /dev/null @@ -1 +0,0 @@ -.so man1/julia.1 diff --git a/doc/man/julia.1 b/doc/man/julia.1 index d289726b6e84d..251bd3c91cb4e 100644 --- a/doc/man/julia.1 +++ b/doc/man/julia.1 @@ -26,7 +26,7 @@ .\" from the front page of http://julialang.org/ .SH NAME -julia, julia-basic, julia-readline - high-level, high-performance dynamic programming language for technical computing +julia, julia-basic - high-level, high-performance dynamic programming language for technical computing .SH SYNOPSIS julia [option] [program] [args..] @@ -50,13 +50,7 @@ For a more in-depth discussion of the rationale and advantages of Julia over other systems, please see the online manual: http://docs.julialang.org/en/latest/manual/ -\fBjulia\fP is an alias for \fBjulia-readline\fP. - -\fBjulia-readline\fP launches an interactive Julia session with Readline -capabilities. - -\fBjulia-basic\fP launches an interactive Julia session without -Readline capabilities. +\fBjulia\fP is an alias for \fBjulia-basic\fP. If a Julia source file is given as \fIprogram\fP (possibly with arguments in \fIargs\fP) then, instead of launching an interactive session, Julia executes diff --git a/ui/.gitignore b/ui/.gitignore index d98615d267e7d..f6371b1c28720 100644 --- a/ui/.gitignore +++ b/ui/.gitignore @@ -4,7 +4,5 @@ /julia-debug-basic /julia-debug-cloud -/julia-debug-readline /julia-release-basic /julia-release-cloud -/julia-release-readline diff --git a/ui/Makefile b/ui/Makefile index 0d556869187f2..7409ac9d8d421 100644 --- a/ui/Makefile +++ b/ui/Makefile @@ -18,8 +18,8 @@ JLDFLAGS += $(WHOLE_ARCHIVE) $(build_libdir)/libopenlibm.a $(NO_WHOLE_ARCHIVE) endif endif -julia-release: julia-readline julia-basic -julia-debug: julia-debug-readline julia-debug-basic +julia-release: julia-basic +julia-debug: julia-debug-basic release debug: $(MAKE) julia-$@ @@ -39,8 +39,6 @@ julia_res.o: $(JULIAHOME)/contrib/windows/julia.rc '/^(\d+)\.?(\d*)\.?(\d*)/ && \ print int $$1,",",int $$2,",0,",int $$3'` && \ $(CROSS_COMPILE)windres $< -O coff -o $@ -DJLVER=$$JLVERi -DJLVER_STR=\\\"$$JLVER\\\" -$(build_bindir)/julia-readline$(EXE): julia_res.o -$(build_bindir)/julia-debug-readline$(EXE): julia_res.o $(build_bindir)/julia-basic$(EXE): julia_res.o $(build_bindir)/julia-debug-basic$(EXE): julia_res.o JLDFLAGS += julia_res.o @@ -48,22 +46,15 @@ endif julia-basic: $(build_bindir)/julia-basic$(EXE) julia-debug-basic: $(build_bindir)/julia-debug-basic$(EXE) -julia-readline: $(build_bindir)/julia-readline$(EXE) -julia-debug-readline: $(build_bindir)/julia-debug-readline$(EXE) $(build_bindir)/julia-basic$(EXE): repl.o repl-basic.o @$(call PRINT_LINK, $(CXX) $(LINK_FLAGS) $(SHIPFLAGS) $^ -o $@ -L$(build_private_libdir) -L$(build_libdir) -L$(build_shlibdir) -ljulia $(JLDFLAGS)) $(build_bindir)/julia-debug-basic$(EXE): repl.do repl-basic.do @$(call PRINT_LINK, $(CXX) $(LINK_FLAGS) $(DEBUGFLAGS) $^ -o $@ -L$(build_private_libdir) -L$(build_libdir) -L$(build_shlibdir) -ljulia-debug $(JLDFLAGS)) -$(build_bindir)/julia-readline$(EXE): repl.o repl-readline.o - @$(call PRINT_LINK, $(CXX) $(LINK_FLAGS) $(SHIPFLAGS) $^ -o $@ $(READLINE) -L$(build_private_libdir) -L$(build_libdir) -L$(build_shlibdir) -ljulia $(JLDFLAGS)) || (echo "*** Please ensure that the ncurses-devel package is installed on your OS, and try again. ***" && false) -$(build_bindir)/julia-debug-readline$(EXE): repl.do repl-readline.do - @$(call PRINT_LINK, $(CXX) $(LINK_FLAGS) $(DEBUGFLAGS) $^ -o $@ $(READLINE) -L$(build_private_libdir) -L$(build_libdir) -L$(build_shlibdir) -ljulia-debug $(JLDFLAGS)) - clean: | $(CLEAN_TARGETS) rm -f *.o *.do - rm -f $(build_bindir)/julia-*-basic $(build_bindir)/julia-*-readline $(build_bindir)/julia + rm -f $(build_bindir)/julia-*-basic $(build_bindir)/julia .PHONY: clean release debug julia-release julia-debug diff --git a/ui/repl-readline.c b/ui/repl-readline.c deleted file mode 100644 index a406cd4dfb2d7..0000000000000 --- a/ui/repl-readline.c +++ /dev/null @@ -1,882 +0,0 @@ -/* - repl-readline.c: Julia REPL with readline support. - Copyright (C) 2009-2011, Jeff Bezanson, Stefan Karpinski, Viral B. Shah. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "repl.h" -#ifdef __WIN32__ -# define WIN32_LEAN_AND_MEAN -# include -# include -#endif - -extern int asprintf(char **strp, const char *fmt, ...); - -#define USE_READLINE_STATIC -#include -#include - -int prompt_length; -int disable_history; -static char *history_file = NULL; -static jl_value_t *rl_ast = NULL; -static char *strtok_saveptr; - -// yes, readline uses inconsistent indexing internally. -#define history_rem(n) remove_history(n-history_base) - -static void init_history(void) -{ - using_history(); - if (disable_history) return; - struct stat stat_info; - if (!stat(".julia_history", &stat_info)) { - // history file in current dir - history_file = ".julia_history"; - } - else { - char *histenv = getenv("JULIA_HISTORY"); - if (histenv) { - history_file = histenv; - } - else { -#ifndef __WIN32__ - char *home = getenv("HOME"); - if (!home) return; - asprintf(&history_file, "%s/.julia_history", home); -#else - char *homedrive = getenv("HOMEDRIVE"); - char *homepath = getenv("HOMEPATH"); - if (!homedrive || !homepath) return; - asprintf(&history_file, "%s/%s/.julia_history", homedrive, homepath); -#endif - } - } - if (!stat(history_file, &stat_info)) { - read_history(history_file); - for (;;) { - HIST_ENTRY *entry = history_get(history_base); - if (entry && isspace(entry->line[0])) - free_history_entry(history_rem(history_base)); - else break; - } - int i, j, k; - for (i=1 ;; i++) { - HIST_ENTRY *first = history_get(i); - if (!first) break; - int length = strlen(first->line)+1; - for (j = i+1 ;; j++) { - HIST_ENTRY *child = history_get(j); - if (!child || !isspace(child->line[0])) break; - length += strlen(child->line)+1; - } - if (j == i+1) continue; - first->line = (char*)realloc(first->line, length); - char *p = strchr(first->line, '\0'); - for (k = i+1; k < j; k++) { - *p = '\n'; - #ifndef __WIN32__ - p = stpcpy(p+1, history_get(i+1)->line); - #else - p = strcpy(p+1, history_get(i+1)->line); - #endif - free_history_entry(history_rem(i+1)); - } - } - } - else if (errno == ENOENT) { - write_history(history_file); - } - else { - jl_printf(jl_uv_stderr, "history file error: %s\n", strerror(errno)); - exit(1); - } -} - -static int last_hist_is_temp = 0; -static int last_hist_offset = -1; - -static void add_history_temporary(char *input) -{ - if (!input || !*input) return; - if (last_hist_is_temp) { - history_rem(history_length); - last_hist_is_temp = 0; - } - last_hist_offset = -1; - add_history(input); - last_hist_is_temp = 1; -} - -static void add_history_permanent(char *input) -{ - if (!input || !*input) return; - if (last_hist_is_temp) { - history_rem(history_length); - last_hist_is_temp = 0; - } - last_hist_offset = -1; - HIST_ENTRY *entry = history_get(history_length); - if (entry && !strcmp(input, entry->line)) return; - last_hist_offset = where_history(); - add_history(input); - if (history_file) - append_history(1, history_file); -} - -static int line_start(int point) -{ - if (!point) return 0; - int i = point-1; - for (; i; i--) if (rl_line_buffer[i] == '\n') return i+1; - return rl_line_buffer[i] == '\n' ? 1 : 0; -} - -static int line_end(int point) -{ - char *nl = strchr(rl_line_buffer + point, '\n'); - if (!nl) return rl_end; - return nl - rl_line_buffer; -} - -static int strip_initial_spaces = 0; -static int spaces_suppressed = 0; - -static void reset_indent(void) -{ - strip_initial_spaces = 0; - spaces_suppressed = 0; -} - -// TODO: is it appropriate to call this on the int values readline uses? -static int jl_word_char(uint32_t wc) -{ - return strchr(rl_completer_word_break_characters, wc) == NULL; -} - -static int newline_callback(int count, int key) -{ - if (!rl_point) return 0; - spaces_suppressed = 0; - rl_insert_text("\n"); - int i; - for (i = 0; i < prompt_length; i++) - rl_insert_text(" "); - return 0; -} - -static jl_value_t* call_jl_function_with_string(const char *fname, const char *arg, size_t arglen) -{ - jl_value_t *f = jl_get_global(jl_base_module,jl_symbol(fname)); - assert(f); - jl_value_t **fargs; - JL_GC_PUSHARGS(fargs, 1); - fargs[0] = jl_pchar_to_string((char*)arg, arglen); - jl_value_t *result = jl_apply((jl_function_t*)f, fargs, 1); - JL_GC_POP(); - return result; -} - -static jl_value_t* repl_parse_input_line(char *buf) -{ - if (buf[0] == ';') { - buf++; - return call_jl_function_with_string("repl_hook", buf, strlen(buf)); - } - else if (buf[0] == '?') { - char *tmpbuf; - asprintf(&tmpbuf, "@Base.help %s", buf+1); - jl_value_t *v = jl_parse_input_line(tmpbuf); - free(tmpbuf); - return v; - } - return jl_parse_input_line(buf); -} - -static int return_callback(int count, int key) -{ - static int consecutive_returns = 0; - if (rl_point > prompt_length && rl_point == rl_end && - rl_line_buffer[rl_point-prompt_length-1] == '\n') - consecutive_returns++; - else - consecutive_returns = 0; - add_history_temporary(rl_line_buffer); - rl_ast = repl_parse_input_line(rl_line_buffer); - rl_done = !rl_ast || !jl_is_expr(rl_ast) || - (((jl_expr_t*)rl_ast)->head != jl_incomplete_sym) || - consecutive_returns > 1; - if (!rl_done) { - newline_callback(count, key); - } - else { - reset_indent(); - rl_point = rl_end; - rl_redisplay(); - } - return 0; -} - -static int suppress_space(void) -{ - int i; - for (i = line_start(rl_point); i < rl_point; i++) - if (rl_line_buffer[i] != ' ') return 0; - if (spaces_suppressed < strip_initial_spaces) return 1; - return 0; -} - -static int space_callback(int count, int key) -{ - if (!rl_point) strip_initial_spaces++; - else if (suppress_space()) spaces_suppressed++; - else rl_insert_text(" "); - return 0; -} - -#if defined(_WIN32) -char *strtok_r(char *str, const char *delim, char **save) -{ - char *res, *last; - - if (!save) - return strtok(str, delim); - if (!str && !(str = *save)) - return NULL; - last = str + strlen(str); - if ((*save = res = strtok(str, delim))) { - *save += strlen(res); - if (*save < last) - (*save)++; - else - *save = NULL; - } - return res; -} -#endif - -int complete_method_table() -{ - if (rl_point < 2 || rl_line_buffer[rl_point-1] != '(') return 0; - - // extract the token preceding the ( - int tokenstart = rl_point-2; - - // check for special operators - if (strchr("\\><=|&+-*/%^~", rl_line_buffer[tokenstart])){ - while (tokenstart>0 && strchr("<>=!", rl_line_buffer[tokenstart-1])){ - tokenstart--; - } - } - else{ - // check for functions that might contain ! but not start with ! - while (tokenstart>=0 && (jl_word_char(rl_line_buffer[tokenstart]) || - rl_line_buffer[tokenstart] == '!')) { - tokenstart--; - } - tokenstart++; - // ! can't be the first character of a function, unless it's the only character - if (tokenstart != rl_point-2 && rl_line_buffer[tokenstart] == '!'){ - tokenstart ++; - } - } - - jl_value_t* result = call_jl_function_with_string("repl_methods", - &rl_line_buffer[tokenstart], - rl_point-tokenstart-1); - - if (!jl_is_byte_string(result)) return 0; - char *completions = jl_string_data(result); - - int nallocmethods = 0; - size_t nchars = strlen(completions); - for (size_t i=0; i maxlen) maxlen = len; - methods[nmethods+1] = method; - nmethods++; - } - - rl_display_match_list(methods, nmethods, maxlen); - free(methods); - rl_forced_update_display(); - return 1; -} - -int complete_filenames_query() -{ - if (rl_line_buffer[0] == ';') - return 1; //rl_filename_completion_function(ch, c); - - int instring = 0; - int incmd = 0; - int escaped = 0; - int nearquote = 0; - int i; - for (i = 0; i < rl_point; ++i) { - switch (rl_line_buffer[i]) { - case '\\': - if (instring) - escaped ^= 1; - nearquote = 0; - break; - case '\'': - if (!instring) - nearquote = 1; - break; - case '"': - if (!escaped && !nearquote && !incmd) - instring ^= 1; - escaped = nearquote = 0; - break; - case '`': - if (!escaped && !nearquote && !instring) - incmd ^= 1; - escaped = nearquote = 0; - break; - default: - escaped = nearquote = 0; - } - } - return (instring || incmd); -} - -int complete_filenames() -{ - if (!complete_filenames_query()) - return 0; - - char * bk_completer_word_break_characters = rl_completer_word_break_characters; - char * (*bk_completion_entry_function)(const char*, int) = rl_completion_entry_function; - - rl_completer_word_break_characters = " \t\n\"\\'`@$><=;|&{("; - rl_completion_entry_function = rl_filename_completion_function; - - rl_complete_internal('!'); - - rl_completer_word_break_characters = bk_completer_word_break_characters; - rl_completion_entry_function = bk_completion_entry_function; - - return 1; -} - -static int tab_callback(int count, int key) -{ - if (!rl_point) { - strip_initial_spaces += tab_width; - return 0; - } - int i; - for (i = line_start(rl_point); i < rl_point; i++) { - if (rl_line_buffer[i] != ' ') { - // do tab completion - i = rl_point; - if (!complete_method_table()) { - if (!complete_filenames()) - rl_complete_internal('!'); - if (i < rl_point && rl_line_buffer[rl_point-1] == ' ') { - rl_delete_text(rl_point-1, rl_point); - rl_point = rl_point-1; - } - } - return 0; - } - } - // indent to next tab stop - if (suppress_space()) { - spaces_suppressed += tab_width; - } - else { - i = line_start(rl_point) + prompt_length; - do { rl_insert_text(" "); } while ((rl_point - i) % tab_width); - } - return 0; -} - -static int line_start_callback(int count, int key) -{ - reset_indent(); - int start = line_start(rl_point); - int flush_left = rl_point == 0 || rl_point == start + prompt_length; - rl_point = flush_left ? 0 : (!start ? start : start + prompt_length); - return 0; -} - -static int line_end_callback(int count, int key) -{ - reset_indent(); - int end = line_end(rl_point); - int flush_right = rl_point == end; - rl_point = flush_right ? rl_end : end; - return 0; -} - -static int line_kill_callback(int count, int key) -{ - reset_indent(); - int end = line_end(rl_point); - int flush_right = rl_point == end; - int kill = flush_right ? end + prompt_length + 1 : end; - if (kill > rl_end) kill = rl_end; - rl_kill_text(rl_point, kill); - return 0; -} - -static int backspace_callback(int count, int key) -{ - reset_indent(); - if (!rl_point) return 0; - - int i = line_start(rl_point), j = rl_point, k; - if (!i || rl_point <= i + prompt_length) goto backspace; - for (k = i; k < rl_point; k++) - if (rl_line_buffer[k] != ' ') goto backspace; - -//unindent: - k = i + prompt_length; - do { rl_point--; } while ((rl_point - k) % tab_width); - goto finish; - -backspace: - do { - rl_point = (i == 0 || rl_point-i > prompt_length) ? rl_point-1 : i-1; - } while (locale_is_utf8 && !isutf(rl_line_buffer[rl_point]) && rl_point > i-1); - -finish: - rl_delete_text(rl_point, j); - return 0; -} - -static int delete_callback(int count, int key) -{ - reset_indent(); - int j = rl_point; - do { - j += (rl_line_buffer[j] == '\n') ? prompt_length+1 : 1; - } while (locale_is_utf8 && !isutf(rl_line_buffer[j])); - if (rl_end < j) j = rl_end; - rl_delete_text(rl_point, j); - return 0; -} - -static int left_callback(int count, int key) -{ - reset_indent(); - if (rl_point > 0) { - int i = line_start(rl_point); - do { - rl_point = (i == 0 || rl_point-i > prompt_length) ? rl_point-1 : i-1; - } while (locale_is_utf8 && !isutf(rl_line_buffer[rl_point]) && rl_point > i-1); - } - return 0; -} - -static int right_callback(int count, int key) -{ - reset_indent(); - do { - rl_point += (rl_line_buffer[rl_point] == '\n') ? prompt_length+1 : 1; - } while (locale_is_utf8 && !isutf(rl_line_buffer[rl_point])); - if (rl_end < rl_point) rl_point = rl_end; - return 0; -} - -static int up_callback(int count, int key) -{ - reset_indent(); - int i = line_start(rl_point); - if (i > 0) { - int j = line_start(i-1); - if (j == 0) rl_point -= prompt_length; - rl_point += j - i; - if (rl_point >= i) rl_point = i - 1; - } - else { - last_hist_offset = -1; - rl_get_previous_history(count, key); - rl_point = line_end(0); - } - return 0; -} - -static int down_callback(int count, int key) -{ - reset_indent(); - int j = line_end(rl_point); - if (j < rl_end) { - int i = line_start(rl_point); - if (i == 0) rl_point += prompt_length; - rl_point += j - i + 1; - int k = line_end(j+1); - if (rl_point > k) rl_point = k; - return 0; - } - else { - if (last_hist_offset >= 0) { - history_set_pos(last_hist_offset); - last_hist_offset = -1; - } - return rl_get_next_history(count, key); - } -} - -static int callback_en=0; - -void jl_input_line_callback(char *input) -{ - int end=0, doprint=1; - if (!input) { - end = 1; - rl_ast = NULL; - } - else if (!rl_ast) { - // In vi mode, it's possible for this function to be called w/o a - // previous call to return_callback. - rl_ast = repl_parse_input_line(rl_line_buffer); - } - - if (rl_ast != NULL) { - doprint = !ends_with_semicolon(input); - add_history_permanent(input); - jl_putc('\n', jl_uv_stdout); - free(input); - } - - callback_en = 0; - rl_callback_handler_remove(); - handle_input(rl_ast, end, doprint); - rl_ast = NULL; -} - -static int common_prefix(const char *s1, const char *s2) -{ - int i = 0; - while (s1[i] && s2[i] && s1[i] == s2[i]) - i++; - return i; -} - -static char *lang_keywords[] = - { "if", "else", "elseif", "while", "for", "begin", "end", "quote", - "try", "catch", "return", "local", "abstract", "function", "macro", "ccall", - "finally", "typealias", "break", "continue", "type", "global", - "module", "using", "import", "export", "const", "let", "bitstype", "do", - "baremodule", "importall", "immutable", NULL }; - -static int is_keyword(char *s) -{ - for(size_t i=0; lang_keywords[i]; i++) { - if (!strcmp(lang_keywords[i],s)) - return 1; - } - return 0; -} - -static int name_visible(char *name, const char *prefix) -{ - return !strchr(name,'#'); -} - -static void symtab_search(jl_sym_t *tree, int *pcount, ios_t *result, - jl_module_t *module, const char *str, - const char *prefix, int plen) -{ - do { - if (common_prefix(prefix, tree->name) == plen && - name_visible(tree->name, prefix) && - (module ? jl_defines_or_exports_p(module, tree) : (jl_boundp(jl_current_module, tree) || - is_keyword(tree->name)))) { - ios_puts(str, result); - ios_puts(tree->name + plen, result); - ios_putc('\n', result); - (*pcount)++; - } - if (tree->left) - symtab_search(tree->left, pcount, result, module, str, prefix, plen); - tree = tree->right; - } while (tree != NULL); -} - -static jl_module_t *find_submodule_named(jl_module_t *module, const char *name) -{ - jl_sym_t *s = jl_symbol_lookup(name); - if (!s) return NULL; - - jl_binding_t *b = jl_get_binding(module, s); - if (!b) return NULL; - - return (jl_is_module(b->value)) ? (jl_module_t *)b->value : NULL; -} - -static int symtab_get_matches(jl_sym_t *tree, const char *str, char **answer) -{ - int x, plen, count=0; - ios_t ans; - - // given str "X.Y.a", set module := X.Y and name := "a" - jl_module_t *module = NULL; - char *name = NULL, *strcopy = strdup(str); - for (char *s=strcopy, *r=NULL;; s=NULL) { - char *t = strtok_r(s, ".", &r); - if (!t) { - if (str[strlen(str)-1] == '.') { - // this case is "Module." - if (name) { - if (!module) module = jl_current_module; - module = find_submodule_named(module, name); - if (!module) goto symtab_get_matches_exit; - } - name = ""; - } - break; - } - if (name) { - if (!module) module = jl_current_module; - module = find_submodule_named(module, name); - if (!module) goto symtab_get_matches_exit; - } - name = t; - } - - if (!name) goto symtab_get_matches_exit; - plen = strlen(name); - - while (tree != NULL) { - x = common_prefix(name, tree->name); - if (x == plen) { - ios_mem(&ans, 0); - symtab_search(tree, &count, &ans, module, str, name, plen); - size_t nb; - *answer = ios_takebuf(&ans, &nb); - break; - } - else { - x = strcmp(name, tree->name); - if (x < 0) - tree = tree->left; - else - tree = tree->right; - } - } - -symtab_get_matches_exit: - free(strcopy); - return count; -} - -int tab_complete(const char *line, char **answer, int *plen) -{ - int len = *plen; - - while (len>=0) { - if (!jl_word_char(line[len])) { - break; - } - len--; - } - len++; - *plen = len; - - return symtab_get_matches(jl_get_root_symbol(), &line[len], answer); -} - -static char *do_completions(const char *ch, int c) -{ - static char *completions = NULL; - char *ptr; - int len, cnt; - - if (c == 0) { - // first time - if (completions) - free(completions); - completions = NULL; - len = strlen(ch); - if (len > 0) - len--; - cnt = tab_complete(ch, &completions, &len); - if (cnt == 0) - return NULL; - ptr = strtok_r(completions, "\n", &strtok_saveptr); - } - else { - ptr = strtok_r(NULL, "\n", &strtok_saveptr); - } - - return ptr ? strdup(ptr) : NULL; -} - -#ifndef __WIN32__ -void sigtstp_handler(int arg) -{ - rl_cleanup_after_signal(); - - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGTSTP); - sigprocmask(SIG_UNBLOCK, &mask, NULL); - signal(SIGTSTP, SIG_DFL); - raise(SIGTSTP); - - signal(SIGTSTP, sigtstp_handler); -} - -void sigcont_handler(int arg) -{ - rl_reset_after_signal(); - if (callback_en) - rl_forced_update_display(); -} -#endif - -static void init_rl(void) -{ - rl_readline_name = "julia"; - rl_completion_entry_function = do_completions; -#if !defined(_WIN32) - rl_sort_completion_matches = 0; -#endif - for(size_t i=0; lang_keywords[i]; i++) { - // make sure keywords are in symbol table - (void)jl_symbol(lang_keywords[i]); - } - rl_completer_word_break_characters = " \t\n\"\\'`$><=;|&{}()[],+-*/?%^~!:"; - Keymap keymaps[] = {emacs_standard_keymap, vi_insertion_keymap}; - int i; - for (i = 0; i < sizeof(keymaps)/sizeof(keymaps[0]); i++) { - rl_bind_key_in_map(' ', space_callback, keymaps[i]); - rl_bind_key_in_map('\t', tab_callback, keymaps[i]); - rl_bind_key_in_map('\r', return_callback, keymaps[i]); - rl_bind_key_in_map('\n', newline_callback, keymaps[i]); - rl_bind_key_in_map('\v', line_kill_callback, keymaps[i]); - rl_bind_key_in_map('\b', backspace_callback, keymaps[i]); - rl_bind_key_in_map('\001', line_start_callback, keymaps[i]); - rl_bind_key_in_map('\005', line_end_callback, keymaps[i]); - rl_bind_key_in_map('\002', left_callback, keymaps[i]); - rl_bind_key_in_map('\006', right_callback, keymaps[i]); - rl_bind_keyseq_in_map("\e[1~", line_start_callback, keymaps[i]); - rl_bind_keyseq_in_map("\e[4~", line_end_callback, keymaps[i]); - rl_bind_keyseq_in_map("\e[3~", delete_callback, keymaps[i]); - rl_bind_keyseq_in_map("\e[5~", rl_named_function("beginning-of-history"), keymaps[i]); - rl_bind_keyseq_in_map("\e[6~", rl_named_function("end-of-history"), keymaps[i]); - rl_bind_keyseq_in_map("\e[A", up_callback, keymaps[i]); - rl_bind_keyseq_in_map("\e[B", down_callback, keymaps[i]); - rl_bind_keyseq_in_map("\e[D", left_callback, keymaps[i]); - rl_bind_keyseq_in_map("\e[C", right_callback, keymaps[i]); - rl_bind_keyseq_in_map("\\C-d", delete_callback, keymaps[i]); - rl_bind_keyseq_in_map("\e\r", newline_callback, keymaps[i]); - } -#ifndef __WIN32__ - signal(SIGTSTP, sigtstp_handler); - signal(SIGCONT, sigcont_handler); -#endif -} - -void jl_prep_terminal(int meta_flag) -{ - FILE *rl_in = rl_instream; - rl_instream = stdin; - rl_prep_terminal(1); - rl_instream = rl_in; -#ifdef __WIN32__ - if (jl_uv_stdin->type == UV_TTY) uv_tty_set_mode((uv_tty_t*)jl_uv_stdin,1); //raw (and libuv-processed) -#endif -} -/* Restore the terminal's normal settings and modes. */ -void jl_deprep_terminal () -{ - FILE *rl_in = rl_instream; - rl_instream = stdin; - rl_deprep_terminal(); - rl_instream = rl_in; -#ifdef __WIN32__ - if (jl_uv_stdin->type == UV_TTY) uv_tty_set_mode((uv_tty_t*)jl_uv_stdin,0); // cooked -#endif -} - -void jl_init_repl(int history) -{ - disable_history = !history; - -#ifdef __WIN32__ - rl_outstream=(void*)jl_uv_stdout; -#endif - rl_catch_signals = 0; - rl_prep_term_function = &jl_prep_terminal; - rl_deprep_term_function = &jl_deprep_terminal; - rl_instream = fopen("/dev/null","r"); - prompt_length = 7; // == strlen("julia> ") - init_history(); - rl_startup_hook = (rl_hook_func_t*)init_rl; -} - -static char *prompt_string=NULL; - -void repl_callback_enable(char *prompt) -{ - callback_en = 1; - if (prompt_string == NULL || strcmp(prompt, prompt_string)) { - if (prompt_string) free(prompt_string); - prompt_string = strdup(prompt); - } - rl_callback_handler_install(prompt_string, jl_input_line_callback); -} - -#include "uv.h" - -void jl_read_buffer(unsigned char *base, ssize_t nread) -{ - unsigned char *start = base; - while(*start != 0 && nread > 0) { - rl_stuff_char(*start); - start++; - nread--; - } - rl_callback_read_char(); -} - -DLLEXPORT void jl_reset_input(void) -{ - //move the cursor to a clean line: - char *p = rl_line_buffer; - int i; - for (i = 0; *p != '\0'; p++, i++) { - if (i >= rl_point && *p == '\n') { - jl_putc('\n', jl_uv_stdout); - } - } - rl_ast = NULL; - //reset state: - rl_reset_line_state(); - reset_indent(); - callback_en = 0; - rl_callback_handler_remove(); -} From e20b648b1b77e575300030749b32a8367428f389 Mon Sep 17 00:00:00 2001 From: Mike Nolta Date: Sun, 23 Mar 2014 20:35:10 -0400 Subject: [PATCH 04/11] fix repl crash when tab-completing type field --- base/REPLCompletions.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/REPLCompletions.jl b/base/REPLCompletions.jl index b368dc4c2d332..41c025e2a7dbd 100644 --- a/base/REPLCompletions.jl +++ b/base/REPLCompletions.jl @@ -91,7 +91,7 @@ module REPLCompletions fields = t.names for field in fields s = string(field) - if beginswith(s,name) && + if beginswith(s,name) push!(suggestions,s) end end @@ -184,4 +184,4 @@ module REPLCompletions return (ret,range) end end -end \ No newline at end of file +end From 320b519c516042befa0cec97c7769c593a9babb5 Mon Sep 17 00:00:00 2001 From: Mike Nolta Date: Mon, 24 Mar 2014 13:06:53 -0400 Subject: [PATCH 05/11] fix repl crash when pasting trailing newlines --- base/REPL.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/REPL.jl b/base/REPL.jl index d2dc73da5b10c..addd5d29c3723 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -515,7 +515,7 @@ module REPL pos = 0 while pos <= length(string) oldpos = pos - ast, pos = Base.parse(string,pos; ) + ast, pos = Base.parse(string, pos; raise=false) # Get the line and strip leading and trailing whitespace line = strip(string[max(oldpos,1):min(pos-1,length(string))]) Readline.replace_line(s,line) From 2c22b905ddc7265d49adb197d461866e2f9f15c6 Mon Sep 17 00:00:00 2001 From: Mike Nolta Date: Tue, 25 Mar 2014 22:10:02 -0400 Subject: [PATCH 06/11] add old-repl path completion behavior to new-repl Cf. #5343 --- base/REPLCompletions.jl | 90 +++++++++++++++++++++++++++++++++-------- base/Readline.jl | 19 +++++---- base/file.jl | 2 +- 3 files changed, 86 insertions(+), 25 deletions(-) diff --git a/base/REPLCompletions.jl b/base/REPLCompletions.jl index 41c025e2a7dbd..5d80c3203fed8 100644 --- a/base/REPLCompletions.jl +++ b/base/REPLCompletions.jl @@ -99,32 +99,90 @@ module REPLCompletions suggestions end + function complete_path(path::ByteString) + matches = ByteString[] + dir,prefix = splitdir(path) + if length(dir) == 0 + files = readdir() + elseif isdir(dir) + files = readdir(dir) + else + return matches + end + for file in files + if beginswith(file, prefix) + p = joinpath(dir, file) + push!(matches, isdir(p) ? joinpath(p,"") : p) + end + end + matches + end + const non_word_chars = " \t\n\"\\'`@\$><=:;|&{}()[].,+-*/?%^~" function completions(string,pos) - startpos = pos + startpos = min(pos,1) dotpos = 0 - while startpos >= 1 - c = string[startpos] - if c < 0x80 && in(char(c), non_word_chars) - if c != '.' - startpos = nextind(string,startpos) - break - elseif dotpos == 0 - dotpos = startpos + instring = false + incmd = false + escaped = false + nearquote = false + i = start(string) + while i <= pos + c,j = next(string, i) + if c == '\\' + if instring + escaped $= true + end + nearquote = false + elseif c == '\'' + if !instring + nearquote = true + end + elseif c == '"' + if !escaped && !nearquote && !incmd + instring $= true + end + escaped = nearquote = false + elseif c == '`' + if !escaped && !nearquote && !instring + incmd $= true end + escaped = nearquote = false + else + escaped = nearquote = false end - if startpos == 1 - break + if c < 0x80 + if instring || incmd + if in(c, " \t\n\"\\'`@\$><=;|&{(") + startpos = j + end + elseif in(c, non_word_chars) + if c == '.' + dotpos = i + else + startpos = j + end + end end - startpos = prevind(string,startpos) + i = j end + + if instring || incmd + r = startpos:pos + paths = complete_path(string[r]) + if instring && length(paths) == 1 + paths[1] *= "\"" + end + return sort(paths), r + end + ffunc = (mod,x)->true suggestions = UTF8String[] r = rsearch(string,"using",startpos) if !isempty(r) && all(isspace,string[nextind(string,last(r)):prevind(string,startpos)]) # We're right after using. Let's look only for packages - # and modules we can reach form here + # and modules we can reach from here # If there's no dot, we're in toplevel, so we should # also search for packages @@ -166,7 +224,7 @@ module REPLCompletions files = readdir(dir) end # Filter out files and directories that do not begin with the partial name we were - # completiong and append "/" to directories to simplify further completion + # completing and append "/" to directories to simplify further completion ret = map(filter(x->beginswith(x,name),files)) do x if !isdir(joinpath(dir,x)) return x @@ -175,8 +233,8 @@ module REPLCompletions end end r = (nextind(string,pos-sizeof(name))):pos - return (ret,r,string[r]) - elseif isexpr(arg,:escape) && (isexpr(arg.args[1],VERSION >= v"0.3-" ? :incomplete : :continue) || isexpr(arg.args[1],:error)) + return (ret,r) + elseif isexpr(arg,:escape) && (isexpr(arg.args[1],:incomplete) || isexpr(arg.args[1],:error)) r = first(last_parse):prevind(last_parse,last(last_parse)) partial = scs[r] ret, range = completions(partial,endof(partial)) diff --git a/base/Readline.jl b/base/Readline.jl index 92bf62546bc72..594a7bfc2f3e3 100644 --- a/base/Readline.jl +++ b/base/Readline.jl @@ -925,14 +925,17 @@ module Readline # the we could be in the middle of a multi-byte # sequence, here but that's ok, since any # whitespace we're interested in is only one byte - if position(buf) != 0 - c = buf.data[position(buf)] - else - c = '\n' - end - if c == ' ' || c == '\n' || c == '\t' - edit_insert(s," "^4) - return + i = position(buf) + if i != 0 + c = buf.data[i] + if c == '\n' || c == '\t' || + # hack to allow path completion in cmds + # after a space, e.g., `cd `, while still + # allowing multiple indent levels + (c == ' ' && i > 3 && buf.data[i-1] == ' ') + edit_insert(s," "^4) + return + end end Readline.completeLine(s) Readline.refresh_line(s) diff --git a/base/file.jl b/base/file.jl index 786faa94d4282..4cee4e894f24f 100644 --- a/base/file.jl +++ b/base/file.jl @@ -146,7 +146,7 @@ function readdir(path::String) # The list of dir entries is returned as a contiguous sequence of null-terminated # strings, the first of which is pointed to by ptr in uv_readdir_req. # The following lines extracts those strings into dirent - entries = String[] + entries = ByteString[] offset = 0 for i = 1:file_count From 048722256e993113e8c3d7cfee0c4d9219c4cea4 Mon Sep 17 00:00:00 2001 From: Mike Nolta Date: Wed, 26 Mar 2014 00:05:07 -0400 Subject: [PATCH 07/11] fix buffer size bug in Readline.edit_replace A symptom of this is if ext(c) is entered into a fresh session, the output is extrema (generic function with 2 methods) instead of the expected ERROR: c not defined --- base/Readline.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/Readline.jl b/base/Readline.jl index 594a7bfc2f3e3..7f4b36dde3b43 100644 --- a/base/Readline.jl +++ b/base/Readline.jl @@ -389,7 +389,7 @@ module Readline function edit_replace(s,from,to,str) room = length(str.data)-(to-from) - ensureroom(s.input_buffer,s.input_buffer.size-to+room) + ensureroom(s.input_buffer, s.input_buffer.size + room) ccall(:memmove, Void, (Ptr{Void},Ptr{Void},Int), pointer(s.input_buffer.data,to+room+1),pointer(s.input_buffer.data,to+1),s.input_buffer.size-to) s.input_buffer.size += room seek(s.input_buffer,from) From f9d3a8bd9772f5b00cd813f5de6ce8d3fbc02b83 Mon Sep 17 00:00:00 2001 From: Mike Nolta Date: Wed, 26 Mar 2014 13:15:21 -0400 Subject: [PATCH 08/11] repl: fix color detection on windows --- base/Terminals.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/Terminals.jl b/base/Terminals.jl index b6a6f2daa3462..fb9f8d0bf60f2 100644 --- a/base/Terminals.jl +++ b/base/Terminals.jl @@ -204,7 +204,8 @@ module Terminals stop_reading(t::UnixTerminal) = stop_reading(t.in_stream) - hascolor(t::UnixTerminal) = (beginswith(t.term_type,"xterm") || success(`tput setaf 0`)) + @unix_only hascolor(t::UnixTerminal) = (beginswith(t.term_type,"xterm") || success(`tput setaf 0`)) + @windows_only hascolor(t::UnixTerminal) = true #writemime(t::UnixTerminal, ::MIME"text/plain", x) = writemime(t.out_stream, MIME("text/plain"), x) end importall .Unix From 889a10b675e92895ea65a832111a9352dc2eabb6 Mon Sep 17 00:00:00 2001 From: Mike Nolta Date: Wed, 26 Mar 2014 13:41:30 -0400 Subject: [PATCH 09/11] repl: bind Delete key --- base/Readline.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/Readline.jl b/base/Readline.jl index 7f4b36dde3b43..bc8c053899bb5 100644 --- a/base/Readline.jl +++ b/base/Readline.jl @@ -1001,6 +1001,8 @@ module Readline "\e[A" => edit_move_up, # Down Arrow "\e[B" => edit_move_down, + # Delete + "\e[3~" => edit_delete, # Bracketed Paste Mode "\e[200~" => s->begin ps = state(s,mode(s)) From fd19a973a8c905721d413e3ca4baf0993725c6cc Mon Sep 17 00:00:00 2001 From: Mike Nolta Date: Wed, 26 Mar 2014 17:52:44 -0400 Subject: [PATCH 10/11] repl: don't ignore --no-history option Also, hoist all the using/imports to the top. --- base/REPL.jl | 90 +++++++++++++++++++++++++++++--------------------- base/client.jl | 4 ++- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/base/REPL.jl b/base/REPL.jl index addd5d29c3723..a10bf36d3fa27 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -1,13 +1,28 @@ module REPL - # compatibility for julia version 0.2 - if !isdefined(:put!) - const put! = put - const take! = take - end + using Base.Meta + using Base.Terminals + using Base.Readline + using Base.REPLCompletions export StreamREPL, BasicREPL + import Base: AsyncStream, + Display, + display, + writemime + + import Base.Terminals: raw! + import Base.Readline: CompletionProvider, + HistoryProvider, + add_history, + char_move_left, + char_move_word_left, + completeLine, + history_prev, + history_next, + history_search + abstract AbstractREPL type REPLBackend @@ -16,8 +31,6 @@ module REPL ans end - using Base.Meta - function eval_user_input(ast::ANY, backend) iserr, lasterr, bt = false, (), nothing while true @@ -84,8 +97,6 @@ module REPL end end - import Base: Display, display, writemime - immutable REPLDisplay <: Display repl::AbstractREPL end @@ -124,12 +135,6 @@ module REPL end end end - - using Base.Terminals - using Base.Readline - - import Base.Readline: char_move_left, char_move_word_left, CompletionProvider, completeLine - type ReadlineREPL <: AbstractREPL t::TextTerminal prompt_color::String @@ -137,13 +142,19 @@ module REPL answer_color::String shell_color::String help_color::String + use_history_file::Bool in_shell::Bool in_help::Bool - consecutive_returns + consecutive_returns::Int end outstream(r::ReadlineREPL) = r.t - ReadlineREPL(t::TextTerminal) = ReadlineREPL(t,julia_green,Base.input_color(),Base.answer_color(),Base.text_colors[:red],Base.text_colors[:yellow],false,false,0) + ReadlineREPL(t::TextTerminal) = ReadlineREPL(t, julia_green, + Base.input_color(), + Base.answer_color(), + Base.text_colors[:red], + Base.text_colors[:yellow], + true, false, false, 0) type REPLCompletionProvider <: CompletionProvider r::ReadlineREPL @@ -153,8 +164,6 @@ module REPL r::ReadlineREPL end - using Base.REPLCompletions - function completeLine(c::REPLCompletionProvider,s) partial = bytestring(s.input_buffer.data[1:position(s.input_buffer)]) ret, range = completions(partial,endof(partial)) @@ -168,8 +177,6 @@ module REPL return (ret, partial[range]) end - import Base.Readline: HistoryProvider, add_history, history_prev, history_next, history_search - type REPLHistoryProvider <: HistoryProvider history::Array{String,1} history_file @@ -179,13 +186,16 @@ module REPL mode_mapping modes::Array{Uint8,1} end + REPLHistoryProvider(mode_mapping) = + REPLHistoryProvider(String[], nothing, 0, IOBuffer(), + nothing, mode_mapping, Uint8[]) - function hist_from_file(file,mode_mapping) - hp = REPLHistoryProvider(String[],file,0,IOBuffer(),nothing,mode_mapping,Uint8[]) + function hist_from_file(hp, file) + hp.history_file = file seek(file,0) while !eof(file) b = readuntil(file,'\0') - if uint8(b[1]) in keys(mode_mapping) + if uint8(b[1]) in keys(hp.mode_mapping) push!(hp.modes,uint8(b[1])) push!(hp.history,b[2:(end-1)]) # Strip trailing \0 else # For history backward compatibility @@ -220,10 +230,12 @@ module REPL push!(hist.history,str) c = mode_idx(hist,Readline.mode(s)) push!(hist.modes,c) - write(hist.history_file,c) - write(hist.history_file,str) - write(hist.history_file,'\0') - flush(hist.history_file) + if hist.history_file !== nothing + write(hist.history_file,c) + write(hist.history_file,str) + write(hist.history_file,'\0') + flush(hist.history_file) + end end function history_adjust(hist::REPLHistoryProvider,s) @@ -382,8 +394,6 @@ module REPL end end - import Base.Terminals: raw! - function reset(d::REPLDisplay) raw!(d.repl.t,false) print(Base.text_colors[:normal]) @@ -422,7 +432,6 @@ module REPL # This will provide completions for REPL and help mode replc = REPLCompletionProvider(repl) - finalizer(replc,(replc)->close(f)) # Set up the main Julia prompt main_prompt = Prompt("julia> "; @@ -463,8 +472,15 @@ module REPL # Setup history # We will have a unified history for all REPL modes - f = open(find_hist_file(),true,true,true,false,false) - hp = hist_from_file(f,(Uint8=>Any)[uint8('\0') => main_prompt, uint8(';') => shell_mode, uint8('?') => help_mode, uint8('>') => main_prompt]) + hp = REPLHistoryProvider((Uint8=>Any)[uint8('\0') => main_prompt, + uint8(';') => shell_mode, + uint8('?') => help_mode, + uint8('>') => main_prompt]) + if repl.use_history_file + f = open(find_hist_file(),true,true,true,false,false) + finalizer(replc,(replc)->close(f)) + hist_from_file(hp, f) + end history_reset_state(hp) main_prompt.hist = hp shell_mode.hist = hp @@ -477,7 +493,6 @@ module REPL extra_repl_keymap = [extra_repl_keymap] end - const repl_keymap = { ';' => function (s) if isempty(s) || position(Readline.buffer(s)) == 0 @@ -564,12 +579,13 @@ module REPL banner(io,t) = Base.banner(io) end - function run_repl(t::TextTerminal) + function run_repl(repl::ReadlineREPL) repl_channel = RemoteRef() response_channel = RemoteRef() start_repl_backend(repl_channel, response_channel) - run_frontend(ReadlineREPL(t),repl_channel,response_channel) + run_frontend(repl, repl_channel, response_channel) end + run_repl(t::TextTerminal) = run_repl(ReadlineREPL(t)) type BasicREPL <: AbstractREPL end @@ -583,8 +599,6 @@ module REPL answer_color::String end - import Base.AsyncStream - outstream(s::StreamREPL) = s.stream StreamREPL(stream::AsyncStream) = StreamREPL(stream,julia_green,Base.text_colors[:white],Base.answer_color()) diff --git a/base/client.jl b/base/client.jl index fa79b07a6fff5..3a897e4e8d5bc 100644 --- a/base/client.jl +++ b/base/client.jl @@ -359,7 +359,9 @@ function _start() end quiet || REPL.banner(term,term) ccall(:jl_install_sigint_handler, Void, ()) - REPL.run_repl(term) + repl = REPL.ReadlineREPL(term) + repl.use_history_file = history + REPL.run_repl(repl) end catch err display_error(err,catch_backtrace()) From 0ff01b577356c9637ad14674891c7b89683dd919 Mon Sep 17 00:00:00 2001 From: Mike Nolta Date: Thu, 27 Mar 2014 15:42:20 -0400 Subject: [PATCH 11/11] cleanup base/Terminals.jl - fix cmove_* fallbacks - remove unused Colors & Attributes code - get rid of submodules - deindent --- base/REPL.jl | 4 +- base/Readline.jl | 6 +- base/Terminals.jl | 316 ++++++++++++++++++---------------------------- 3 files changed, 131 insertions(+), 195 deletions(-) diff --git a/base/REPL.jl b/base/REPL.jl index a10bf36d3fa27..c6368d6800079 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -540,9 +540,9 @@ module REPL # This is slightly ugly but ok for now terminal = Readline.terminal(s) stop_reading(terminal) - raw!(terminal,false) && Terminals.Unix.disable_bracketed_paste(terminal) + raw!(terminal,false) && disable_bracketed_paste(terminal) Readline.mode(s).on_done(s,Readline.buffer(s),true) - raw!(terminal,true) && Terminals.Unix.enable_bracketed_paste(terminal) + raw!(terminal,true) && enable_bracketed_paste(terminal) start_reading(terminal) else break diff --git a/base/Readline.jl b/base/Readline.jl index bc8c053899bb5..7fe769fa986a6 100644 --- a/base/Readline.jl +++ b/base/Readline.jl @@ -1,7 +1,7 @@ module Readline using Base.Terminals - import Base.Terminals: raw!, width, height, cmove, Rect, Size, getX, + import Base.Terminals: raw!, width, height, cmove, getX, getY, clear_line, beep import Base: ensureroom, peek @@ -1119,7 +1119,7 @@ module Readline function prompt!(terminal,prompt,s=init_state(terminal,prompt)) raw!(terminal,true) - isa(terminal,UnixTerminal) && Terminals.Unix.enable_bracketed_paste(terminal) + enable_bracketed_paste(terminal) try start_reading(terminal) activate(prompt,s) @@ -1136,7 +1136,7 @@ module Readline end end finally - raw!(terminal,false) && Terminals.Unix.disable_bracketed_paste(terminal) + raw!(terminal,false) && disable_bracketed_paste(terminal) end end end diff --git a/base/Terminals.jl b/base/Terminals.jl index fb9f8d0bf60f2..b7af55b00d9af 100644 --- a/base/Terminals.jl +++ b/base/Terminals.jl @@ -1,213 +1,149 @@ module Terminals - import Base.size, Base.write, Base.flush - abstract TextTerminal <: Base.IO - export TextTerminal, writepos, cmove, pos, getX, getY, hascolor - - # Stuff that really should be in a Geometry package - immutable Rect - top - left - width - height - end - immutable Size - width - height - end +export + TextTerminal, + UnixTerminal, + cmove, + cmove_col, + cmove_down, + cmove_left, + cmove_line_down, + cmove_line_up, + cmove_right, + cmove_up, + disable_bracketed_paste, + enable_bracketed_paste, + getX, + getY, + hascolor, + pos, + writepos + +import Base: flush, + read, + readuntil, + size, + start_reading, + stop_reading, + write, + writemime + +immutable Size + width + height +end + +## TextTerminal ## - # INTERFACE - size(::TextTerminal) = error("Unimplemented") - writepos(t::TextTerminal,x,y,s::Array{Uint8,1}) = error("Unimplemented") - cmove(t::TextTerminal,x,y) = error("Unimplemented") - getX(t::TextTerminal) = error("Unimplemented") - getY(t::TextTerminal) = error("Unimplemented") - pos(t::TextTerminal) = (getX(t),getY(t)) +abstract TextTerminal <: Base.IO - # Relative moves (Absolute position fallbacks) - export cmove_up, cmove_down, cmove_left, cmove_right, cmove_line_up, cmove_line_down, cmove_col +# INTERFACE +size(::TextTerminal) = error("Unimplemented") +writepos(t::TextTerminal,x,y,s::Array{Uint8,1}) = error("Unimplemented") +cmove(t::TextTerminal,x,y) = error("Unimplemented") +getX(t::TextTerminal) = error("Unimplemented") +getY(t::TextTerminal) = error("Unimplemented") +pos(t::TextTerminal) = (getX(t),getY(t)) - cmove_up(t::TextTerminal,n) = cmove(getX(),max(1,getY()-n)) - cmove_up(t) = cmove_up(t,1) +# Relative moves (Absolute position fallbacks) +cmove_up(t::TextTerminal,n) = cmove(getX(t),max(1,getY(t)-n)) +cmove_up(t) = cmove_up(t,1) - cmove_down(t::TextTerminal,n) = cmove(getX(),max(height(t),getY()+n)) - cmove_down(t) = cmove_down(t,1) +cmove_down(t::TextTerminal,n) = cmove(getX(t),max(height(t),getY(t)+n)) +cmove_down(t) = cmove_down(t,1) - cmove_left(t::TextTerminal,n) = cmove(max(1,getX()-n),getY()) - cmove_left(t) = cmove_left(t,1) +cmove_left(t::TextTerminal,n) = cmove(max(1,getX(t)-n),getY(t)) +cmove_left(t) = cmove_left(t,1) - cmove_right(t::TextTerminal,n) = cmove(max(width(t),getX()+n),getY()) - cmove_right(t) = cmove_right(t,1) +cmove_right(t::TextTerminal,n) = cmove(max(width(t),getX(t)+n),getY(t)) +cmove_right(t) = cmove_right(t,1) - cmove_line_up(t::TextTerminal,n) = cmove(1,max(1,getY()-n)) - cmove_line_up(t) = cmove_line_up(t,1) +cmove_line_up(t::TextTerminal,n) = cmove(1,max(1,getY(t)-n)) +cmove_line_up(t) = cmove_line_up(t,1) - cmove_line_down(t::TextTerminal,n) = cmove(1,max(height(t),getY()+n)) - cmove_line_down(t) = cmove_line_down(t,1) +cmove_line_down(t::TextTerminal,n) = cmove(1,max(height(t),getY(t)+n)) +cmove_line_down(t) = cmove_line_down(t,1) - cmove_col(t::TextTerminal,c) = comve(c,getY()) +cmove_col(t::TextTerminal,c) = cmove(c,getY(t)) - # Defaults - hascolor(::TextTerminal) = false +# Defaults +hascolor(::TextTerminal) = false - # Utility Functions - function writepos{T}(t::TextTerminal, x, y, b::Array{T}) - if isbits(T) - writepos(t,x,y,reinterpret(Uint8,b)) - else - cmove(t,x,y) - invoke(write, (IO, Array), s, a) - end - end - function writepos(t::TextTerminal,x,y,args...) +# Utility Functions +function writepos{T}(t::TextTerminal, x, y, b::Array{T}) + if isbits(T) + writepos(t,x,y,reinterpret(Uint8,b)) + else cmove(t,x,y) - write(t,args...) - end - width(t::TextTerminal) = size(t).width - height(t::TextTerminal) = size(t).height - - # For terminals with buffers - flush(t::TextTerminal) = nothing - - clear(t::TextTerminal) = error("Unimplemented") - clear_line(t::TextTerminal,row) = error("Unimplemented") - clear_line(t::TextTerminal) = error("Unimplemented") - - raw!(t::TextTerminal,raw::Bool) = error("Unimplemented") - - beep(t::TextTerminal) = nothing - - abstract TextAttribute - - module Attributes - # This is just to get started and will have to be revised - - import ..Terminals: TextAttribute, TextTerminal - - export Standout, Underline, Reverse, Blink, Dim, Bold, AltCharset, Invisible, Protect, Left, Right, Top, - Vertical, Horizontal, Low - - macro flag_attribute(name) - quote - immutable $name <: TextAttribute - end - end - end - - @flag_attribute Standout - @flag_attribute Underline - @flag_attribute Reverse - @flag_attribute Blink - @flag_attribute Dim - @flag_attribute Bold - @flag_attribute AltCharset - @flag_attribute Invisible - @flag_attribute Protect - @flag_attribute Left - @flag_attribute Right - @flag_attribute Top - @flag_attribute Vertical - @flag_attribute Horizontal - @flag_attribute Low - - attr_simplify(::TextTerminal, x::TextAttribute) = x - attr_simplify{T<:TextAttribute}(::TextTerminal, ::Type{T}) = T() - function attr_simplify(::TextTerminal, s::Symbol) - if s == :standout - return Standout() - elseif s == :underline - return Underline() - elseif s == :reverse - return Reverse() - elseif s == :blink - return Blink() - end - end + invoke(write, (IO, Array), s, a) + end +end +function writepos(t::TextTerminal,x,y,args...) + cmove(t,x,y) + write(t,args...) +end +width(t::TextTerminal) = size(t).width +height(t::TextTerminal) = size(t).height +# For terminals with buffers +flush(t::TextTerminal) = nothing - end +clear(t::TextTerminal) = error("Unimplemented") +clear_line(t::TextTerminal,row) = error("Unimplemented") +clear_line(t::TextTerminal) = error("Unimplemented") - module Colors - import ..Terminals: TextAttribute, TextTerminal +raw!(t::TextTerminal,raw::Bool) = error("Unimplemented") - export TerminalColor, TextColor, BackgroundColor, ForegroundColor, - lookup_color, terminal_color, maxcolors, maxcolorpairs, palette, numcolors +beep(t::TextTerminal) = nothing +enable_bracketed_paste(t::TextTerminal) = nothing +disable_bracketed_paste(t::TextTerminal) = nothing - # Represents a color actually displayable by the current terminal - abstract TerminalColor +## UnixTerminal ## - immutable TextColor <: TextAttribute - c::TerminalColor - end - immutable BackgroundColor <: TextAttribute - c::TerminalColor - end +type UnixTerminal <: TextTerminal + term_type::ASCIIString + in_stream::Base.TTY + out_stream::Base.TTY + err_stream::Base.TTY +end - # Terminals should implement this - lookup_color(t::TextTerminal) = error("Unimplemented") - maxcolors(t::TextTerminal) = error("Unimplemented") - maxcolorpairs(t::TextTerminal) = error("Unimplemented") - palette(t::TextTerminal) = error("Unimplemented") - numcolors(t::TextTerminal) = error("Unimplemented") - end +const CSI = "\x1b[" - module Unix - importall ..Terminals - - import ..Terminals: width, height, cmove, Rect, Size, getX, - getY, raw!, clear, clear_line, beep, hascolor - import Base: size, read, write, flush, TTY, writemime, readuntil, start_reading, stop_reading - - export UnixTerminal - - type UnixTerminal <: TextTerminal - term_type - in_stream::TTY - out_stream::TTY - err_stream::TTY - end - - const CSI = "\x1b[" - - cmove_up(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)A") - cmove_down(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)B") - cmove_right(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)C") - cmove_left(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)D") - cmove_line_up(t::UnixTerminal,n) = (cmove_up(t,n);cmove_col(t,0)) - cmove_line_down(t::UnixTerminal,n) = (cmove_down(t,n);cmove_col(t,0)) - cmove_col(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)G") - - raw!(t::UnixTerminal,raw::Bool) = ccall(:uv_tty_set_mode,Int32,(Ptr{Void},Int32),t.in_stream.handle,raw?1:0)!=-1 - enable_bracketed_paste(t::UnixTerminal) = write(t.out_stream,"$(CSI)?2004h") - disable_bracketed_paste(t::UnixTerminal) = write(t.out_stream,"$(CSI)?2004l") - - function size(t::UnixTerminal) - s = Array(Int32,2) - Base.uv_error("size (TTY)",ccall(:uv_tty_get_winsize,Int32,(Ptr{Void},Ptr{Int32},Ptr{Int32}),t.out_stream.handle,pointer(s,1),pointer(s,2))!=0) - Size(s[1],s[2]) - end - - clear(t::UnixTerminal) = write(t.out_stream,"\x1b[H\x1b[2J") - clear_line(t::UnixTerminal) = write(t.out_stream,"\x1b[0G\x1b[0K") - beep(t::UnixTerminal) = write(t.err_stream,"\x7") - - write{T,N}(t::UnixTerminal,a::Array{T,N}) = write(t.out_stream,a) - write(t::UnixTerminal,p::Ptr{Uint8}) = write(t.out_stream,p) - write(t::UnixTerminal,p::Ptr{Uint8},x::Integer) = write(t.out_stream,p,x) - write(t::UnixTerminal,x::Uint8) = write(t.out_stream,x) - read{T,N}(t::UnixTerminal,x::Array{T,N}) = read(t.in_stream,x) - readuntil(t::UnixTerminal,s::String) = readuntil(t.in_stream,s) - readuntil(t::UnixTerminal,c::Char) = readuntil(t.in_stream,c) - readuntil(t::UnixTerminal,s) = readuntil(t.in_stream,s) - read(t::UnixTerminal,::Type{Uint8}) = read(t.in_stream,Uint8) - start_reading(t::UnixTerminal) = start_reading(t.in_stream) - stop_reading(t::UnixTerminal) = stop_reading(t.in_stream) - - - @unix_only hascolor(t::UnixTerminal) = (beginswith(t.term_type,"xterm") || success(`tput setaf 0`)) - @windows_only hascolor(t::UnixTerminal) = true - #writemime(t::UnixTerminal, ::MIME"text/plain", x) = writemime(t.out_stream, MIME("text/plain"), x) - end - importall .Unix - export UnixTerminal +cmove_up(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)A") +cmove_down(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)B") +cmove_right(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)C") +cmove_left(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)D") +cmove_line_up(t::UnixTerminal,n) = (cmove_up(t,n);cmove_col(t,0)) +cmove_line_down(t::UnixTerminal,n) = (cmove_down(t,n);cmove_col(t,0)) +cmove_col(t::UnixTerminal,n) = write(t.out_stream,"$(CSI)$(n)G") + +raw!(t::UnixTerminal,raw::Bool) = ccall(:uv_tty_set_mode,Int32,(Ptr{Void},Int32),t.in_stream.handle,raw?1:0)!=-1 +enable_bracketed_paste(t::UnixTerminal) = write(t.out_stream,"$(CSI)?2004h") +disable_bracketed_paste(t::UnixTerminal) = write(t.out_stream,"$(CSI)?2004l") + +function size(t::UnixTerminal) + s = Array(Int32,2) + Base.uv_error("size (TTY)",ccall(:uv_tty_get_winsize,Int32,(Ptr{Void},Ptr{Int32},Ptr{Int32}),t.out_stream.handle,pointer(s,1),pointer(s,2))!=0) + Size(s[1],s[2]) end + +clear(t::UnixTerminal) = write(t.out_stream,"\x1b[H\x1b[2J") +clear_line(t::UnixTerminal) = write(t.out_stream,"\x1b[0G\x1b[0K") +beep(t::UnixTerminal) = write(t.err_stream,"\x7") + +write{T,N}(t::UnixTerminal,a::Array{T,N}) = write(t.out_stream,a) +write(t::UnixTerminal,p::Ptr{Uint8}) = write(t.out_stream,p) +write(t::UnixTerminal,p::Ptr{Uint8},x::Integer) = write(t.out_stream,p,x) +write(t::UnixTerminal,x::Uint8) = write(t.out_stream,x) +read{T,N}(t::UnixTerminal, x::Array{T,N}) = read(t.in_stream,x) +readuntil(t::UnixTerminal,s::String) = readuntil(t.in_stream,s) +readuntil(t::UnixTerminal,c::Char) = readuntil(t.in_stream,c) +readuntil(t::UnixTerminal,s) = readuntil(t.in_stream, s) +read(t::UnixTerminal,::Type{Uint8}) = read(t.in_stream,Uint8) +start_reading(t::UnixTerminal) = start_reading(t.in_stream) +stop_reading(t::UnixTerminal) = stop_reading(t.in_stream) + +@unix_only hascolor(t::UnixTerminal) = (beginswith(t.term_type,"xterm") || success(`tput setaf 0`)) +@windows_only hascolor(t::UnixTerminal) = true + +end # module