Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 2 additions & 1 deletion LSP/src/URIs2/uri_helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ function uri2filename(uri::URI)
return uri2filepath(uri)::String
elseif uri.scheme == "untitled"
return uri.path
else
error(lazy"Unsupported uri: $uri")
end
return nothing
end

function uri2filepath(uri::URI)
Expand Down
12 changes: 6 additions & 6 deletions src/analysis/full-analysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ function begin_full_analysis_progress(server::Server, info::FullAnalysisInfo)
if token === nothing
return nothing
end
filepath = uri2filepath(entryuri(info.entry))
filename = uri2filename(entryuri(info.entry))
pre = info.reanalyze ? "Reanalyzing" : "Analyzing"
title = "$(pre) $(basename(filepath)) [$(entrykind(info.entry))]"
title = "$(pre) $(basename(filename)) [$(entrykind(info.entry))]"
send(server, ProgressNotification(;
params = ProgressParams(;
token,
Expand Down Expand Up @@ -77,7 +77,7 @@ function analyze_parsed_if_exist(server::Server, info::FullAnalysisInfo, args...
fi = get_saved_file_info(server.state, uri)
if !isnothing(fi)
filename = uri2filename(uri)
@assert !isnothing(filename) "Unsupported URI: $uri"
@assert !isnothing(filename) lazy"Unsupported URI: $uri"
parsed = build_tree!(JS.SyntaxNode, fi; filename)
begin_full_analysis_progress(server, info)
try
Expand All @@ -87,7 +87,7 @@ function analyze_parsed_if_exist(server::Server, info::FullAnalysisInfo, args...
end
else
filepath = uri2filepath(uri)
@assert filepath !== nothing "Unsupported URI: $uri"
@assert filepath !== nothing lazy"Unsupported URI: $uri"
begin_full_analysis_progress(server, info)
try
return JET.analyze_and_report_file!(LSInterpreter(server, info), filepath, args...; jetconfigs...)
Expand Down Expand Up @@ -117,7 +117,7 @@ function new_analysis_unit(entry::AnalysisEntry, result)
successfully_analyzed_file_infos = copy(analyzed_file_infos)
is_full_analysis_successful(result) || empty!(successfully_analyzed_file_infos)
analysis_result = FullAnalysisResult(
#=staled=#false, result.res.actual2virtual, update_analyzer_world(result.analyzer),
#=staled=#false, result.res.actual2virtual::JET.Actual2Virtual, update_analyzer_world(result.analyzer),
uri2diagnostics, analyzed_file_infos, successfully_analyzed_file_infos)
return AnalysisUnit(entry, analysis_result)
end
Expand Down Expand Up @@ -219,7 +219,7 @@ function initiate_analysis_unit!(server::Server, uri::URI; token::Union{Nothing,
elseif pkgname === nothing
@goto analyze_script
else # this file is likely one within a package
filepath = uri2filepath(uri)
filepath = uri2filepath(uri)::String # uri.scheme === "file"
filekind, filedir = find_package_directory(filepath, env_path)
if filekind === :script
@goto analyze_script
Expand Down
11 changes: 5 additions & 6 deletions src/completions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,9 @@ function to_completion(binding::JL.BindingInfo,
JL.showprov(io, st; include_location=false)
println(io)
println(io, "```")
filepath = uri2filepath(uri)
line, character = JS.source_location(st)
showtext = "`@ " * simple_loc_text(filepath; line) * "`"
println(io, create_source_location_link(filepath, showtext; line, character))
showtext = "`@ " * simple_loc_text(uri; line) * "`"
println(io, create_source_location_link(uri, showtext; line, character))
value = String(take!(io))
documentation = MarkupContent(;
kind = MarkupKind.Markdown,
Expand Down Expand Up @@ -185,18 +184,18 @@ function global_completions!(items::Dict{String, CompletionItem}, state::ServerS

# Case: `@│`
if prev_kind === JS.K"@"
edit_start_pos = offset_to_xy(fi, JS.token_first_byte(fi.parsed_stream, prev_token_idx))
edit_start_pos = offset_to_xy(fi, JS.token_first_byte(fi.parsed_stream, prev_token_idx::Int))
is_macro_invoke = true
# Case: `@macr│`
elseif prev_kind === JS.K"MacroName"
edit_start_pos = offset_to_xy(fi, JS.token_first_byte(fi.parsed_stream, prev_token_idx-1))
edit_start_pos = offset_to_xy(fi, JS.token_first_byte(fi.parsed_stream, prev_token_idx::Int-1))
is_macro_invoke = true
# Case `│` (empty program)
elseif isnothing(prev_token_idx)
edit_start_pos = Position(; line=0, character=0)
is_macro_invoke = false
elseif JS.is_identifier(prev_kind)
edit_start_pos = offset_to_xy(fi, JS.token_first_byte(fi.parsed_stream, prev_token_idx))
edit_start_pos = offset_to_xy(fi, JS.token_first_byte(fi.parsed_stream, prev_token_idx::Int))
is_macro_invoke = false
else
# When completion is triggered within unknown scope (e.g., comment),
Expand Down
1 change: 1 addition & 0 deletions src/definition.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ For now, it just returns the first line of the method
"""
function LSP.Location(m::Method)
file, line = functionloc(m)
file = file::String
file = to_full_path(file)
return Location(;
uri = filename2uri(file),
Expand Down
2 changes: 1 addition & 1 deletion src/diagnostics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ function handle_DocumentDiagnosticRequest(server::Server, msg::DocumentDiagnosti
end
parsed_stream = file_info.parsed_stream
filename = uri2filename(uri)
@assert !isnothing(filename) "Unsupported URI: $uri"
@assert !isnothing(filename) lazy"Unsupported URI: $uri"
if isempty(parsed_stream.diagnostics)
diagnostics = lowering_diagnostics(file_info, filename)
else
Expand Down
5 changes: 2 additions & 3 deletions src/hover.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,14 @@ function handle_HoverRequest(server::Server, msg::HoverRequest)
target_binding, definitions = target_binding_definitions
io = IOBuffer()
n = length(definitions)
filepath = uri2filepath(uri)
for (i, definition) in enumerate(definitions)
println(io, "```julia")
JL.showprov(io, definition; include_location=false)
println(io)
println(io, "```")
line, character = JS.source_location(definition)
showtext = "`@ " * simple_loc_text(filepath; line) * "`"
println(io, create_source_location_link(filepath, showtext; line, character))
showtext = "`@ " * simple_loc_text(uri; line) * "`"
println(io, create_source_location_link(uri, showtext; line, character))
if i ≠ n
println(io, "\n---\n") # separator
else
Expand Down
6 changes: 3 additions & 3 deletions src/signature-help.jl
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,10 @@ function make_siginfo(m::Method, ca::CallArgs, active_arg::Union{Int, Symbol};
documentation = let
mdl = postprocessor(string(Base.parentmodule(m)))
file, line = Base.updated_methodloc(m)
filepath = to_full_path(file)
filename = to_full_path(file)
MarkupContent(;
kind = MarkupKind.Markdown,
value = "@ `$(mdl)` " * create_source_location_link(filepath; line))
value = "@ `$(mdl)` " * create_source_location_link(filename2uri(filename); line))
end

# We could show the full docs, but there isn't a way to resolve items lazily
Expand Down Expand Up @@ -384,7 +384,7 @@ function cursor_call(ps::JS.ParseStream, st0::JL.SyntaxTree, b::Int)
bas = byte_ancestors(st0, pnb)
# If the previous nontrivia byte is part of a call or macrocall, and it is
# missing a closing paren, use that.
i = findfirst(st -> is_relevant_call(st) && !noparen_macrocall(st), bas)
i = findfirst(st::JL.SyntaxTree -> is_relevant_call(st) && !noparen_macrocall(st), bas)
if !isnothing(i)
basᵢ = bas[i]
if JS.is_error(JS.children(basᵢ)[end])
Expand Down
1 change: 1 addition & 0 deletions src/utils/binding.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ let lowering_module = Module()
ctx1, st1 = JL.expand_forms_1(lowering_module, remove_macrocalls(st0));
ctx2, st2 = JL.expand_forms_2(ctx1, st1);
ctx3, st3 = JL.resolve_scopes(ctx2, st2);
@assert !isnothing(st3)
return ctx3, st3
end
end
Expand Down
74 changes: 43 additions & 31 deletions src/utils/lsp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,68 +14,80 @@ const DEFAULT_DOCUMENT_SELECTOR = DocumentFilter[
]

"""
create_source_location_link(filepath::AbstractString, [showtext::AbstractString];
line=nothing, character=nothing)
create_source_location_link(uri::URI, showtext::Union{Nothing,AbstractString}=nothing;
line::Union{Integer,Nothing}=nothing,
character::Union{Integer,Nothing}=nothing)

Create a markdown-style link to a source location that can be displayed in LSP clients.

This function generates links in the format `"[show text](file://path#L#C)"` which, while
not explicitly stated in the LSP specification, is supported by most LSP clients for
navigation to specific file locations.
This function generates clickable links in the format `[display text](uri#L<line>C<character>)`
that LSP clients can use to navigate to specific file locations. While not explicitly part of
the LSP specification, this markdown link format is widely supported by LSP clients including
VS Code, Neovim, and others.

# Arguments
- `filepath::AbstractString`: The file path to link to
- `showtext::AbstractString`: Optional display text for the link. If not provided,
defaults to the filepath with optional line number
- `line::Union{Integer,Nothing}=nothing`: Optional 1-based line number
- `character::Union{Integer,Nothing}=nothing`: Optional character position (requires `line` to be specified)
- `uri::URI`: The file URI to link to
- `showtext::Union{Nothing,AbstractString}`: Optional display text for the link.
If unspecified, automatically generated using `full_loc_text` from the URI's filename.
- `line::Union{Integer,Nothing}=nothing`: Optional 1-based line number to link to
- `character::Union{Integer,Nothing}=nothing`: Optional 1-based character position within the line.
Note: `character` is only used when `line` is also specified.

# Returns
A markdown-formatted string containing the clickable link.
A markdown-formatted string containing the clickable link that can be rendered in hover
documentation, completion items, or other LSP responses supporting markdown content.

[remote file](http://example.com/file.jl#L5)

# Examples
```julia
create_source_location_link("/path/to/file.jl")
# Returns: "[/path/to/file.jl](file:///path/to/file.jl)"

create_source_location_link("/path/to/file.jl", line=42)
# Basic file link
uri = URI("file:///path/to/file.jl")
create_source_location_link(uri, "file.jl")
# Returns: "[file.jl](file:///path/to/file.jl)"

# Link with line number
create_source_location_link(uri, "file.jl:42"; line=42)
# Returns: "[file.jl:42](file:///path/to/file.jl#L42)"

# Link with line and character position
create_source_location_link(uri, "file.jl:42:10"; line=42, character=10)
# Returns: "[file.jl:42:10](file:///path/to/file.jl#L42C10)"

# Using URI with automatic display text
uri = URI("file:///path/to/file.jl")
create_source_location_link(uri; line=42)
# Returns: "[/path/to/file.jl:42](file:///path/to/file.jl#L42)"

create_source_location_link("/path/to/file.jl", line=42, character=10)
# Returns: "[/path/to/file.jl:42](file:///path/to/file.jl#L42C10)"
```
"""
function create_source_location_link(filepath::AbstractString, showtext::AbstractString;
function create_source_location_link(uri::URI,
showtext::Union{Nothing,AbstractString} = nothing;
line::Union{Integer,Nothing}=nothing,
character::Union{Integer,Nothing}=nothing)
linktext = string(filepath2uri(filepath))
linktext = string(uri)
if line !== nothing
linktext *= "#L$line"
if character !== nothing
linktext *= "C$character"
end
end
if isnothing(showtext)
showtext = full_loc_text(uri; line)
end
return "[$showtext]($linktext)"
end

function create_source_location_link(filepath::AbstractString;
line::Union{Integer,Nothing}=nothing,
character::Union{Integer,Nothing}=nothing)
create_source_location_link(filepath, full_loc_text(filepath; line); line, character)
end

function full_loc_text(filepath::AbstractString;
line::Union{Integer,Nothing}=nothing)
loctext = filepath
function full_loc_text(uri::URI; line::Union{Integer,Nothing}=nothing)
loctext = uri2filename(uri)
Base.stacktrace_contract_userdir() && (loctext = Base.contractuser(loctext))
if line !== nothing
loctext *= string(":", line)
end
return loctext
end

function simple_loc_text(filepath::AbstractString; line::Union{Integer,Nothing}=nothing)
loctext = basename(filepath)
function simple_loc_text(uri::URI; line::Union{Integer,Nothing}=nothing)
loctext = basename(uri2filename(uri))
if line !== nothing
loctext *= string(":", line)
end
Expand Down
4 changes: 2 additions & 2 deletions src/utils/pkg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function find_analysis_env_path(state::ServerState, uri::URI)
# try to analyze untitled editors using the root environment
return isdefined(state, :root_env_path) ? state.root_env_path : nothing
end
error("Unsupported URI: $uri")
error(lazy"Unsupported URI: $uri")
end

function find_uri_env_path(state::ServerState, uri::URI)
Expand All @@ -28,7 +28,7 @@ function find_uri_env_path(state::ServerState, uri::URI)
# try to analyze untitled editors using the root environment
return isdefined(state, :root_env_path) ? state.root_env_path : nothing
end
error("Unsupported URI: $uri")
error(lazy"Unsupported URI: $uri")
end

function find_pkg_name(env_path::AbstractString)
Expand Down
15 changes: 12 additions & 3 deletions test/utils/test_lsp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@ module test_lsp
using Test
using JETLS: JETLS
using JETLS.LSP
using JETLS.LSP.URIs2

@testset "create_source_location_link" begin
@test JETLS.create_source_location_link("/path/to/file.jl") == "[/path/to/file.jl](file:///path/to/file.jl)"
@test JETLS.create_source_location_link("/path/to/file.jl", line=42) == "[/path/to/file.jl:42](file:///path/to/file.jl#L42)"
@test JETLS.create_source_location_link("/path/to/file.jl", line=42, character=10) == "[/path/to/file.jl:42](file:///path/to/file.jl#L42C10)"
uri = URI("file:///path/to/file.jl")
@test JETLS.create_source_location_link(uri) == "[/path/to/file.jl](file:///path/to/file.jl)"
@test JETLS.create_source_location_link(uri; line=42) == "[/path/to/file.jl:42](file:///path/to/file.jl#L42)"
@test JETLS.create_source_location_link(uri; line=42, character=10) == "[/path/to/file.jl:42](file:///path/to/file.jl#L42C10)"
@test JETLS.create_source_location_link(uri, "file.jl") == "[file.jl](file:///path/to/file.jl)"
@test JETLS.create_source_location_link(uri, "file.jl:42"; line=42) == "[file.jl:42](file:///path/to/file.jl#L42)"
@test JETLS.create_source_location_link(uri, "file.jl:42:10"; line=42, character=10) == "[file.jl:42:10](file:///path/to/file.jl#L42C10)"
@test JETLS.create_source_location_link(uri; character=10) == "[/path/to/file.jl](file:///path/to/file.jl)"

http_uri = URI("http://example.com/file.jl")
@test JETLS.create_source_location_link(http_uri, "remote file"; line=5) == "[remote file](http://example.com/file.jl#L5)"
end

@testset "Position comparison" begin
Expand Down
Loading