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

Integrate Cthulhu with VSCode - show types in source code #469

Merged
merged 50 commits into from
Aug 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
e48a8b5
Add support for inlay types with VSCode
Zentrik Jun 13, 2023
40538f4
Fix overwritten method
Zentrik Jun 13, 2023
6414e3e
Possible fix for tests
Zentrik Jun 13, 2023
a7b4bcc
Fix InlayHint, WarnUnstable not defined in printstyled
Zentrik Jun 14, 2023
6f3568a
Switch to just warning on the entire line
Zentrik Jun 14, 2023
a30cf26
Bug Fixes
Zentrik Jun 14, 2023
0f980a2
Run Tests on Extension
Zentrik Jun 14, 2023
8f9877c
Fix tests
Zentrik Jun 14, 2023
8bed8ec
Potential Fix
Zentrik Jun 14, 2023
fccc9ca
Remove code duplication
Zentrik Jun 15, 2023
0cac4f7
Add test for VSCode displaying
Zentrik Jun 15, 2023
d845ee3
Fix tests
Zentrik Jun 15, 2023
621172f
Clean up code
Zentrik Jun 15, 2023
315d34f
Descend into functions in same file or module automatically
Zentrik Jun 16, 2023
700f9a1
Clean up
Zentrik Jun 16, 2023
4a6982a
Add toggles for vscode integration
Zentrik Jun 16, 2023
41f11d7
Fix bug and clean up code
Zentrik Jun 16, 2023
d4473ee
Clean up code
Zentrik Jun 16, 2023
4da0775
Fix typo
Zentrik Jun 16, 2023
1ab675d
Descend recursively into callsites - vscode integration
Zentrik Jun 16, 2023
6d3047a
Fix bugs
Zentrik Jun 17, 2023
acf9693
Fix printing
Zentrik Jun 17, 2023
470dfba
Don't descend into same function multiple times
Zentrik Jun 17, 2023
7738e2a
Prevent crash when automatically descending with vscode
Zentrik Jun 17, 2023
ec8cb53
Remove warnings when descending automatically in vscode
Zentrik Jun 17, 2023
087ee56
Fix crash when descending automatically into recursive function
Zentrik Jun 18, 2023
f9a3cba
Fix VSCode integration for functions defined in Julia Base
Zentrik Jun 19, 2023
eb51cfa
Decent speed up
Zentrik Jun 19, 2023
3aac843
Speed up and clean up
Zentrik Jun 19, 2023
fd95d42
Fix tests and remove code duplication in tests
Zentrik Jun 19, 2023
446bedd
Fix filepath sometimes being nothing
Zentrik Jun 19, 2023
15411cd
Fix possible bug and remove outdated comment
Zentrik Jun 21, 2023
2f07187
Clean up code and improve tests
Zentrik Jul 6, 2023
6ad60a9
Clean up code
Zentrik Jul 30, 2023
d123e95
Clear inlay hints and diagnostics on exit
Zentrik Jul 30, 2023
fc3062f
Revert creation of new files for tests
Zentrik Aug 4, 2023
22985c4
Fix typo
Zentrik Aug 7, 2023
c37e281
Remove duplication by using IOContext
Zentrik Aug 7, 2023
1e27a37
Rename WarnUnstable to Diagnostic
Zentrik Aug 7, 2023
fb45d2c
Clarify comment
Zentrik Aug 7, 2023
84c431b
Improve and speed up VSCode Integration
Zentrik Aug 8, 2023
e75e955
Improve performance by reducing duplicate calls
Zentrik Aug 10, 2023
a5bcc27
Fix crash when diagnostics or inlay hints are disabled
Zentrik Aug 11, 2023
8a2c5cc
Fix bug
Zentrik Aug 14, 2023
1c21e25
Uncomment out test
Zentrik Aug 14, 2023
b221c29
Rename variables
Zentrik Aug 16, 2023
455a21d
Fix return type not being shown
Zentrik Aug 16, 2023
1f17bab
Add proper testing for vscode
Zentrik Aug 16, 2023
5bef950
Remove comment
Zentrik Aug 16, 2023
10a7660
Fix typo
Zentrik Aug 16, 2023
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
1 change: 1 addition & 0 deletions TypedSyntax/src/TypedSyntax.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ using CodeTracking
export TypedSyntaxNode

include("node.jl")
include("vscode.jl")
include("show.jl")

