Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

incremental document handling #54

Closed
wants to merge 13 commits into from
10 changes: 8 additions & 2 deletions scripts/languageserver/languageserver.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
type Document
data::Vector{UInt8}
blocks::Vector{Any}
end

type LanguageServer
pipe_in
pipe_out

documents::Dict{String,Vector{UInt8}}
rootPath::String
documents::Dict{String,Document}
DocStore::Dict{String,Any}

function LanguageServer(pipe_in,pipe_out)
new(pipe_in,pipe_out,Dict{String,Array{Vector{UInt8},1}}(),Dict{String,Any}())
new(pipe_in,pipe_out,"",Dict{String,Document}(),Dict{String,Any}())
end
end

Expand Down
1 change: 1 addition & 0 deletions scripts/languageserver/main.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ include("provider_hover.jl")
include("provider_completions.jl")
include("provider_definitions.jl")
include("provider_signatures.jl")
include("parse.jl")
include("transport.jl")

include("utilities.jl")
Expand Down
243 changes: 243 additions & 0 deletions scripts/languageserver/parse.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
type VarInfo
t
doc::String
end

type Block
uptodate::Bool
ex::Any
range::Range
name::String
var::VarInfo
localvar::Dict{String,VarInfo}
diags::Vector{Diagnostic}
end

function Block(utd, ex, r::Range)
t, name, doc, lvars = classify_expr(ex)
ctx = LintContext()
ctx.lineabs = r.start.line+1
dl = r.end.line-r.start.line-ctx.line
Lint.lintexpr(ex, ctx)
diags = map(ctx.messages) do l
return Diagnostic(Range(Position(r.start.line+l.line+dl-1, 0), Position(r.start.line+l.line+dl-1, 100)),
LintSeverity[string(l.code)[1]],
string(l.code),
"Lint.jl",
l.message)
end
v = VarInfo(t, doc)

return Block(utd, ex, r, name,v, lvars, diags)
end

function Base.parse(uri::String, server::LanguageServer, updateall=false)
doc = String(server.documents[uri].data)
linebreaks = get_linebreaks(doc)
n = length(doc.data)
if doc==""
server.documents[uri].blocks = []
return
end
i = findfirst(b->!b.uptodate, server.documents[uri].blocks)

if isempty(server.documents[uri].blocks) || updateall || i==0
i0 = i1 = 1
p0 = p1 = Position(0, 0)
out = Block[]
i4 = 0
else
i4 = findnext(b->b.uptodate, server.documents[uri].blocks,i)
p0 = p1 = server.documents[uri].blocks[i].range.start
i0 = i1 = linebreaks[p0.line+1]+p0.character+1
out = server.documents[uri].blocks[1:i-1]
end

while 0 < i1 ≤ n
(ex,i1) = parse(doc, i0, raise=false)
p0 = get_pos(i0, linebreaks)
p1 = get_pos(i1-1, linebreaks)
if isa(ex, Expr) && ex.head in[:incomplete,:error]
push!(out,Block(false, ex, Range(p0, Position(p0.line+1, 0))))
while true
!(doc[i0] in ['\n','\t',' ']) && break
i0 += 1
end
i0 = i1 = search(doc,'\n',i0)
else
push!(out,Block(true,ex,Range(p0,p1)))
i0 = i1
if i4>0 && ex==server.documents[uri].blocks[i4].ex
dl = p0.line - server.documents[uri].blocks[i4].range.start.line
out = vcat(out,server.documents[uri].blocks[i4+1:end])
for i = i4+1:length(out)
out[i].range.start.line += dl
out[i].range.end.line += dl
end
break
end
end
end
server.documents[uri].blocks = out
return
end



