diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 64bf39a460f1..c5e5bac52ee2 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -616,8 +616,8 @@ }, "@@rules_rust+//crate_universe:extension.bzl%crate": { "general": { - "bzlTransitiveDigest": "hCuKz2FMeqFDuYQoH1d3ptMTfAOR4t1WTwnn+cqGoWc=", - "usagesDigest": "jdSuIdTp7Rqi17ioQocHd+ejglFQ3z+AyEWLmlCK/og=", + "bzlTransitiveDigest": "2bm8JGpVfTeP2/OEWLfaiC0BppLkZjpmq45/K90H3Zw=", + "usagesDigest": "hS+FhJH7CqGt4T1cVOYgEsYbEdtnyHf5KbpPm2ZiT0c=", "recordedFileInputs": { "@@rules_rust++rust_host_tools+rust_host_tools//bin/rustc": "b953c69e6f682bf55e39bdd55bdcbd3e1cc0cbdf9dfda24b9c6478ee2c38359b", "@@//app/rust-ffi/Cargo.toml": "b17bf21f56720ffba5031fdf70296acb53ff018adcf17a8cdcec86b46103d1d8", @@ -625,7 +625,7 @@ "@@//lib/rust/parser/debug/fuzz/Cargo.toml": "d93cea0a1b3a5e96ea99d0f57c7a07361b6e3b46afb95dab362e7562d86655d0", "@@//build_tools/macros/lib/Cargo.toml": "9cd827c2abf32278530d4312e45a9ecd15c122cc180289ed1a58a632f46d1911", "@@//lib/rust/parser/doc-parser/Cargo.toml": "5416f179aa138f97123f02b999ab71338f489f6b912f31a17735375c1e2e704a", - "@@//MODULE.bazel": "db1bab27ac7b5124180f2a3b55fba526b152c13f71f07b0eea760ddcaeb19e91", + "@@//MODULE.bazel": "5bba807265158f71fbbf3292698460fa47614eb782f655d1fe6e5ba32c1ceb6e", "@@//build_tools/install/installer/Cargo.toml": "285f8e82a63acb798f9b19307e2f9a16ae7f1cc73bbec40804249cab86ea879e", "@@//lib/rust/parser/debug/Cargo.toml": "c7af0403667534b52690811421790515f98b22d95932cadcd9cc8a3ac79bb06d", "@@//lib/rust/parser/generate-java/Cargo.toml": "c74133c954f6c6ef3b8af72a466a313ca583f7130e7c4c5779c8c352d8d96a65", diff --git a/app/gui/src/project-view/components/MarkdownEditor/__tests__/blockFormatting.test.ts b/app/gui/src/project-view/components/MarkdownEditor/__tests__/blockFormatting.test.ts index 1d418de6a092..4599714d2a47 100644 --- a/app/gui/src/project-view/components/MarkdownEditor/__tests__/blockFormatting.test.ts +++ b/app/gui/src/project-view/components/MarkdownEditor/__tests__/blockFormatting.test.ts @@ -1,6 +1,5 @@ import { printTestInput, setupEditor } from '@/components/MarkdownEditor/__tests__/testInput' import { - canInsertCodeBlock, getBlockType, insertCodeBlock, removeCodeBlock, @@ -387,7 +386,6 @@ test.each([ }, ])('Insert code block: $source', ({ source, expected }) => { const view = setupEditor(source) - expect(canInsertCodeBlock(view.state)).toBe(true) view.dispatch(insertCodeBlock(view.state)) expect(getBlockType(view.state)).toBe('FencedCode') expect(printTestInput(view.state.doc.toString(), view.state.selection.main)).toEqual(expected) diff --git a/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/block.ts b/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/block.ts index 9d92fd2fad65..b4d8c5bfa9fa 100644 --- a/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/block.ts +++ b/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/block.ts @@ -86,15 +86,6 @@ export function setBlockType( } } -/** - * @returns Whether {@link insertCodeBlock} is supported for the current cursor location or - * selected range. - */ -export function canInsertCodeBlock(_state: EditorState): boolean { - // TODO: Disable button when the cursor is already inside an unformattable block. - return true -} - /** * Insert a code block after the cursor, or if there is a selection convert the selected lines to a * code block. diff --git a/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/index.ts b/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/index.ts index 7da68ba8ef82..8e1f1d8b9236 100644 --- a/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/index.ts +++ b/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/index.ts @@ -1,6 +1,5 @@ /** @file Provides a Vue reactive API for Markdown formatting in CodeMirror. */ import { - canInsertCodeBlock, getBlockType, insertCodeBlock, removeCodeBlock, @@ -24,6 +23,7 @@ export { type BlockType } interface ReactiveFormatting { inline: Record> blockType: Ref + unformattable: Ref } const reactiveFormattingFacet = Facet.define({ @@ -47,8 +47,10 @@ export function useMarkdownFormatting(view: EditorView) { insertLink: computed( () => canInsertLink(view.state) && (() => view.dispatch(insertLink(view.state))), ), - insertCodeBlock: computed( - () => canInsertCodeBlock(view.state) && (() => view.dispatch(insertCodeBlock(view.state))), + insertCodeBlock: computed(() => + reactiveFormatting.unformattable.value ? + undefined + : () => view.dispatch(insertCodeBlock(view.state)), ), blockType: proxyRefs({ value: readonly(reactiveFormatting.blockType), @@ -68,13 +70,14 @@ export function useMarkdownFormatting(view: EditorView) { /** Returns an extension that supports reactively watch the formatting of the selected text. */ export function markdownFormatting(): Extension { - const reactiveFormatting = { + const reactiveFormatting: ReactiveFormatting = { inline: { - Emphasis: ref(), - StrongEmphasis: ref(), - Strikethrough: ref(), + Emphasis: ref(), + StrongEmphasis: ref(), + Strikethrough: ref(), }, - blockType: ref(), + blockType: ref(), + unformattable: ref(false), } const reactiveFormattingFacetExt = reactiveFormattingFacet.of(reactiveFormatting) return [ @@ -85,6 +88,7 @@ export function markdownFormatting(): Extension { for (const key of objects.unsafeKeys(reactiveFormatting.inline)) reactiveFormatting.inline[key].value = formatting?.[key] reactiveFormatting.blockType.value = getBlockType(update.view.state) + reactiveFormatting.unformattable.value = formatting === undefined }), ] } diff --git a/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/inline.ts b/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/inline.ts index 8a64d129b28c..c16e4bced3cf 100644 --- a/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/inline.ts +++ b/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/inline.ts @@ -52,9 +52,9 @@ export function setInlineFormatting( changes, // TODO SelectionMapping // `SelectionRange.map` produces a "valid" new selection based on the old selection and the - // changes, but it isn't perfect. Once MDChangeBuilder's selection-adjusting logic is - // consistently better than that sane default, we should switch to it and enable the checks of - // after-edit selection boundaries `inlineFormatting.test.ts`. + // changes, but it isn't perfect. Once MarkdownEdit's selection-adjusting logic is consistently + // better than that sane default, we should switch to it and enable the checks of after-edit + // selection boundaries `inlineFormatting.test.ts`. // selection: rangeToSelection(md.adjustedSelection), selection: state.selection.main.map(changes), } diff --git a/app/lezer-markdown/src/markdown.ts b/app/lezer-markdown/src/markdown.ts index 49883a840221..2c7a72c56631 100644 --- a/app/lezer-markdown/src/markdown.ts +++ b/app/lezer-markdown/src/markdown.ts @@ -217,7 +217,8 @@ function skipForList(bl: CompositeBlock, cx: BlockContext, line: Line) { const DefaultSkipMarkup: {[type: number]: (bl: CompositeBlock, cx: BlockContext, line: Line) => boolean} = { [Type.Blockquote](bl, cx, line) { if (line.next != 62 /* '>' */) return false - let sAfter = space(line.text.charCodeAt(line.pos + 1)), size = sAfter ? 2 : 1 + let sAfter = space(line.text.charCodeAt(line.pos + 1)) + let size = sAfter ? 2 : 1 line.markers.push(elt(Type.QuoteMark, cx.lineStart + line.pos, cx.lineStart + line.pos + (includeSpaceInDelimiterNode ? size : 1))) line.moveBase(line.pos + size) bl.end = cx.lineStart + line.text.length @@ -484,17 +485,15 @@ const DefaultBlockParsers: {[name: string]: ((cx: BlockContext, line: Line) => B ATXHeading(cx, line) { let size = isAtxHeading(line) if (size < 0) return false - let level = size - if (includeSpaceInDelimiterNode && space(line.text.charCodeAt(size))) size += 1 let off = line.pos, from = cx.lineStart + off let endOfSpace = skipSpaceBack(line.text, line.text.length, off), after = endOfSpace while (after > off && line.text.charCodeAt(after - 1) == line.next) after-- if (after == endOfSpace || after == off || !space(line.text.charCodeAt(after - 1))) after = line.text.length let buf = cx.buffer - .write(Type.HeaderMark, 0, size) + .write(Type.HeaderMark, 0, size + (includeSpaceInDelimiterNode && space(line.text.charCodeAt(size)) ? 1 : 0)) .writeElements(cx.parser.parseInline(line.text.slice(off + size + 1, after), from + size + 1), -from) if (after < line.text.length) buf.write(Type.HeaderMark, after - off, endOfSpace - off) - let node = buf.finish(Type.ATXHeading1 - 1 + level, line.text.length - off) + let node = buf.finish(Type.ATXHeading1 - 1 + size, line.text.length - off) cx.nextLine() cx.addNode(node, from) return true diff --git a/app/ydoc-shared/src/ast/__tests__/ensoMarkdown.test.ts b/app/ydoc-shared/src/ast/__tests__/ensoMarkdown.test.ts index b5658493e6dd..4cd281354ced 100644 --- a/app/ydoc-shared/src/ast/__tests__/ensoMarkdown.test.ts +++ b/app/ydoc-shared/src/ast/__tests__/ensoMarkdown.test.ts @@ -104,8 +104,7 @@ test.each([ ], not: ['Document', ['OrderedList', ['ListItem', ['ListMark', '1.'], ['Paragraph', 'Numbered']]]], }, - /* - { // FIXME + { source: '# *Formatted header*', expected: [ 'Document', @@ -116,7 +115,6 @@ test.each([ ], ], }, - */ ])('Syntax extension: Delimiter tokens include syntactic spaces: $source', checkTree) // === "Incomplete" syntax special cases ===