Skip to content

Commit 7b4759b

Browse files
authored
Merge pull request #978 from julia-vscode/newuri
Rewrite uri handling
2 parents fccc5b8 + d1fb755 commit 7b4759b

34 files changed

+565
-245
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
1313
StaticLint = "b3cc710f-9c33-5bdb-a03d-a94903873e97"
1414
SymbolServer = "cf896787-08d5-524d-9de7-132aaa0cb996"
1515
Tokenize = "0796e94c-ce3b-5d07-9a54-7f471281c624"
16-
URIParser = "30578b45-9adc-5946-b283-645ec420af67"
16+
URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
1717
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
1818

1919
[compat]
2020
CSTParser = "3.3"
2121
JSON = "0.20, 0.21"
2222
JSONRPC = "1.1"
23+
URIs = "1.3"
2324
JuliaFormatter = "0.20.0, 0.21, 0.22"
2425
StaticLint = "8.0"
2526
SymbolServer = "7.1"
2627
Tokenize = "0.5.10"
27-
URIParser = "0.4.1"
2828
julia = "1"
2929

3030
[extras]

src/LanguageServer.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
module LanguageServer
2-
import URIParser
32
using JSON, REPL, CSTParser, JuliaFormatter, SymbolServer, StaticLint
43
using CSTParser: EXPR, Tokenize.Tokens, Tokenize.Tokens.kind, headof, parentof, valof, to_codeobject
54
using StaticLint: refof, scopeof, bindingof
@@ -10,8 +9,12 @@ using JSONRPC: Outbound, @dict_readable
109

1110
export LanguageServerInstance, runserver
1211

12+
include("URIs2/URIs2.jl")
13+
using .URIs2
14+
15+
JSON.lower(uri::URI) = string(uri)
16+
1317
include("exception_types.jl")
14-
include("uri2.jl")
1518
include("protocol/protocol.jl")
1619
include("extensions/extensions.jl")
1720
include("document.jl")

