Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions packages/app-bundle/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -918,8 +918,8 @@
"packages/session-ui/src/v2/components/editable-diff-view-core.ts": "f01a341c4c1cdb4a9522f9ccd3c856bd3b9b7310c01d87327b3ce777052c8c59",
"packages/session-ui/src/v2/components/editable-diff-view.test.ts": "ab206fa17779e6e9015d343ae37e3067dfc1821a93b6ba566b7a0dce0fc19acb",
"packages/session-ui/src/v2/components/editable-diff-view.tsx": "f682e398ece864b5b854ca36698581c7e1cc5964eed96e7b45cabfcb64748b24",
"packages/session-ui/src/v2/components/editor-core.test.ts": "bd6552c0e7080afd554cb57ad690cc1504555712274c8b02702c64bb09460234",
"packages/session-ui/src/v2/components/editor-core.ts": "8306fc9b87fffe853c96212664ba5003c4e020c8779fc7d5410c26b543736a7e",
"packages/session-ui/src/v2/components/editor-core.test.ts": "1efdf2860abe85f96b4b0453dd0357d599326cb964e580ea382d4eac30d633d7",
"packages/session-ui/src/v2/components/editor-core.ts": "aa23db589635a0169c9ff190f0682cabab3d76671603e0a2cc52685811347cb4",
"packages/session-ui/src/v2/components/file-name-picker.test.tsx": "1ce9741e315b549165d390c74b5885a43580aa70e24de987f9ba8388cd521b41",
"packages/session-ui/src/v2/components/markdown-utils.test.ts": "10e1034293bf385cf50d860bf1667d8370abc61dcee0c5a93295f21f5f55876a",
"packages/session-ui/src/v2/components/markdown-utils.ts": "3bc47cfed5b99ab23f857d5324be3189f3a7e800d991103d72ef1c2f970b910b",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {
externalUpdate,
detectMode,
} from "./editor-core"
import { EditorView } from "@codemirror/view"
import { EditorView, runScopeHandlers } from "@codemirror/view"
import { EditorState } from "@codemirror/state"
import { foldable, foldedRanges, syntaxTree } from "@codemirror/language"

/**
* Tests for editor-core — the extracted general-purpose CM6 setup functions
Expand Down Expand Up @@ -115,6 +117,71 @@ describe("baseExtensions (via editor-core)", () => {
})
})

// ---------------------------------------------------------------------------
// Code folding (#1517) — fold gutter, Mod-Shift-[ / Mod-Shift-] shortcuts
// ---------------------------------------------------------------------------

describe("baseExtensions code folding (#1517)", () => {
const SRC = "function f() {\n const a = 1\n return a\n}\n"

async function makeView(): Promise<EditorView> {
const language = await loadLanguage("ts")
expect(language).not.toBeNull()
const parent = document.createElement("div")
document.body.appendChild(parent)
const view = new EditorView({
parent,
state: EditorState.create({
doc: SRC,
selection: { anchor: 0 },
extensions: baseExtensions({ theme: buildThemeExtension("dark"), language }),
}),
})
// Make sure the lezer tree is fully parsed so fold ranges are available.
syntaxTree(view.state)
return view
}

function keyEvent(init: KeyboardEventInit): KeyboardEvent {
return new KeyboardEvent("keydown", { bubbles: true, cancelable: true, ...init })
}

test("renders a fold gutter next to the line numbers", async () => {
const view = await makeView()
expect(view.dom.querySelector(".cm-foldGutter")).not.toBeNull()
expect(view.dom.querySelector(".cm-lineNumbers")).not.toBeNull()
view.destroy()
})

test("the lezer grammar reports a foldable region on the function line", async () => {
const view = await makeView()
const line = view.state.doc.line(1)
expect(foldable(view.state, line.from, line.to)).not.toBeNull()
view.destroy()
})

test("Mod-Shift-[ folds the region at the cursor and Mod-Shift-] unfolds it", async () => {
const view = await makeView()
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.platform)
const mod = isMac ? { metaKey: true } : { ctrlKey: true }
const count = () => {
let n = 0
foldedRanges(view.state).between(0, view.state.doc.length, () => { n++ })
return n
}
expect(count()).toBe(0)

const fold = keyEvent({ key: "{", code: "BracketLeft", keyCode: 219, shiftKey: true, ...mod })
expect(runScopeHandlers(view, fold, "editor")).toBe(true)
expect(count()).toBe(1)

const unfold = keyEvent({ key: "}", code: "BracketRight", keyCode: 221, shiftKey: true, ...mod })
expect(runScopeHandlers(view, unfold, "editor")).toBe(true)
expect(count()).toBe(0)
view.destroy()
})
})

// ---------------------------------------------------------------------------
// editableExtensions — readOnly/onChange contract preserved
// ---------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ import {
} from "@codemirror/view"
import { history, defaultKeymap, historyKeymap } from "@codemirror/commands"
import { search, searchKeymap, highlightSelectionMatches } from "@codemirror/search"
import { type LanguageSupport, bracketMatching } from "@codemirror/language"
import {
type LanguageSupport,
bracketMatching,
foldGutter,
foldKeymap,
foldCode,
unfoldCode,
} from "@codemirror/language"
import {
HighlightStyle,
syntaxHighlighting,
Expand Down Expand Up @@ -189,6 +196,14 @@ export function buildThemeExtension(mode: "light" | "dark"): Extension {
".cm-activeLine": {
backgroundColor: "color-mix(in srgb, var(--v2-background-bg-layer-01, var(--background-weak)) 50%, transparent)",
},
".cm-foldGutter .cm-gutterElement": {
cursor: "pointer",
padding: "0 4px",
color: "var(--v2-text-text-muted, var(--text-weak))",
},
".cm-foldGutter .cm-gutterElement:hover": {
color: "var(--v2-text-text-base, var(--text-strong))",
},
".cm-foldPlaceholder": {
backgroundColor: "transparent",
border: "none",
Expand Down Expand Up @@ -296,6 +311,16 @@ export function editableExtensions(opts: {
return exts
}

/**
* Cmd/Ctrl+Shift+[ folds the region at the cursor, Cmd/Ctrl+Shift+] unfolds it
* (#1517). `foldKeymap` binds these to Ctrl-Shift on non-mac but to Cmd-Alt on
* mac, so the mac chord is added explicitly; `foldKeymap` stays for fold-all.
*/
const foldShortcutKeymap = [
{ key: "Mod-Shift-[", run: foldCode },
{ key: "Mod-Shift-]", run: unfoldCode },
]

export function baseExtensions(opts: {
theme: Extension
language?: LanguageSupport | null
Expand All @@ -304,11 +329,14 @@ export function baseExtensions(opts: {
}): Extension[] {
return [
lineNumbers(),
// Syntax-aware code folding (#1517): chevrons in the gutter next to the
// line numbers; fold regions come from each language's lezer grammar.
foldGutter(),
highlightActiveLine(),
highlightSpecialChars(),
drawSelection(),
bracketMatching(),
keymap.of([...defaultKeymap, ...historyKeymap, ...searchKeymap]),
keymap.of([...foldShortcutKeymap, ...defaultKeymap, ...historyKeymap, ...searchKeymap, ...foldKeymap]),
search(),
highlightSelectionMatches(),
EditorView.lineWrapping,
Expand Down
Loading