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

Add support for Python cells #2936

Merged
merged 4 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
77 changes: 53 additions & 24 deletions assets/css/js_interop.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,33 +124,64 @@ server in solely client-side operations.
}
}

[data-el-cell][data-type="setup"]:not(
[data-js-focused],
[data-eval-validity="fresh"]:not([data-js-empty]),
[data-eval-errored],
[data-js-changed]
/* When all setup cells satisfy collapse conditions, collapse the
first one and hide the later ones */
[data-el-setup-section]:not(
:has(
[data-el-cell][data-setup]:is(
[data-js-focused],
[data-eval-validity="fresh"]:not([data-js-empty]),
[data-eval-errored],
[data-js-changed]
)
),
:focus-within
) {
[data-el-editor-box] {
[data-el-cell][data-setup]:not(:first-child) {
@apply hidden;
}

[data-el-outputs-container] {
@apply hidden;
[data-el-cell][data-setup]:first-child {
[data-el-editor-box] {
@apply hidden;
}

[data-el-outputs-container] {
@apply hidden;
}

[data-el-cell-indicator] {
@apply bg-gray-50 border-gray-200 text-gray-500;
}
}

[data-el-cell-indicator] {
@apply bg-gray-50 border-gray-200 text-gray-500;
[data-el-language-buttons] {
@apply hidden;
}
}

[data-el-cell][data-type="setup"]:is(
[data-js-focused],
[data-eval-validity="fresh"]:not([data-js-empty]),
[data-eval-errored],
[data-js-changed]
)
[data-el-info-box] {
@apply hidden;
/* This is "else" for the above */
[data-el-setup-section]:is(
:has(
[data-el-cell][data-setup]:is(
[data-js-focused],
[data-eval-validity="fresh"]:not([data-js-empty]),
[data-eval-errored],
[data-js-changed]
)
),
:focus-within
) {
[data-el-cell][data-setup] {
[data-el-info-box] {
@apply hidden;
}

/* Make the primary actions visible for all cells */
[data-el-actions][data-primary] {
@apply opacity-100;
}
}
}

/* Outputs */
Expand Down Expand Up @@ -299,13 +330,11 @@ server in solely client-side operations.
}

&[data-js-hide-code] {
[data-el-cell]:is(
[data-type="code"],
[data-type="setup"],
[data-type="smart"]
):not([data-js-insert-mode]) {
[data-el-cell]:is([data-type="code"], [data-type="smart"]):not(
[data-js-insert-mode]
) {
[data-el-editor-box],
&[data-type="setup"] [data-el-info-box],
&[data-setup] [data-el-info-box],
&[data-type="smart"] [data-el-ui-box] {
@apply hidden;
}
Expand Down
9 changes: 5 additions & 4 deletions assets/js/hooks/cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ const Cell = {

// Setup action handlers

if (["code", "smart"].includes(this.props.type)) {
const amplifyButton = this.el.querySelector(
`[data-el-amplify-outputs-button]`,
);
const amplifyButton = this.el.querySelector(
`[data-el-amplify-outputs-button]`,
);

if (amplifyButton) {
amplifyButton.addEventListener("click", (event) => {
this.el.toggleAttribute("data-js-amplified");
});
Expand Down
12 changes: 12 additions & 0 deletions assets/js/hooks/cell_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ const CellEditor = {
this.el.querySelector(`[data-el-editor-container]`).removeAttribute("id");
},

updated() {
const prevProps = this.props;
this.props = this.getProps();

if (
this.props.language !== prevProps.language ||
this.props.intellisense !== prevProps.intellisense
) {
this.liveEditor.setLanguage(this.props.language, this.props.intellisense);
}
},

destroyed() {
if (this.connection) {
this.connection.destroy();
Expand Down
60 changes: 40 additions & 20 deletions assets/js/hooks/cell_editor/live_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
lineNumbers,
highlightActiveLineGutter,
} from "@codemirror/view";
import { EditorState, EditorSelection } from "@codemirror/state";
import { EditorState, EditorSelection, Compartment } from "@codemirror/state";
import {
indentOnInput,
bracketMatching,
Expand Down Expand Up @@ -236,6 +236,15 @@ export default class LiveEditor {
this.deltaSubscription.destroy();
}

setLanguage(language, intellisense) {
this.language = language;
this.intellisense = intellisense;

this.view.dispatch({
effects: this.languageCompartment.reconfigure(this.languageExtensions()),
});
}

/**
* Either adds or updates doctest indicators.
*/
Expand Down Expand Up @@ -322,13 +331,6 @@ export default class LiveEditor {
},
});

const lineWrappingEnabled =
this.language === "markdown" && settings.editor_markdown_word_wrap;

const language =
this.language &&
LanguageDescription.matchLanguageName(languages, this.language, false);

const customKeymap = [
{ key: "Escape", run: exitMulticursor },
{ key: "Alt-Enter", run: insertBlankLineAndCloseHints },
Expand All @@ -338,6 +340,8 @@ export default class LiveEditor {
this.handleViewUpdate(update),
);

this.languageCompartment = new Compartment();

this.view = new EditorView({
parent: this.container,
doc: this.source,
Expand Down Expand Up @@ -365,7 +369,6 @@ export default class LiveEditor {
keymap.of(vscodeKeymap),
EditorState.tabSize.of(2),
EditorState.lineSeparator.of("\n"),
lineWrappingEnabled ? EditorView.lineWrapping : [],
// We bind tab to actions within the editor, which would trap
// the user if they tabbed into the editor, so we remove it
// from the tab navigation
Expand All @@ -379,19 +382,9 @@ export default class LiveEditor {
activateOnTyping: settings.editor_auto_completion,
defaultKeymap: false,
}),
this.intellisense
? [
autocompletion({ override: [this.completionSource.bind(this)] }),
hoverDetails(this.docsHoverTooltipSource.bind(this)),
signature(this.signatureSource.bind(this), {
activateOnTyping: settings.editor_auto_signature,
}),
formatter(this.formatterSource.bind(this)),
]
: [],
settings.editor_mode === "vim" ? [vim()] : [],
settings.editor_mode === "emacs" ? [emacs()] : [],
language ? language.support : [],
this.languageCompartment.of(this.languageExtensions()),
EditorView.domEventHandlers({
click: this.handleEditorClick.bind(this),
keydown: this.handleEditorKeydown.bind(this),
Expand All @@ -404,6 +397,33 @@ export default class LiveEditor {
});
}

/** @private */
languageExtensions() {
const settings = settingsStore.get();

const lineWrappingEnabled =
this.language === "markdown" && settings.editor_markdown_word_wrap;

const language =
this.language &&
LanguageDescription.matchLanguageName(languages, this.language, false);

return [
lineWrappingEnabled ? EditorView.lineWrapping : [],
language ? language.support : [],
this.intellisense
? [
autocompletion({ override: [this.completionSource.bind(this)] }),
hoverDetails(this.docsHoverTooltipSource.bind(this)),
signature(this.signatureSource.bind(this), {
activateOnTyping: settings.editor_auto_signature,
}),
formatter(this.formatterSource.bind(this)),
]
: [],
];
}

/** @private */
handleEditorClick(event) {
const cmd = isMacOS() ? event.metaKey : event.ctrlKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { javascript } from "@codemirror/lang-javascript";
import { erlang } from "@codemirror/legacy-modes/mode/erlang";
import { dockerFile } from "@codemirror/legacy-modes/mode/dockerfile";
import { shell } from "@codemirror/legacy-modes/mode/shell";
import { toml } from "@codemirror/legacy-modes/mode/toml";
import { elixir } from "codemirror-lang-elixir";

export const elixirDesc = LanguageDescription.of({
Expand Down Expand Up @@ -77,6 +78,12 @@ const shellDesc = LanguageDescription.of({
support: new LanguageSupport(StreamLanguage.define(shell)),
});

const tomlDesc = LanguageDescription.of({
name: "TOML",
alias: ["pyproject.toml"],
support: new LanguageSupport(StreamLanguage.define(toml)),
});

const markdownDesc = LanguageDescription.of({
name: "Markdown",
support: markdown({
Expand All @@ -94,6 +101,7 @@ const markdownDesc = LanguageDescription.of({
javascriptDesc,
dockerfileDesc,
shellDesc,
tomlDesc,
],
}),
});
Expand All @@ -111,5 +119,6 @@ export const languages = [
javascriptDesc,
dockerfileDesc,
shellDesc,
tomlDesc,
markdownDesc,
];
4 changes: 2 additions & 2 deletions assets/js/lib/notebook.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
* Checks if the given cell type is eligible for evaluation.
*/
export function isEvaluable(cellType) {
return ["code", "smart", "setup"].includes(cellType);
return ["code", "smart"].includes(cellType);
}

/**
* Checks if the given cell type has primary editable editor.
*/
export function isDirectlyEditable(cellType) {
return ["markdown", "code", "setup"].includes(cellType);
return ["markdown", "code"].includes(cellType);
}
17 changes: 11 additions & 6 deletions lib/livebook/live_markdown/export.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ defmodule Livebook.LiveMarkdown.Export do
end

defp render_notebook(notebook, ctx) do
%{setup_section: %{cells: [setup_cell]}} = notebook
%{setup_section: %{cells: setup_cells}} = notebook

comments =
Enum.map(notebook.leading_comments, fn
Expand All @@ -65,13 +65,13 @@ defmodule Livebook.LiveMarkdown.Export do
end)

name = ["# ", notebook.name]
setup_cell = render_setup_cell(setup_cell, %{ctx | include_outputs?: false})
setup_cells = render_setup_cells(setup_cells, %{ctx | include_outputs?: false})
sections = Enum.map(notebook.sections, &render_section(&1, notebook, ctx))

metadata = notebook_metadata(notebook)

notebook_with_metadata =
[name, setup_cell | sections]
[name | setup_cells ++ sections]
|> Enum.reject(&is_nil/1)
|> Enum.intersperse("\n\n")
|> prepend_metadata(metadata)
Expand Down Expand Up @@ -175,8 +175,13 @@ defmodule Livebook.LiveMarkdown.Export do
%{"branch_parent_index" => parent_idx}
end

defp render_setup_cell(%{source: ""}, _ctx), do: nil
defp render_setup_cell(cell, ctx), do: render_cell(cell, ctx)
defp render_setup_cells([%{source: ""}], _ctx), do: []

defp render_setup_cells(cells, ctx) do
Enum.map(cells, fn cell ->
render_cell(cell, ctx)
end)
end

defp render_cell(%Cell.Markdown{} = cell, _ctx) do
metadata = cell_metadata(cell)
Expand Down Expand Up @@ -322,7 +327,7 @@ defmodule Livebook.LiveMarkdown.Export do
defp add_markdown_annotation_before_elixir_block(ast) do
Enum.flat_map(ast, fn
{"pre", _, [{"code", [{"class", language}], [_source], %{}}], %{}} = ast_node
when language in ["elixir", "erlang"] ->
when language in ["elixir", "erlang", "python", "pyproject.toml"] ->
[{:comment, [], [~s/livebook:{"force_markdown":true}/], %{comment: true}}, ast_node]

ast_node ->
Expand Down
Loading
Loading