mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-11-10 14:11:29 +08:00
Add doctest decorations to monaco editor per result (#1911)
This commit is contained in:
parent
5c923b3a2c
commit
e614dcbac1
6 changed files with 157 additions and 1 deletions
|
|
@ -158,3 +158,37 @@ Also some spacing adjustments.
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* To style circles for doctest results */
|
||||||
|
.line-circle-red {
|
||||||
|
background-color: red;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 100%;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 2px;
|
||||||
|
max-width: 15px;
|
||||||
|
height: 15px !important;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-circle-green {
|
||||||
|
background-color: green;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 100%;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 2px;
|
||||||
|
max-width: 15px;
|
||||||
|
height: 15px !important;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-circle-grey {
|
||||||
|
background-color: grey;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 100%;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 2px;
|
||||||
|
max-width: 15px;
|
||||||
|
height: 15px !important;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,28 @@ const Cell = {
|
||||||
liveEditor.setCodeErrorMarker(code_error);
|
liveEditor.setCodeErrorMarker(code_error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.handleEvent(`start_evaluation:${this.props.cellId}`, () => {
|
||||||
|
liveEditor.clearDoctestDecorations();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.handleEvent(
|
||||||
|
`doctest_result:${this.props.cellId}`,
|
||||||
|
({ state, line }) => {
|
||||||
|
console.log({ state, line });
|
||||||
|
switch (state) {
|
||||||
|
case "evaluating":
|
||||||
|
liveEditor.addEvaluatingDoctestDecoration(line);
|
||||||
|
break;
|
||||||
|
case "success":
|
||||||
|
liveEditor.addSuccessDoctestDecoration(line);
|
||||||
|
break;
|
||||||
|
case "failed":
|
||||||
|
liveEditor.addFailedDoctestDecoration(line);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,15 @@ class LiveEditor {
|
||||||
this._onBlur = [];
|
this._onBlur = [];
|
||||||
this._onCursorSelectionChange = [];
|
this._onCursorSelectionChange = [];
|
||||||
this._remoteUserByClientId = {};
|
this._remoteUserByClientId = {};
|
||||||
|
/* For doctest decorations we store the params to create the
|
||||||
|
* decorations and also the result of creating the decorations.
|
||||||
|
* The params are IModelDeltaDecoration from https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IModelDeltaDecoration.html
|
||||||
|
* and the result is IEditorDecorationsCollection from https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorDecorationsCollection.html
|
||||||
|
*/
|
||||||
|
this._doctestDecorations = {
|
||||||
|
deltaDecorations: {},
|
||||||
|
decorationCollection: null,
|
||||||
|
};
|
||||||
|
|
||||||
const serverAdapter = new HookServerAdapter(hook, cellId, tag);
|
const serverAdapter = new HookServerAdapter(hook, cellId, tag);
|
||||||
this.editorClient = new EditorClient(serverAdapter, revision);
|
this.editorClient = new EditorClient(serverAdapter, revision);
|
||||||
|
|
@ -270,6 +279,9 @@ class LiveEditor {
|
||||||
: "off",
|
: "off",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._doctestDecorations.decorationCollection =
|
||||||
|
this.editor.createDecorationsCollection([]);
|
||||||
|
|
||||||
this.editor.addAction({
|
this.editor.addAction({
|
||||||
contextMenuGroupId: "word-wrapping",
|
contextMenuGroupId: "word-wrapping",
|
||||||
id: "enable-word-wrapping",
|
id: "enable-word-wrapping",
|
||||||
|
|
@ -566,6 +578,40 @@ class LiveEditor {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearDoctestDecorations() {
|
||||||
|
this._doctestDecorations.decorationCollection.clear();
|
||||||
|
this._doctestDecorations.deltaDecorations = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
_createDoctestDecoration(lineNumber, className) {
|
||||||
|
return {
|
||||||
|
range: new monaco.Range(lineNumber, 1, lineNumber, 1),
|
||||||
|
options: {
|
||||||
|
isWholeLine: true,
|
||||||
|
linesDecorationsClassName: className,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_addDoctestDecoration(line, className) {
|
||||||
|
const newDecoration = this._createDoctestDecoration(line, className);
|
||||||
|
this._doctestDecorations.deltaDecorations[line] = newDecoration;
|
||||||
|
const decos = Object.values(this._doctestDecorations.deltaDecorations);
|
||||||
|
this._doctestDecorations.decorationCollection.set(decos);
|
||||||
|
}
|
||||||
|
|
||||||
|
addSuccessDoctestDecoration(line) {
|
||||||
|
this._addDoctestDecoration(line, "line-circle-green");
|
||||||
|
}
|
||||||
|
|
||||||
|
addFailedDoctestDecoration(line) {
|
||||||
|
this._addDoctestDecoration(line, "line-circle-red");
|
||||||
|
}
|
||||||
|
|
||||||
|
addEvaluatingDoctestDecoration(line) {
|
||||||
|
this._addDoctestDecoration(line, "line-circle-grey");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function completionItemsToSuggestions(items, settings) {
|
function completionItemsToSuggestions(items, settings) {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,12 @@ defmodule Livebook.Runtime.Evaluator.Doctests do
|
||||||
tests =
|
tests =
|
||||||
test_module.tests
|
test_module.tests
|
||||||
|> Enum.sort_by(& &1.tags.doctest_line)
|
|> Enum.sort_by(& &1.tags.doctest_line)
|
||||||
|> Enum.map(&run_test/1)
|
|> Enum.map(fn test ->
|
||||||
|
report_doctest_state(:evaluating, test)
|
||||||
|
test = run_test(test)
|
||||||
|
report_doctest_state(:success_or_failed, test)
|
||||||
|
test
|
||||||
|
end)
|
||||||
|
|
||||||
formatted = format_results(tests)
|
formatted = format_results(tests)
|
||||||
put_output({:text, formatted})
|
put_output({:text, formatted})
|
||||||
|
|
@ -35,6 +40,24 @@ defmodule Livebook.Runtime.Evaluator.Doctests do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp report_doctest_state(:evaluating, test) do
|
||||||
|
result = %{
|
||||||
|
doctest_line: test.tags.doctest_line,
|
||||||
|
state: :evaluating
|
||||||
|
}
|
||||||
|
|
||||||
|
put_output({:doctest_result, result})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp report_doctest_state(:success_or_failed, test) do
|
||||||
|
result = %{
|
||||||
|
doctest_line: test.tags.doctest_line,
|
||||||
|
state: get_in(test, [Access.key(:state), Access.elem(0)]) || :success
|
||||||
|
}
|
||||||
|
|
||||||
|
put_output({:doctest_result, result})
|
||||||
|
end
|
||||||
|
|
||||||
defp define_test_module(modules) do
|
defp define_test_module(modules) do
|
||||||
id =
|
id =
|
||||||
modules
|
modules
|
||||||
|
|
|
||||||
|
|
@ -536,6 +536,19 @@ defmodule Livebook.Session.Data do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def apply_operation(
|
||||||
|
data,
|
||||||
|
{:add_cell_evaluation_output, _client_id, id, {:doctest_result, _result}}
|
||||||
|
) do
|
||||||
|
with {:ok, _cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id) do
|
||||||
|
data
|
||||||
|
|> with_actions()
|
||||||
|
|> wrap_ok()
|
||||||
|
else
|
||||||
|
_ -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def apply_operation(data, {:add_cell_evaluation_output, _client_id, id, output}) do
|
def apply_operation(data, {:add_cell_evaluation_output, _client_id, id, output}) do
|
||||||
with {:ok, cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id) do
|
with {:ok, cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id) do
|
||||||
data
|
data
|
||||||
|
|
|
||||||
|
|
@ -1760,6 +1760,17 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp after_operation(
|
||||||
|
socket,
|
||||||
|
_prev_socket,
|
||||||
|
{:add_cell_evaluation_output, _client_id, cell_id, {:doctest_result, result}}
|
||||||
|
) do
|
||||||
|
push_event(socket, "doctest_result:#{cell_id}", %{
|
||||||
|
state: result.state,
|
||||||
|
line: result.doctest_line
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
defp after_operation(
|
defp after_operation(
|
||||||
socket,
|
socket,
|
||||||
_prev_socket,
|
_prev_socket,
|
||||||
|
|
@ -1821,6 +1832,10 @@ defmodule LivebookWeb.SessionLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_action(socket, {:start_evaluation, cell, _section}) do
|
||||||
|
push_event(socket, "start_evaluation:#{cell.id}", %{})
|
||||||
|
end
|
||||||
|
|
||||||
defp handle_action(socket, _action), do: socket
|
defp handle_action(socket, _action), do: socket
|
||||||
|
|
||||||
defp client_info(id, user) do
|
defp client_info(id, user) do
|
||||||
|
|
@ -2229,6 +2244,9 @@ defmodule LivebookWeb.SessionLive do
|
||||||
|
|
||||||
data_view
|
data_view
|
||||||
|
|
||||||
|
{:add_cell_evaluation_output, _client_id, _cell_id, {:doctest_result, _result}} ->
|
||||||
|
data_view
|
||||||
|
|
||||||
{:add_cell_evaluation_output, _client_id, cell_id, {:stdout, text}} ->
|
{:add_cell_evaluation_output, _client_id, cell_id, {:stdout, text}} ->
|
||||||
# Lookup in previous data to see if the output is already there
|
# Lookup in previous data to see if the output is already there
|
||||||
case Notebook.fetch_cell_and_section(prev_data.notebook, cell_id) do
|
case Notebook.fetch_cell_and_section(prev_data.notebook, cell_id) do
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue