From 9c7fb8ecd6e9810662d8473e827d72221598466b Mon Sep 17 00:00:00 2001 From: expandingman Date: Wed, 15 Oct 2025 19:08:37 -0400 Subject: [PATCH 1/4] make an App with Pkg.jl app --- Project.toml | 13 +++++++++---- runserver.jl => src/CLI.jl | 6 ++---- src/JETLS.jl | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) rename runserver.jl => src/CLI.jl (98%) diff --git a/Project.toml b/Project.toml index f52a5cd57..f8b02a7bf 100644 --- a/Project.toml +++ b/Project.toml @@ -17,14 +17,15 @@ Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Preferences = "21216c6a-2e73-6563-6e65-726566657250" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" [sources] JET = {rev = "master", url = "https://github.com/aviatesk/JET.jl"} -JSONRPC = {path = "JSONRPC"} -JuliaLowering = {rev = "jetls-hacking-2", url = "https://github.com/mlechu/JuliaLowering.jl"} -JuliaSyntax = {rev = "jetls-hacking-2", url = "https://github.com/JuliaLang/JuliaSyntax.jl"} -LSP = {path = "LSP"} +JSONRPC = {subdir = "JSONRPC", url = "https://github.com/ExpandingMan/JETLS.jl"} +JuliaLowering = {rev = "jetls-hacking", url = "https://github.com/mlechu/JuliaLowering.jl"} +JuliaSyntax = {rev = "jetls-hacking", url = "https://github.com/JuliaLang/JuliaSyntax.jl"} +LSP = {subdir = "LSP", url = "https://github.com/ExpandingMan/JETLS.jl"} [compat] JET = "0.10.6" @@ -37,5 +38,9 @@ Pkg = "1.11.0" PrecompileTools = "1.3.2" Preferences = "1.4.3" REPL = "1.11.0" +Sockets = "1.11.0" TOML = "1.0.3" julia = "1.12" + +[apps.jetls] +submodule = "CLI" diff --git a/runserver.jl b/src/CLI.jl similarity index 98% rename from runserver.jl rename to src/CLI.jl index ae9d7849c..16755c47b 100644 --- a/runserver.jl +++ b/src/CLI.jl @@ -1,4 +1,4 @@ -module var"##__JETLSEntryPoint__##" +module CLI @info "Running JETLS with Julia version" VERSION @@ -173,6 +173,4 @@ function (@main)(args::Vector{String})::Cint return res.exit_code end -end # module var"##__JETLSEntryPoint__##" - -using .var"##__JETLSEntryPoint__##": main +end # module CLI diff --git a/src/JETLS.jl b/src/JETLS.jl index f80ab6d11..0c6c7e605 100644 --- a/src/JETLS.jl +++ b/src/JETLS.jl @@ -388,4 +388,6 @@ end include("precompile.jl") +include("CLI.jl") + end # module JETLS From 59d4249d7d5cf3e7cc9aff499730a0eb5bcba2ce Mon Sep 17 00:00:00 2001 From: expandingman Date: Thu, 16 Oct 2025 18:25:51 -0400 Subject: [PATCH 2/4] oops; you resolved the conflict the wrong way dumbass --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index f8b02a7bf..03083d3cd 100644 --- a/Project.toml +++ b/Project.toml @@ -23,8 +23,8 @@ TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" [sources] JET = {rev = "master", url = "https://github.com/aviatesk/JET.jl"} JSONRPC = {subdir = "JSONRPC", url = "https://github.com/ExpandingMan/JETLS.jl"} -JuliaLowering = {rev = "jetls-hacking", url = "https://github.com/mlechu/JuliaLowering.jl"} -JuliaSyntax = {rev = "jetls-hacking", url = "https://github.com/JuliaLang/JuliaSyntax.jl"} +JuliaLowering = {rev = "jetls-hacking-2", url = "https://github.com/mlechu/JuliaLowering.jl"} +JuliaSyntax = {rev = "jetls-hacking-2", url = "https://github.com/JuliaLang/JuliaSyntax.jl"} LSP = {subdir = "LSP", url = "https://github.com/ExpandingMan/JETLS.jl"} [compat] From 25b8f41bf925bf3dfa41b10406aa5f71af3a4386 Mon Sep 17 00:00:00 2001 From: expandingman Date: Thu, 16 Oct 2025 18:38:19 -0400 Subject: [PATCH 3/4] add flags --- Project.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 03083d3cd..560542fc0 100644 --- a/Project.toml +++ b/Project.toml @@ -22,10 +22,10 @@ TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" [sources] JET = {rev = "master", url = "https://github.com/aviatesk/JET.jl"} -JSONRPC = {subdir = "JSONRPC", url = "https://github.com/ExpandingMan/JETLS.jl"} +JSONRPC = {subdir = "JSONRPC", url = "https://github.com/aviatesk/JETLS.jl"} JuliaLowering = {rev = "jetls-hacking-2", url = "https://github.com/mlechu/JuliaLowering.jl"} JuliaSyntax = {rev = "jetls-hacking-2", url = "https://github.com/JuliaLang/JuliaSyntax.jl"} -LSP = {subdir = "LSP", url = "https://github.com/ExpandingMan/JETLS.jl"} +LSP = {subdir = "LSP", url = "https://github.com/aviatesk/JETLS.jl"} [compat] JET = "0.10.6" @@ -44,3 +44,4 @@ julia = "1.12" [apps.jetls] submodule = "CLI" +julia_flags = ["--startup-file=no", "--history-file=no", "--threads=auto"] From 1dd2dd1ccfdd92183106f36aa040fa3805d556fd Mon Sep 17 00:00:00 2001 From: expandingman Date: Thu, 16 Oct 2025 18:40:36 -0400 Subject: [PATCH 4/4] restore runserver.jl so as not to break anything --- runserver.jl | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 runserver.jl diff --git a/runserver.jl b/runserver.jl new file mode 100644 index 000000000..ae9d7849c --- /dev/null +++ b/runserver.jl @@ -0,0 +1,178 @@ +module var"##__JETLSEntryPoint__##" + +@info "Running JETLS with Julia version" VERSION + +using Pkg +using Sockets + +# TODO load Revise only when `JETLS_DEV_MODE` is true +try + # load Revise with JuliaInterpreter used by JETLS + using Revise +catch + @warn "Revise not found" +end + +@info "Loading JETLS..." + +try + using JETLS +catch + @error "JETLS not found in this environment" Pkg.project().path + exit(1) +end + +function show_help() + println(stdout, """ + JETLS - A Julia language server providing advanced static analysis and seamless + runtime integration. Powered by JET.jl, JuliaSyntax.jl, and JuliaLowering.jl. + + Usage: julia runserver.jl [OPTIONS] + + Communication channel options (choose one, default: --stdio): + --stdio Use standard input/output + --pipe= Use named pipe (Windows) or Unix domain socket + --socket= Use TCP socket on specified port + + Options: + --clientProcessId= Monitor client process (server shuts down if client exits) + --help, -h Show this help message + + Examples: + julia runserver.jl + julia runserver.jl --socket=8080 + julia runserver.jl --pipe=/tmp/jetls.sock --clientProcessId=12345 + """) +end + +function (@main)(args::Vector{String})::Cint + pipe_name = socket_port = client_process_id = nothing + help_requested = false + + i = 1 + while i <= length(args) + arg = args[i] + if occursin(r"^(?:-h|--help|help)$", arg) + show_help() + return Cint(0) + elseif occursin(r"^(?:--)?stdio$", arg) + elseif occursin(r"^(?:--)?pipe$", arg) + socket_port = nothing + if i < length(args) + pipe_name = args[i+1] + i += 1 + else + @error "--pipe requires a path argument: use --pipe= or --pipe " + return Cint(1) + end + elseif (m = match(r"^--pipe=(.+)$", arg); !isnothing(m)) + pipe_name = m.captures[1] + elseif occursin(r"^(?:--)?socket$", arg) + if i < length(args) + socket_port = tryparse(Int, args[i+1]) + i += 1 + @goto check_socket_port + else + @error "--socket requires a port argument: use --socket= or --socket " + return Cint(1) + end + elseif (m = match(r"^--socket=(\d+)$", arg); !isnothing(m)) + socket_port = tryparse(Int, m.captures[1]) + @label check_socket_port + if isnothing(socket_port) + @error "Invalid port number for --socket (must be a valid integer)" + return Cint(1) + end + elseif occursin(r"^--clientProcessId$", arg) + if i < length(args) + client_process_id = tryparse(Int, args[i+1]) + i += 1 + @goto check_client_process_id + else + @error "--clientProcessId requires a process ID argument: use --clientProcessId= or --clientProcessId " + return Cint(1) + end + elseif (m = match(r"^--clientProcessId=(\d+)$", arg); !isnothing(m)) + client_process_id = tryparse(Int, m.captures[1]) + @label check_client_process_id + if isnothing(client_process_id) + @error "Invalid process ID for --clientProcessId (must be a valid integer)" + return Cint(1) + end + else + @error "Unknown CLI argument" arg + return Cint(1) + end + i += 1 + end + + isnothing(client_process_id) || + @info "Client process ID provided via command line" client_process_id + + # Create endpoint based on communication channel + if !isnothing(pipe_name) + # Try to connect to client-created socket first, then fallback to creating our own + try + pipe_type = Sys.iswindows() ? "Windows named pipe" : "Unix domain socket" + # Most LSP clients expect server to create the socket, but VSCode extension creates it + # Try connecting first (for VSCode), fallback to listen/accept (for other clients). + try + conn = connect(pipe_name) + endpoint = LSEndpoint(conn, conn) + @info "Connected to existing $pipe_type" pipe_name + catch + # Connection failed - client expects us to create the socket + @info "No existing socket found, creating server socket: $pipe_name" + server_socket = listen(pipe_name) + @info "Waiting for connection on $pipe_type: $pipe_name" + conn = accept(server_socket) + endpoint = LSEndpoint(conn, conn) + @info "Accepted connection on $pipe_type" + end + catch e + @error "Failed to create pipe/socket connection" pipe_name + Base.display_error(stderr, e, catch_backtrace()) + return Cint(1) + end + elseif !isnothing(socket_port) + try + server_socket = listen(socket_port) + actual_port = getsockname(server_socket)[2] + println(stdout, "$actual_port") + @info "Waiting for connection on port" actual_port + conn = accept(server_socket) + endpoint = LSEndpoint(conn, conn) + @info "Connected via TCP socket" actual_port + catch e + @error "Failed to create socket connection" socket_port + Base.display_error(stderr, e, catch_backtrace()) + return Cint(1) + end + else # use stdio as the communication channel + endpoint = LSEndpoint(stdin, stdout) + @info "Using stdio for communication" + end + + if JETLS.JETLS_DEV_MODE + server = Server(endpoint) do s::Symbol, x + @nospecialize x + # allow Revise to apply changes with the dev mode enabled + if s === :received + if !(x isa JETLS.ShutdownRequest || x isa JETLS.ExitNotification) + Revise.revise() + end + end + end + JETLS.currently_running = server + t = Threads.@spawn :interactive runserver(server) + else + t = Threads.@spawn :interactive runserver(endpoint) + end + res = fetch(t) + @info "JETLS server stopped" res.exit_code + return res.exit_code +end + +end # module var"##__JETLSEntryPoint__##" + +using .var"##__JETLSEntryPoint__##": main