function classify_expr(ex)
if isa(ex, Expr)
if ex.head==:macrocall && ex.args[1]==GlobalRef(Core, Symbol("@doc"))
return classify_expr(ex.args[3])
elseif ex.head in [:const, :global]
return classify_expr(ex.args[1])
elseif ex.head==:function || (ex.head==:(=) && isa(ex.args[1], Expr) && ex.args[1].head==:call)
return parsefunction(ex)
elseif ex.head==:macro
return "macro", ex.args[1].args[1], "", Dict(string(x)=>VarInfo(Any,"macro argument") for x in ex.args[1].args[2:end])
elseif ex.head in [:abstract, :bitstype, :type, :immutable]
return parsedatatype(ex)
elseif ex.head==:module
return "Module", string(ex.args[2]), "", Dict()
elseif ex.head == :(=) && isa(ex.args[1], Symbol)
return "Any", string(ex.args[1]), "", Dict()
end
end
return "Any", "none", "", Dict()
end

function parsefunction(ex)
(isa(ex.args[1], Symbol) || isempty(ex.args[1].args)) && return "Function", "none", "", Dict()
name = string(isa(ex.args[1].args[1], Symbol) ? ex.args[1].args[1] : ex.args[1].args[1].args[1])
lvars = Dict()
for a in ex.args[1].args[2:end]
if isa(a, Symbol)
lvars[string(a)] = VarInfo(Any, "Function argument")
elseif a.head==:(::)
if length(a.args)>1
lvars[string(a.args[1])] = VarInfo(a.args[2], "Function argument")
else
lvars[string(a.args[1])] = VarInfo(DataType, "Function argument")
end
elseif a.head==:kw
if isa(a.args[1], Symbol)
lvars[string(a.args[1])] = VarInfo(Any, "Function keyword argument")
else
lvars[string(a.args[1].args[1])] = VarInfo(a.args[1].args[2],"Function keyword argument")
end
elseif a.head==:parameters
if isa(a.args[1], Symbol)
lvars[string(a.args[1])] = VarInfo(Any, "Function argument")
else
lvars[string(a.args[1].args[1])] = VarInfo(a.args[1].args[2], "Function Argument")
end
end
end
doc = string(ex.args[1])
return "Function", name, doc, lvars
end


function parsedatatype(ex)
fields = Dict()
if ex.head in [:abstract, :bitstype]
name = string(isa(ex.args[1], Symbol) ? ex.args[1] : ex.args[1].args[1])
doc = string(ex)
else
name = string(isa(ex.args[2], Symbol) ? ex.args[2] : ex.args[2].args[1])
st = string(isa(ex.args[2], Symbol) ? "Any" : string(ex.args[2].args[1]))
for a in ex.args[3].args
if isa(a, Symbol)
fields[string(a)] = VarInfo(Any, "")
elseif a.head==:(::)
fields[string(a.args[1])] = VarInfo(length(a.args)==1 ? a.args[1] : a.args[2], "")
end
end
doc = "$name <: $(st)\n"*prod(" $(f[1])::$(f[2].t)\n" for f in fields)
end
return "DataType", name, doc, fields
end

import Base:<, in, intersect
<(a::Position, b::Position) = a.line<b.line || (a.line≤b.line && a.character<b.character)
function in(p::Position, r::Range)
(r.start.line < p.line < r.end.line) ||
(r.start.line == p.line && r.start.character ≤ p.character) ||
(r.end.line == p.line && p.character ≤ r.end.character)
end

intersect(a::Range, b::Range) = a.start in b || b.start in a

get_linebreaks(doc) = [0; find(c->c==0x0a, doc.data); length(doc.data)+1]

function get_pos(i0, lb)
nlb = length(lb)-1
for l in 1:nlb
if lb[l] < i0 ≤ lb[l+1]
return Position(l-1, i0-lb[l]-1)
end
end
end






function get_block(tdpp::TextDocumentPositionParams, server)
for b in server.documents[tdpp.textDocument.uri].blocks
if tdpp.position in b.range
return b
end
end
return
end

function get_block(uri::String, str::String, server)
for b in server.documents[uri].blocks
if str==b.name
return b
end
end
return false
end

function get_type(sword::Vector, tdpp, server)
t = get_type(sword[1],tdpp,server)
for i = 2:length(sword)
fn = get_fn(t, tdpp, server)
if sword[i] in keys(fn)
t = fn[sword[i]]
else
return ""
end
end
return t
end

