Add doctest decorations to monaco editor per result (#1911)

This commit is contained in:
Jose Vargas 2023-05-24 06:56:13 -06:00 committed by GitHub
parent 5c923b3a2c
commit e614dcbac1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 157 additions and 1 deletions

View file

@ -158,3 +158,37 @@ Also some spacing adjustments.
margin-left: 2px;
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;
}

View file

@ -242,6 +242,28 @@ const Cell = {
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;
}
}
);
}
}
},

View file

@ -35,6 +35,15 @@ class LiveEditor {
this._onBlur = [];
this._onCursorSelectionChange = [];
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);
this.editorClient = new EditorClient(serverAdapter, revision);
@ -270,6 +279,9 @@ class LiveEditor {
: "off",
});
this._doctestDecorations.decorationCollection =
this.editor.createDecorationsCollection([]);
this.editor.addAction({
contextMenuGroupId: "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) {

View file

@ -20,7 +20,12 @@ defmodule Livebook.Runtime.Evaluator.Doctests do
tests =
test_module.tests
|> 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)
put_output({:text, formatted})
@ -35,6 +40,24 @@ defmodule Livebook.Runtime.Evaluator.Doctests do
:ok
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
id =
modules

View file

@ -536,6 +536,19 @@ defmodule Livebook.Session.Data do
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
with {:ok, cell, _} <- Notebook.fetch_cell_and_section(data.notebook, id) do
data

View file

@ -1760,6 +1760,17 @@ defmodule LivebookWeb.SessionLive do
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(
socket,
_prev_socket,
@ -1821,6 +1832,10 @@ defmodule LivebookWeb.SessionLive do
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 client_info(id, user) do
@ -2229,6 +2244,9 @@ defmodule LivebookWeb.SessionLive do
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}} ->
# Lookup in previous data to see if the output is already there
case Notebook.fetch_cell_and_section(prev_data.notebook, cell_id) do