src/URIs2/URIs2.jl

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
module URIs2
2+
3+
import URIs
4+
5+
export URI, uri2filepath, filepath2uri, @uri_str
6+
7+
struct URI
8+
scheme::Union{String,Nothing}
9+
authority::Union{String,Nothing}
10+
path::String
11+
query::Union{String,Nothing}
12+
fragment::Union{String,Nothing}
13+
end
14+
15+
function percent_decode(str::AbstractString)
16+
return URIs.unescapeuri(str)
17+
end
18+
19+
function URI(value::AbstractString)
20+
m = match(r"^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?", value)
21+
22+
m===nothing && error("Invalid argument.")
23+
24+
return URI(
25+
m.captures[2],
26+
m.captures[4]===nothing ? nothing : percent_decode(m.captures[4]),
27+
m.captures[5]===nothing ? nothing : percent_decode(m.captures[5]),
28+
m.captures[7]===nothing ? nothing : percent_decode(m.captures[7]),
29+
m.captures[9]===nothing ? nothing : percent_decode(m.captures[9])
30+
)
31+
end
32+
33+
function URI(;
34+
scheme::Union{AbstractString,Nothing}=nothing,
35+
authority::Union{AbstractString,Nothing}=nothing,
36+
path::AbstractString="",
37+
query::Union{AbstractString,Nothing}=nothing,
38+
fragment::Union{AbstractString,Nothing}=nothing
39+
)
40+
return URI(scheme, authority, path, query, fragment)
41+
end
42+
43+
@inline function is_rfc3986_unreserved(c::Char)
44+
return 'A' <= c <= 'Z' ||
45+
'a' <= c <= 'z' ||
46+
'0' <= c <= '9' ||
47+
c == '-' ||
48+
c == '.' ||
49+
c == '_' ||
50+
c == '~'
51+
end
52+
53+
@inline function is_rfc3986_sub_delim(c::Char)
54+
return c == '!' ||
55+
c == '$' ||
56+
c == '&' ||
57+
c == '\'' ||
58+
c == '(' ||
59+
c == ')' ||
60+
c == '*' ||
61+
c == '+' ||
62+
c == ',' ||
63+
c == ';' ||
64+
c == '='
65+
end
66+
67+
@inline function is_rfc3986_pchar(c::Char)
68+
return is_rfc3986_unreserved(c) ||
69+
is_rfc3986_sub_delim(c) ||
70+
c == ':' ||
71+
c == '@'
72+
end
73+
74+
@inline function is_rfc3986_query(c::Char)
75+
return is_rfc3986_pchar(c) || c=='/' || c=='?'
76+
end
77+
78+
@inline function is_rfc3986_fragment(c::Char)
79+
return is_rfc3986_pchar(c) || c=='/' || c=='?'
80+
end
81+
82+
@inline function is_rfc3986_userinfo(c::Char)
83+
return is_rfc3986_unreserved(c) ||
84+
is_rfc3986_sub_delim(c) ||
85+
c == ':'
86+
end
87+
88+
@inline function is_rfc3986_reg_name(c::Char)
89+
return is_rfc3986_unreserved(c) ||
90+
is_rfc3986_sub_delim(c)
91+
end
92+
93+
function encode(io::IO, s::AbstractString, issafe::Function)
94+
for c in s
95+
if issafe(c)
96+
print(io, c)
97+
else
98+
print(io, '%')
99+
print(io, uppercase(string(Int(c), base=16, pad=2)))
100+
end
101+
end
102+
end
103+
104+
@inline function is_ipv4address(s::AbstractString)
105+
if length(s)==1
106+
return '0' <= s[1] <= '9'
107+
elseif length(s)==2
108+
return '1' <= s[1] <= '9' && '0' <= s[2] <= '9'
109+
elseif length(s)==3
110+
return (s[1]=='1' && '0' <= s[2] <= '9' && '0' <= s[3] <= '9') ||
111+
(s[1]=='2' && '0' <= s[2] <= '4' && '0' <= s[3] <= '9') ||
112+
(s[1]=='2' && s[2] == '5' && '0' <= s[3] <= '5')
113+
else
114+
return false
115+
end
116+
end
117+
118+
@inline function is_ipliteral(s::AbstractString)
119+
# TODO Implement this
120+
return false
121+
end
122+
123+
function encode_host(io::IO, s::AbstractString)
124+
if is_ipv4address(s) || is_ipliteral(s)
125+
print(io, s)
126+
else
127+
# The host must be a reg-name
128+
encode(io, s, is_rfc3986_reg_name)
129+
end
130+
end
131+
132+
function encode_path(io::IO, s::AbstractString)
133+
# TODO Write our own version
134+
print(io, URIs.escapepath(s))
135+
end
136+
137+
function Base.print(io::IO, uri::URI)
138+
scheme = uri.scheme
139+
authority = uri.authority
140+
path = uri.path
141+
query = uri.query
142+
fragment = uri.fragment
143+
144+
if scheme!==nothing
145+
print(io, scheme)
146+
print(io, ':')
147+
end
148+
149+
if authority!==nothing
150+
print(io, "//")
151+
152+
idx = findfirst("@", authority)
153+
if idx !== nothing
154+
# <user>@<auth>
155+
userinfo = SubString(authority, 1:idx.start-1)
156+
host_and_port = SubString(authority, idx.start + 1)
157+
encode(io, userinfo, is_rfc3986_userinfo)
158+
print(io, '@')
159+
else
160+
host_and_port = SubString(authority, 1)
161+
end
162+
163+
idx3 = findfirst(":", host_and_port)
164+
if idx3 === nothing
165+
encode_host(io, host_and_port)
166+
else
167+
# <auth>:<port>
168+
encode_host(io, SubString(host_and_port, 1:idx3.start-1))
169+
print(io, SubString(host_and_port, idx3.start))
170+
end
171+
end
172+
173+
# Append path
174+
encode_path(io, path)
175+
176+
if query!==nothing
177+
print(io, '?')
178+
encode(io, query, is_rfc3986_query)
179+
end
180+
181+
if fragment!==nothing
182+
print(io, '#')
183+
encode(io, fragment, is_rfc3986_fragment)
184+
end
185+
186+
return nothing
187+
end
188+
189+
function Base.string(uri::URI)
190+
io = IOBuffer()
191+
192+
print(io, uri)
193+
194+
return String(take!(io))
195+
end
196+
197+
macro uri_str(ex)
198+
return URI(ex)
199+
end
200+
201+
include("uri_helpers.jl")
202+
203+
end