end
3 changes: 2 additions & 1 deletion TypedSyntax/src/node.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ function tsn_and_mappings(m::Method, src::CodeInfo, @nospecialize(rt); warn::Boo
end

function tsn_and_mappings(m::Method, src::CodeInfo, @nospecialize(rt), sourcetext::AbstractString, lineno::Integer; warn::Bool=true, strip_macros::Bool=false, kwargs...)
rootnode = JuliaSyntax.parsestmt(SyntaxNode, sourcetext; filename=string(m.file), first_line=lineno, kwargs...)
filename = isnothing(functionloc(m)[1]) ? string(m.file) : functionloc(m)[1]
timholy marked this conversation as resolved.
Show resolved Hide resolved
rootnode = JuliaSyntax.parsestmt(SyntaxNode, sourcetext; filename=filename, first_line=lineno, kwargs...)
if strip_macros
rootnode = get_function_def(rootnode)
if !is_function_def(rootnode)
Expand Down
36 changes: 25 additions & 11 deletions TypedSyntax/src/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function Base.printstyled(io::IO, rootnode::MaybeTypedSyntaxNode;
sig, body = children(rootnode)
type_annotate, pre, pre2, post = type_annotation_mode(sig, rt; type_annotations, hide_type_stable)
position = show_src_expr(io, sig, position, pre, pre2; type_annotations, iswarn, hide_type_stable, nd)
type_annotate && show_annotation(io, rt, post; iswarn)
type_annotate && show_annotation(io, rt, post, rootnode.source, position; iswarn)
rootnode = body
end
position = show_src_expr(io, rootnode, position, "", ""; type_annotations, iswarn, hide_type_stable, nd)
Expand All @@ -55,6 +55,14 @@ Base.printstyled(rootnode::MaybeTypedSyntaxNode; kwargs...) = printstyled(stdout

ndigits_linenumbers(node::AbstractSyntaxNode, idxend = last_byte(node)) = ndigits(node.source.first_line + nlines(node.source, idxend) - 1)

function _print(io::IO, x, node, position)
print(io, x)

if !isempty(x)
add_hint!(get(io, :inlay_hints, nothing), x, node, position+1)
end
end

function show_src_expr(io::IO, node::MaybeTypedSyntaxNode, position::Int, pre::String, pre2::String; type_annotations::Bool=true, iswarn::Bool=false, hide_type_stable::Bool=false, nd::Int)
_lastidx = last_byte(node)
position = catchup(io, node, position, nd)
Expand All @@ -64,13 +72,13 @@ function show_src_expr(io::IO, node::MaybeTypedSyntaxNode, position::Int, pre::S
position = catchup(io, first(children(node)), position, nd)
end
end
print(io, pre)
_print(io, pre, node.source, position)
for (i, child) in enumerate(children(node))
i == 2 && print(io, pre2)
i == 2 && _print(io, pre2, node.source, position)
cT = gettyp(child)
ctype_annotate, cpre, cpre2, cpost = type_annotation_mode(child, cT; type_annotations, hide_type_stable)
position = show_src_expr(io, child, position, cpre, cpre2; type_annotations, iswarn, hide_type_stable, nd)
ctype_annotate && show_annotation(io, cT, cpost; iswarn)
ctype_annotate && show_annotation(io, cT, cpost, node.source, position; iswarn)
end
return catchup(io, node, position, nd, _lastidx+1)
end
Expand Down Expand Up @@ -100,19 +108,25 @@ function type_annotation_mode(node, @nospecialize(T); type_annotations::Bool, hi
return type_annotate, pre, pre2, post
end

function show_annotation(io, @nospecialize(T), post=""; iswarn::Bool)
function show_annotation(io, @nospecialize(T), post, node, position; iswarn::Bool)
diagnostics = get(io, :diagnostics, nothing)
inlay_hints = get(io, :inlay_hints, nothing)

print(io, post)
if iswarn
color = !is_type_unstable(T) ? :cyan :
is_small_union_or_tunion(T) ? :yellow : :red
printstyled(io, "::", T; color)
T_str = string(T)
if iswarn && is_type_unstable(T)
color = is_small_union_or_tunion(T) ? :yellow : :red
printstyled(io, "::", T_str; color)
add_diagnostic!(diagnostics, node, position+1, is_small_union_or_tunion(T) ? DiagnosticKinds.Information : DiagnosticKinds.Warning)
add_hint!(inlay_hints, string(post, "::", T_str), node, position+1; kind=InlayHintKinds.Nothing)
else
printstyled(io, "::", T; color=:cyan)
printstyled(io, "::", T_str; color=:cyan)
add_hint!(inlay_hints, string(post, "::", T_str), node, position+1; kind=InlayHintKinds.Type)
end
end

print_linenumber(io::IO, node::MaybeTypedSyntaxNode, position::Int, nd::Int) =
print_linenumber(io, source_line(node.source, position), nd)
print_linenumber(io, source_line(node.source, position+1), nd)
print_linenumber(io::IO, ln::Int, nd::Int) = printstyled(io, lpad(ln, nd), " "; color=:light_black)

# Do any "overdue" printing, generating a line number if needed. Mostly, this catches whitespace.
Expand Down
91 changes: 91 additions & 0 deletions TypedSyntax/src/vscode.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
diagnostics_available_vscode() = isdefined(Main, :VSCodeServer) && Main.VSCodeServer isa Module && isdefined(Main.VSCodeServer, :DIAGNOSTICS_ENABLED) && Main.VSCodeServer.DIAGNOSTICS_ENABLED[]
inlay_hints_available_vscode() = isdefined(Main, :VSCodeServer) && Main.VSCodeServer isa Module && isdefined(Main.VSCodeServer, :INLAY_HINTS_ENABLED) && Main.VSCodeServer.INLAY_HINTS_ENABLED[]
Comment on lines +1 to +2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pfitzseb VSCodeServer is not registered right?

Otherwise we could use package extensions here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not registered, yes. Do package extensions actually require that though? VSCodeServer is a package and is loaded as such, so the appropriate hooks should fire.


module DiagnosticKinds
@enum T Error=0 Warning=1 Information=2 Hint=3
end

struct Diagnostic
path::String
line::Int # 1-based indexing
severity::DiagnosticKinds.T
msg::String
end

to_vscode_type(x::Diagnostic) = (msg=x.msg, path = x.path, line = x.line, severity = Int(x.severity))
function Base.show(io::IO, ::MIME"application/vnd.julia-vscode.diagnostics", diagnostics::AbstractVector{Diagnostic})
return (
source = "Cthulhu",
items = to_vscode_type.(diagnostics),
)
end

add_diagnostic!(::Nothing, node, position, severity) = nothing
function add_diagnostic!(diagnostics, node, position, severity)
file_path = node.filename
line = source_line(node, position)
push!(diagnostics, Diagnostic(file_path, line, severity, "Unstable Type"))
end

function clear_diagnostics_vscode()
if diagnostics_available_vscode()
display(Main.VSCodeServer.InlineDisplay(false), TypedSyntax.Diagnostic[])
end
end

function display_diagnostics_vscode(diagnostics)
if diagnostics_available_vscode() && !isnothing(diagnostics)
# InlineDisplay(false) means we don't print to REPL
display(Main.VSCodeServer.InlineDisplay(false), diagnostics)
end
end
display_diagnostics_vscode(io::IO) = display_diagnostics_vscode(get(io, :diagnostics, nothing))

const InlayHintKinds = (Type=1, Parameter=2, Nothing=nothing)

struct InlayHint
line::Int # 1-based indexing
column::Int # 1-based indexing
label::String
kind::Union{Nothing, Int}
end

to_vscode_type(x::InlayHint) = (position=(x.line, x.column), label=x.label, kind=x.kind)
function Base.show(io::IO, ::MIME"application/vnd.julia-vscode.inlayHints", inlay_hints_by_file::Dict{T, Vector{InlayHint}}) where T
if inlay_hints_available_vscode()
return Dict{T, Vector{NamedTuple{(:position, :label, :kind), Tuple{Tuple{Int, Int}, String, Union{Nothing, Int}}}}}(
filepath => to_vscode_type.(inlay_hints) for (filepath, inlay_hints) in inlay_hints_by_file
)
end
return nothing
end

add_hint!(::Nothing, message, node, position; kind=InlayHintKinds.Type) = nothing
function add_hint!(inlay_hints, message, node, position; kind=InlayHintKinds.Type)
filepath = node.filename
line, column = source_location(node, position)

if filepath ∉ keys(inlay_hints)
inlay_hints[filepath] = InlayHint[]
end
push!(inlay_hints[filepath], InlayHint(line-1, column-1, message, kind))
end

function clear_inlay_hints_vscode()
if inlay_hints_available_vscode()
display(Main.VSCodeServer.InlineDisplay(false), Dict{String, Vector{TypedSyntax.InlayHint}}())
end
end

function display_inlay_hints_vscode(inlay_hints)
if inlay_hints_available_vscode() && !isnothing(inlay_hints)
# InlineDisplay(false) means we don't print to REPL
display(Main.VSCodeServer.InlineDisplay(false), inlay_hints)
end
end
display_inlay_hints_vscode(io::IO) = display_inlay_hints_vscode(get(io, :inlay_hints, nothing))

function clear_all_vscode()
clear_diagnostics_vscode()
clear_inlay_hints_vscode()
end
21 changes: 21 additions & 0 deletions TypedSyntax/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -634,3 +634,24 @@ end
if parse(Bool, get(ENV, "CI", "false"))
include("exhaustive.jl")
end

module VSCodeServer
struct InlineDisplay
is_repl::Bool
end
const INLAY_HINTS_ENABLED = Ref(true)
const DIAGNOSTICS_ENABLED = Ref(true)

function Base.display(d::InlineDisplay, x)
return nothing
end
end
module TestVSCodeExt # stops modules defined in test files from overwriting stuff from previous test
using Test, ..VSCodeServer

@testset "VSCode TypedSyntax.jl" begin
@testset "test_vscode.jl" begin
include("test_vscode.jl")
end
end
end
6 changes: 6 additions & 0 deletions TypedSyntax/test/test_module.jl
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,10 @@ const T426 = Dict{Type{<:Dates.Period}, Bool}(
# Issue #458
f458() = return

function fVSCode(x)
z = x + 1
y = 2 * z
return y + (x > 0 ? -1 : 1.0)
end

end
Loading