function get_type(word::AbstractString, tdpp::TextDocumentPositionParams, server)
b = get_block(tdpp, server)
if word in keys(b.localvar)
t = string(b.localvar[word].t)
elseif word in (b->b.name).(server.documents[tdpp.textDocument.uri].blocks)
t = get_block(uri, word, server).var.t
elseif isdefined(Symbol(word))
t = string(typeof(get_sym(word)))
else
t = "Any"
end
return t
end

function get_fn(t::AbstractString, tdpp::TextDocumentPositionParams, server)
if t in (b->b.name).(server.documents[tdpp.textDocument.uri].blocks)
b = get_block(tdpp.textDocument.uri, t, server)
fn = Dict(k => string(b.localvar[k].t) for k in keys(b.localvar))
elseif isdefined(Symbol(t))
sym = get_sym(t)
names = string.(fieldnames(sym))
fn = Dict(names[i]=>string(sym.types[i]) for i = 1:length(names))
else
fn = String[]
end
return fn
end
4 changes: 3 additions & 1 deletion scripts/languageserver/provider_completions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ function process(r::Request{Val{Symbol("textDocument/completion")},TextDocumentP
s = get_sym(i)
d = ""
try
d = join(get_docs(s)[2:end],'\n')
d = get_docs(s)
d = isa(d,Vector{MarkedString}) ? (x->x.value).(d) : d
d = join(d[2:end],'\n')
end
kind = 6
if isa(s,String)
Expand Down
36 changes: 26 additions & 10 deletions scripts/languageserver/provider_diagnostics.jl
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
const LintSeverity = Dict('E'=>1,'W'=>2,'I'=>3)

# function process_diagnostics(uri::String, server::LanguageServer)
# document = server.documents[uri].data
# L = lintfile(URI(replace(unescape(uri),"\\","/")).path[2:end],String(document))
# diags = map(L) do l
# start_col = findfirst(i->i!=' ', document[l.line])-1
# Diagnostic(Range(Position(l.line-1, start_col), Position(l.line-1, typemax(Int)) ),
# LintSeverity[string(l.code)[1]],
# string(l.code),
# "Lint.jl",
# l.message)
# end
# publishDiagnosticsParams = PublishDiagnosticsParams(uri,diags)

# response = Request{Val{Symbol("textDocument/publishDiagnostics")},PublishDiagnosticsParams}(Nullable{Union{String,Int}}(), publishDiagnosticsParams)
# send(response, server)
# end


function process_diagnostics(uri::String, server::LanguageServer)
document = server.documents[uri]
L = lintfile(URI(replace(unescape(uri),"\\","/")).path[2:end],String(document))
diags = map(L) do l
start_col = findfirst(i->i!=' ', document[l.line])-1
Diagnostic(Range(Position(l.line-1, start_col), Position(l.line-1, typemax(Int)) ),
LintSeverity[string(l.code)[1]],
string(l.code),
"Lint.jl",
l.message)
diags = Diagnostic[]
for b in server.documents[uri].blocks
for d in b.diags
push!(diags, d)
end
end
publishDiagnosticsParams = PublishDiagnosticsParams(uri,diags)
publishDiagnosticsParams = PublishDiagnosticsParams(uri, diags)

response = Request{Val{Symbol("textDocument/publishDiagnostics")},PublishDiagnosticsParams}(Nullable{Union{String,Int}}(), publishDiagnosticsParams)
send(response, server)
end


10 changes: 8 additions & 2 deletions scripts/languageserver/provider_hover.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
function process(r::Request{Val{Symbol("textDocument/hover")},TextDocumentPositionParams}, server)
documentation = get_docs(r.params, server)

word = get_word(r.params, server)
sword = split(word,'.')
if length(sword)>1
documentation = [MarkedString(get_type(sword, r.params, server))]
else
documentation = get_docs(r.params, server)
end

response = Response(get(r.id),Hover(documentation))
send(response, server)
end
Expand Down