src/URIs2/uri_helpers.jl

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
function uri2filepath(uri::URI)
3+
if uri.scheme != "file"
4+
return nothing
5+
end
6+
7+
path = uri.path
8+
host = uri.authority
9+
10+
if host!==nothing && host != "" && length(path) > 1
11+
# unc path: file://shares/c$/far/boo
12+
value = "//$host$path"
13+
elseif length(path) >= 3 &&
14+
path[1] == '/' &&
15+
isascii(path[2]) && isletter(path[2]) &&
16+
path[3] == ':'
17+
# windows drive letter: file:///c:/far/boo
18+
value = lowercase(path[2]) * path[3:end]
19+
else
20+
# other path
21+
value = path
22+
end
23+
24+
if Sys.iswindows()
25+
value = replace(value, '/' => '\\')
26+
end
27+
28+
return value
29+
end
30+
31+
function filepath2uri(path::String)
32+
isabspath(path) || error("Relative path `$path` is not valid.")
33+
34+
path = normpath(path)
35+
36+
if Sys.iswindows()
37+
path = replace(path, "\\" => "/")
38+
end
39+
40+
authority = ""
41+
42+
if startswith(path, "//")
43+
# UNC path //foo/bar/foobar
44+
idx = findnext("/", path, 3)
45+
if idx===nothing
46+
authority = path[3:end]
47+
path = "/"
48+
else
49+
authority = path[3:idx.start-1]
50+
path = path[idx.start:end]
51+
end
52+
elseif length(path)>=2 && isascii(path[1]) && isletter(path[1]) && path[2]==':'
53+
path = string('/', lowercase(path[1]), SubString(path, 2))
54+
end
55+
56+
return URI(scheme="file", authority=authority, path=path)
57+
end

src/document.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
mutable struct Document
2-
_uri::String
2+
_uri::URI
33
_path::String
44
_content::String
55
_line_offsets::Union{Nothing,Vector{Int}}
@@ -11,7 +11,7 @@ mutable struct Document
1111
_version::Int
1212
server
1313
root::Document
14-
function Document(uri::AbstractString, text::AbstractString, workspace_file::Bool, server=nothing)
14+
function Document(uri::URI, text::AbstractString, workspace_file::Bool, server=nothing)
1515
path = something(uri2filepath(uri), "")
1616
path == "" || isabspath(path) || throw(LSRelativePath("Relative path `$path` is not valid."))
1717
cst = CSTParser.parse(text, true)

src/languageserverinstance.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ For normal usage, the language server can be instantiated with
2828
mutable struct LanguageServerInstance
2929
jr_endpoint::Union{JSONRPC.JSONRPCEndpoint,Nothing}
3030
workspaceFolders::Set{String}
31-
_documents::Dict{URI2,Document}
31+
_documents::Dict{URI,Document}
3232

3333
env_path::String
3434
depot_path::String
@@ -67,7 +67,7 @@ mutable struct LanguageServerInstance
6767
new(
6868
JSONRPC.JSONRPCEndpoint(pipe_in, pipe_out, err_handler),
6969
Set{String}(),
70-
Dict{URI2,Document}(),
70+
Dict{URI,Document}(),
7171
env_path,
7272
depot_path,
7373
SymbolServer.SymbolServerInstance(depot_path, symserver_store_path; symbolcache_upstream = symbolcache_upstream),
@@ -101,11 +101,11 @@ function Base.display(server::LanguageServerInstance)
101101
end
102102
end
103103

104-
function hasdocument(server::LanguageServerInstance, uri::URI2)
104+
function hasdocument(server::LanguageServerInstance, uri::URI)
105105
return haskey(server._documents, uri)
106106
end
107107

108-
function getdocument(server::LanguageServerInstance, uri::URI2)
108+
function getdocument(server::LanguageServerInstance, uri::URI)
109109
return server._documents[uri]
110110
end
111111

@@ -121,11 +121,11 @@ function getdocuments_value(server::LanguageServerInstance)
121121
return values(server._documents)
122122
end
123123

124-
function setdocument!(server::LanguageServerInstance, uri::URI2, doc::Document)
124+
function setdocument!(server::LanguageServerInstance, uri::URI, doc::Document)
125125
server._documents[uri] = doc
126126
end
127127

128-
function deletedocument!(server::LanguageServerInstance, uri::URI2)
128+
function deletedocument!(server::LanguageServerInstance, uri::URI)
129129
doc = getdocument(server, uri)
130130
StaticLint.clear_meta(getcst(doc))
131131
delete!(server._documents, uri)

src/protocol/basic.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ mutable struct DocumentFilter <: Outbound
2121
pattern::Union{String,Missing}
2222
end
2323
const DocumentSelector = Vector{DocumentFilter}
24-
const DocumentUri = String
24+
const DocumentUri = URI
2525

2626
@dict_readable struct Position
2727
line::Int
@@ -56,7 +56,7 @@ Location(f::String, line::Integer) = Location(f, Range(line))
5656
end
5757

5858
@dict_readable struct WorkspaceFolder
59-
uri::String
59+
uri::DocumentUri
6060
name::String
6161
end
6262

src/protocol/features.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ end
120120

121121
struct DocumentLink <: Outbound
122122
range::Range
123-
target::Union{String,Missing}
123+
target::Union{DocumentUri,Missing}
124124
tooltip::Union{String,Missing}
125125
data::Union{Any,Missing}
126126
end

0 commit comments

Comments
 (0)