Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .JETLSConfig.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@ match_type = "regex"
severity = "off"
path = "LSP/src/**/*.jl"

# This is type instability within JSON3.jl
[[diagnostic.patterns]]
pattern = "no matching method found `convert(.+)`"
match_by = "message"
match_type = "regex"
severity = "off"
path = "src/testrunner/testrunner-types.jl"

# Treat all other diagnostics as warnings (for early detection via jetls check)
[[diagnostic.patterns]]
pattern = ".*"
Expand Down
10 changes: 6 additions & 4 deletions LSP/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ authors = ["Shuhei Kadowaki <aviatesk@gmail.com>"]
projects = ["test"]

[deps]
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
StructUtils = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42"

[compat]
JSON3 = "1.14.3"
JSON = "1.5"
PrecompileTools = "1.3.3"
StructTypes = "1.11.0"
Preferences = "1.5.0"
StructUtils = "2.6.0"
2 changes: 1 addition & 1 deletion LSP/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ to faithfully translate the TypeScript LSP specification into idiomatic Julia co
- Uses `Union{Nothing, Type} = nothing` field to represent TypeScript's optional properties (`field?: Type`)
- Supports inheritance through `@extends` to compose interfaces (similar to TypeScript's `extends`)
- Enables anonymous interface definitions within `Union` types for inline type specifications
- Automatically configures `StructTypes.omitempties()` to omit optional fields during JSON serialization
- Integrates with JSON.jl v1 and StructUtils.jl for JSON serialization/deserialization
- Creates method dispatchers for `RequestMessage` and `NotificationMessage` types to enable LSP message routing

- **`@namespace` macro**: Creates Julia modules containing typed constants that correspond to TypeScript `namespace`s:
Expand Down
290 changes: 207 additions & 83 deletions LSP/src/DSL/interface.jl

Large diffs are not rendered by default.

21 changes: 20 additions & 1 deletion LSP/src/LSP.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
module LSP

using StructTypes: StructTypes
using StructUtils: StructUtils
using JSON: JSON

using Preferences: Preferences
const LSP_DEV_MODE = Preferences.@load_preference("LSP_DEV_MODE", false)

include("URIs2/URIs2.jl")
using ..URIs2: URI

const exports = Set{Symbol}()

const method_dispatcher = Dict{String,DataType}()

# NOTE `Null` and `URI` are referenced directly from interface.jl, so it should be defined before that.

"""
A special object representing `null` value.
When used as a field that might be omitted in the serialized JSON (i.e. the field can be `nothing`),
the key-value pair appears as `null` instead of being omitted.
This special object is specifically intended for use in `ResponseMessage`.
"""
StructUtils.@nonstruct struct Null end
const null = Null()
Base.show(io::IO, ::Null) = print(io, "null")
StructUtils.lower(::Null) = JSON.Null()
push!(exports, :Null, :null)

include("DSL/interface.jl")
include("DSL/namespace.jl")

Expand Down
11 changes: 5 additions & 6 deletions LSP/src/URIs2/URIs2.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module URIs2

export @uri_str, URI, filename2uri, filepath2uri, isunsavedfile, isunsaveduri, uri2filename, uri2filepath

using StructTypes: StructTypes
using StructUtils: StructUtils

include("vendored-from-uris.jl")

Expand All @@ -22,7 +22,7 @@ Details of a Unified Resource Identifier.
- query::Union{Nothing, String}
- fragment::Union{Nothing, String}
"""
struct URI
StructUtils.@nonstruct struct URI
scheme::Union{String,Nothing}
authority::Union{String,Nothing}
path::String
Expand Down Expand Up @@ -72,10 +72,9 @@ else
end
end

Base.convert(::Type{URI}, s::AbstractString) = URI(s)

# This overload requires `URI(::AbstractString)` as well, which is defined later
StructTypes.StructType(::Type{URI}) = StructTypes.StringType()
# Tell StructUtils how to serialize/deserialize URI as a string
StructUtils.lower(uri::URI) = string(uri)
StructUtils.lift(::Type{URI}, s::String) = URI(s)

function percent_decode(str::AbstractString)
return unescapeuri(str)
Expand Down
12 changes: 0 additions & 12 deletions LSP/src/base-protocol.jl
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
# Base types
# =========

"""
A special object representing `null` value.
When used as a field specified as `StructTypes.omitempties`, the key-value pair is not
omitted in the serialized JSON but instead appears as `null`.
This special object is specifically intended for use in `ResponseMessage`.
"""
struct Null end
const null = Null()
StructTypes.StructType(::Type{Null}) = StructTypes.CustomStruct()
StructTypes.lower(::Null) = nothing
push!(exports, :Null, :null)

const boolean = Bool
const string = String

Expand Down
20 changes: 8 additions & 12 deletions LSP/src/communication.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using JSON3: JSON3

"""
Endpoint

Expand Down Expand Up @@ -115,19 +113,17 @@ function read_transport_layer(io::IO)
return String(read(io, message_length))
end

const Parsed = @NamedTuple{method::Union{Nothing,String}}

function to_lsp_object(msg_str::AbstractString)
parsed = JSON3.read(msg_str, Parsed)
parsed_method = parsed.method
if parsed_method !== nothing
if haskey(method_dispatcher, parsed_method)
return JSON3.read(msg_str, method_dispatcher[parsed_method])
lazyjson = JSON.lazy(msg_str)
if hasproperty(lazyjson, :method)
method = lazyjson.method[]
if method isa String && haskey(method_dispatcher, method)
return JSON.parse(lazyjson, method_dispatcher[method])
end
return JSON3.read(msg_str, Dict{Symbol,Any})
return JSON.parse(lazyjson, Dict{Symbol,Any})
end
# TODO Parse response message?
return JSON3.read(msg_str, Dict{Symbol,Any})
return JSON.parse(lazyjson, Dict{Symbol,Any})
end

writelsp(io::IO, @nospecialize msg) = write_transport_layer(io, to_lsp_json(msg))
Expand All @@ -141,7 +137,7 @@ function write_transport_layer(io::IO, response::String)
return n
end

to_lsp_json(@nospecialize msg) = JSON3.write(msg)
to_lsp_json(@nospecialize msg) = JSON.json(msg; omit_null=true)

function Base.close(endpoint::Endpoint)
put!(endpoint.out_msg_queue, nothing) # send a special token to terminate the write task
Expand Down
4 changes: 2 additions & 2 deletions LSP/src/precompile.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
function test_roundtrip(f, s::AbstractString, Typ)
x = JSON3.read(s, Typ)
x = JSON.parse(s, Typ)
f(x)
s′ = to_lsp_json(x)
x′ = JSON3.read(s′, Typ)
x′ = JSON.parse(s′, Typ)
f(x′)
end

Expand Down
2 changes: 1 addition & 1 deletion LSP/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ using Test
"result": null
}""", DefinitionResponse) do res
@test res isa DefinitionResponse
@test_broken res.result === null # this null should be preserved through the roundtrip
@test res.result === null # this null should be preserved through the roundtrip
end
test_roundtrip("""{
"jsonrpc": "2.0",
Expand Down
4 changes: 2 additions & 2 deletions src/testrunner/testrunner.jl
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ function _testrunner_run_testset(
end

try
LSP.JSON3.read(testrunnerproc, TestRunnerResult)
LSP.JSON.parse(testrunnerproc, TestRunnerResult)
catch err
@error "Error from testrunner process" err
show_error_message(server, """
Expand Down Expand Up @@ -666,7 +666,7 @@ function _testrunner_run_testcase(
end

try
LSP.JSON3.read(testrunnerproc, TestRunnerResult)
LSP.JSON.parse(testrunnerproc, TestRunnerResult)
catch err
@error "Error from testrunner process" err
show_error_message(server, """
Expand Down
Loading