Skip to content

Commit

Permalink
Added remote debug shell
Browse files Browse the repository at this point in the history
Added a simple remote debugging shell which supplants the debug app we
were using previously. This one allows us to connect observer as well.

I had to update project node to handle this, as it assumed that _any_
nodedown message was coming from the project node.
  • Loading branch information
scohen committed Aug 11, 2023
1 parent d234d89 commit 21123e3
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 240 deletions.
157 changes: 1 addition & 156 deletions .iex.exs
Original file line number Diff line number Diff line change
@@ -1,156 +1 @@
alias Lexical.RemoteControl
alias Lexical.Document
alias Lexical.Document.Position

defmodule Helpers do
alias Lexical.Document.Position
alias Lexical.Project
alias Lexical.Protocol.Types.Completion
alias Lexical.Server.CodeIntelligence

def observer do
:observer.start()
end

def observer(project) do
project
|> ensure_project()
|> RemoteControl.call(:observer, :start)
end

def doc(text) do
doc(:lexical, text)
end

def doc(project, text) do
root_path =
project
|> project()
|> Project.root_path()

[root_path, "lib", "file.ex"]
|> Path.join()
|> Document.Path.to_uri()
|> Document.new(text, 0)
end

def pos(line, character) do
Position.new(line, character)
end

def compile_project(project) do
project
|> ensure_project()
|> RemoteControl.Api.schedule_compile(true)
end

def compile_file(project, source) when is_binary(source) do
project
|> ensure_project()
|> compile_file(doc(source))
end

def compile_file(project, %Document{} = document) do
project
|> ensure_project()
|> RemoteControl.Api.compile_document(document)
end

def complete(project, source, context \\ nil)

def complete(project, source, context) when is_binary(source) do
case completion_position(source) do
{:found, line, character} ->
complete(project, doc(source), line, character, context)

other ->
other
end
end

def complete(project, %Document{} = source, line, character, context) do
context =
if is_nil(context) do
Completion.Context.new(trigger_kind: nil)
else
context
end

position = pos(line, character)

project
|> ensure_project()
|> CodeIntelligence.Completion.complete(source, position, context)
end

def connect do
manager_name = manager_name()
Node.start(:"[email protected]")
Node.set_cookie(:lexical)
Node.connect(:"#{manager_name}@127.0.0.1")
end

def project(project) when is_atom(project) do
project_path =
[File.cwd!(), "..", to_string(project)]
|> Path.join()
|> Path.expand()

project_uri = "file://#{project_path}"
Lexical.Project.new(project_uri)
end

def stop_project(project) do
project
|> ensure_project()
|> Lexical.Server.Project.Supervisor.stop()
end

def start_project(project) do
project
|> ensure_project()
|> Lexical.Server.Project.Supervisor.start()
end

defp manager_name do
{:ok, names} = :erl_epmd.names()

names
|> Enum.map(fn {name, _port} -> List.to_string(name) end)
|> Enum.find(&String.starts_with?(&1, "manager"))
end

defp completion_position(source_string) do
source_string
|> String.split(["\r\n", "\r", "\n"])
|> Enum.with_index()
|> Enum.reduce_while(:not_found, fn {line, line_number}, _ ->
if String.contains?(line, "|") do
index =
line
|> String.graphemes()
|> Enum.find_index(&(&1 == "|"))

{:halt, {:found, line_number + 1, index + 1}}
else
{:cont, :not_found}
end
end)
end

defp ensure_project(%Project{} = project) do
project
end

defp ensure_project(project) when is_binary(project) do
project
|> String.to_atom()
|> project()
end

defp ensure_project(project) when is_atom(project) do
project(project)
end
end

import Helpers
use Lexical.Server.IEx.Helpers
1 change: 1 addition & 0 deletions .iex.namespaced.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use LXical.Server.IEx.Helpers
45 changes: 31 additions & 14 deletions apps/remote_control/lib/lexical/remote_control/project_node.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ defmodule Lexical.RemoteControl.ProjectNode do

erlang_env =
Enum.map(environment_variables, fn {key, value} ->
{String.to_charlist(key), String.to_charlist(value)}
# using to_string ensures nil values won't blow things up
erl_key = key |> to_string() |> String.to_charlist()
erl_value = value |> to_string() |> String.to_charlist()
{erl_key, erl_value}
end)

args = [
Expand Down Expand Up @@ -70,12 +73,25 @@ defmodule Lexical.RemoteControl.ProjectNode do
%{state | status: :stopped}
end

def on_nodeup(%__MODULE__{} = state) do
%{state | status: :started}
def on_nodeup(%__MODULE__{} = state, node_name) do
if node_name == Project.node_name(state.project) do
{pid, _ref} = state.started_by
Process.monitor(pid)
GenServer.reply(state.started_by, :ok)

%{state | status: :started}
else
state
end
end

def on_nodedown(%__MODULE__{} = state) do
%{state | status: :stopped}
def on_nodedown(%__MODULE__{} = state, node_name) do
if node_name == Project.node_name(state.project) do
GenServer.reply(state.stopped_by, :ok)
{:shutdown, %{state | status: :stopped}}
else
:continue
end
end

def on_monitored_dead(%__MODULE__{} = state) do
Expand Down Expand Up @@ -168,11 +184,8 @@ defmodule Lexical.RemoteControl.ProjectNode do
end

@impl true
def handle_info({:nodeup, _node, _}, %State{} = state) do
{pid, _ref} = state.started_by
Process.monitor(pid)
GenServer.reply(state.started_by, :ok)
state = State.on_nodeup(state)
def handle_info({:nodeup, node, _}, %State{} = state) do
state = State.on_nodeup(state, node)
{:noreply, state}
end

Expand All @@ -188,10 +201,14 @@ defmodule Lexical.RemoteControl.ProjectNode do
end

@impl true
def handle_info({:nodedown, _, _}, %State{} = state) do
GenServer.reply(state.stopped_by, :ok)
state = State.on_nodedown(state)
{:stop, :shutdown, state}
def handle_info({:nodedown, node_name, _}, %State{} = state) do
case State.on_nodedown(state, node_name) do
{:shutdown, new_state} ->
{:stop, :shutdown, new_state}

:continue ->
{:noreply, state}
end
end

@impl true
Expand Down
Loading

0 comments on commit 21123e3

Please sign in to comment.