Commit 265b9d6362
Verified · cmc
Layout: unified · split
Sources/OrgApp/EmacsImport.swift +2 −1
| @@ -448,7 +448,8 @@ public struct EmacsImport: Sendable { | |||
| 448 | guard let n = value.integer else { return note("not a number") } | 448 | guard let n = value.integer else { return note("not a number") } |
| 449 | add(name, .integer(n)) | 449 | add(name, .integer(n)) |
| 450 | case "org-insert-heading-respect-content", "org-list-allow-alphabetical", "org-hide-emphasis-markers", "org-pretty-entities", | 450 | case "org-insert-heading-respect-content", "org-list-allow-alphabetical", "org-hide-emphasis-markers", "org-pretty-entities", |
| 451 | "org-cycle-hide-drawer-startup", "org-cycle-hide-block-startup", "org-use-speed-commands", "org-startup-with-inline-images": | 451 | "org-cycle-hide-drawer-startup", "org-cycle-hide-block-startup", "org-use-speed-commands", "org-startup-with-inline-images", |
| 452 | "org-startup-indented", "org-hide-leading-stars", "org-startup-align-all-tables": | ||
| 452 | add(name, .bool(truthy)) | 453 | add(name, .bool(truthy)) |
| 453 | case "org-hide-drawer-startup", "org-hide-block-startup": | 454 | case "org-hide-drawer-startup", "org-hide-block-startup": |
| 454 | add(name == "org-hide-drawer-startup" ? "org-cycle-hide-drawer-startup" : "org-cycle-hide-block-startup", .bool(truthy)) | 455 | add(name == "org-hide-drawer-startup" ? "org-cycle-hide-drawer-startup" : "org-cycle-hide-block-startup", .bool(truthy)) |
Sources/OrgApp/UserConfig.swift +6
| @@ -82,6 +82,12 @@ public enum UserConfig { | |||
| 82 | fallback: .string("nil"), comment: "nil, time or note: log changing or removing a DEADLINE", legacy: ""), | 82 | fallback: .string("nil"), comment: "nil, time or note: log changing or removing a DEADLINE", legacy: ""), |
| 83 | Setting(section: "", key: "org-log-into-drawer", defaultsKey: "logIntoDrawer", kind: .string, fallback: .string(""), | 83 | Setting(section: "", key: "org-log-into-drawer", defaultsKey: "logIntoDrawer", kind: .string, fallback: .string(""), |
| 84 | comment: "the drawer state notes go in (\"LOGBOOK\" is Emacs's t); \"\" for none", legacy: ""), | 84 | comment: "the drawer state notes go in (\"LOGBOOK\" is Emacs's t); \"\" for none", legacy: ""), |
| 85 | Setting(section: "", key: "org-startup-indented", defaultsKey: "startupIndented", kind: .bool, fallback: .bool(true), | ||
| 86 | comment: "indent bodies under their headings (org-indent-mode); #+STARTUP: (no)indent overrides", legacy: ""), | ||
| 87 | Setting(section: "", key: "org-hide-leading-stars", defaultsKey: "hideLeadingStars", kind: .bool, fallback: .bool(false), | ||
| 88 | comment: "show only a heading's last star without indentation; #+STARTUP: hidestars/showstars overrides", legacy: ""), | ||
| 89 | Setting(section: "", key: "org-startup-align-all-tables", defaultsKey: "startupAlignAllTables", kind: .bool, fallback: .bool(false), | ||
| 90 | comment: "align every table when a file opens; #+STARTUP: (no)align overrides", legacy: ""), | ||
| 85 | Setting(section: "", key: "org-startup-with-inline-images", defaultsKey: "startupWithInlineImages", kind: .bool, fallback: .bool(false), | 91 | Setting(section: "", key: "org-startup-with-inline-images", defaultsKey: "startupWithInlineImages", kind: .bool, fallback: .bool(false), |
| 86 | comment: "show image links as images when a file opens; #+STARTUP: (no)inlineimages overrides", legacy: ""), | 92 | comment: "show image links as images when a file opens; #+STARTUP: (no)inlineimages overrides", legacy: ""), |
| 87 | Setting(section: "", key: "org-use-speed-commands", defaultsKey: "useSpeedCommands", kind: .bool, fallback: .bool(false), | 93 | Setting(section: "", key: "org-use-speed-commands", defaultsKey: "useSpeedCommands", kind: .bool, fallback: .bool(false), |
Sources/OrgCore/Commands/Command.swift +11 −1
| @@ -26,6 +26,12 @@ public struct EditingOptions: Sendable, Equatable, Codable { | |||
| 26 | public var useSpeedCommands: Bool | 26 | public var useSpeedCommands: Bool |
| 27 | /// `org-startup-with-inline-images`. | 27 | /// `org-startup-with-inline-images`. |
| 28 | public var startupWithInlineImages: Bool | 28 | public var startupWithInlineImages: Bool |
| 29 | /// `org-startup-indented`: org-indent's virtual indentation. | ||
| 30 | public var startupIndented = true | ||
| 31 | /// `org-hide-leading-stars`; org-indent hides them too. | ||
| 32 | public var hideLeadingStars = false | ||
| 33 | /// `org-startup-align-all-tables`. | ||
| 34 | public var startupAlignAllTables = false | ||
| 29 | /// `org-cycle-hide-drawer-startup`: drawers are folded when a file opens. | 35 | /// `org-cycle-hide-drawer-startup`: drawers are folded when a file opens. |
| 30 | public var hideDrawerStartup: Bool | 36 | public var hideDrawerStartup: Bool |
| 31 | /// `org-cycle-hide-block-startup`: blocks are folded when a file opens. | 37 | /// `org-cycle-hide-block-startup`: blocks are folded when a file opens. |
| @@ -45,8 +51,12 @@ public struct EditingOptions: Sendable, Equatable, Codable { | |||
| 45 | hideEmphasisMarkers: Bool = false, prettyEntities: Bool = false, subSuperscriptsNeedBraces: Bool = false, | 51 | hideEmphasisMarkers: Bool = false, prettyEntities: Bool = false, subSuperscriptsNeedBraces: Bool = false, |
| 46 | logDone: LogHow? = nil, logIntoDrawer: String? = nil, fastTodoSelection: Bool = true, | 52 | logDone: LogHow? = nil, logIntoDrawer: String? = nil, fastTodoSelection: Bool = true, |
| 47 | hideDrawerStartup: Bool = true, hideBlockStartup: Bool = false, logReschedule: LogHow? = nil, logRedeadline: LogHow? = nil, | 53 | hideDrawerStartup: Bool = true, hideBlockStartup: Bool = false, logReschedule: LogHow? = nil, logRedeadline: LogHow? = nil, |
| 48 | useSpeedCommands: Bool = false, startupWithInlineImages: Bool = false | 54 | useSpeedCommands: Bool = false, startupWithInlineImages: Bool = false, |
| 55 | startupIndented: Bool = true, hideLeadingStars: Bool = false, startupAlignAllTables: Bool = false | ||
| 49 | ) { | 56 | ) { |
| 57 | self.startupIndented = startupIndented | ||
| 58 | self.hideLeadingStars = hideLeadingStars | ||
| 59 | self.startupAlignAllTables = startupAlignAllTables | ||
| 50 | self.useSpeedCommands = useSpeedCommands | 60 | self.useSpeedCommands = useSpeedCommands |
| 51 | self.startupWithInlineImages = startupWithInlineImages | 61 | self.startupWithInlineImages = startupWithInlineImages |
| 52 | self.logReschedule = logReschedule | 62 | self.logReschedule = logReschedule |
Sources/OrgCore/Commands/Table.swift +25
| @@ -620,6 +620,31 @@ public struct TableCommand: OrgCommand { | |||
| 620 | } | 620 | } |
| 621 | } | 621 | } |
| 622 | 622 | ||
| 623 | /// `#+STARTUP: align` (`org-startup-align-all-tables`): every table aligned. | ||
| 624 | public struct AlignAllTables: OrgCommand { | ||
| 625 | public init() {} | ||
| 626 | public var id: String { "org.table.align-all" } | ||
| 627 | public var title: String { "Align All Tables" } | ||
| 628 | public func applies(in context: EditContext) -> Bool { true } | ||
| 629 | public func run(in context: EditContext) -> CommandStep { | ||
| 630 | let starts = context.tree.root.descendants().filter { $0.kind == .table }.map(\.range.lowerBound) | ||
| 631 | guard !starts.isEmpty else { return .commit(EditResult(baseRevision: context.revision, edits: [])) } | ||
| 632 | return commitBuffer(context) { buffer in | ||
| 633 | let caret = buffer.marker(buffer.point) | ||
| 634 | defer { | ||
| 635 | buffer.point = caret.position | ||
| 636 | buffer.release(caret) | ||
| 637 | } | ||
| 638 | for start in starts.reversed() { | ||
| 639 | buffer.point = start | ||
| 640 | buffer.skipForward([32, 9]) | ||
| 641 | buffer.point += 1 | ||
| 642 | buffer.tableAlign() | ||
| 643 | } | ||
| 644 | } | ||
| 645 | } | ||
| 646 | } | ||
| 647 | |||
| 623 | /// C-c |: `org-table-create` with a size prompt. | 648 | /// C-c |: `org-table-create` with a size prompt. |
| 624 | public struct CreateTable: OrgCommand { | 649 | public struct CreateTable: OrgCommand { |
| 625 | public init() {} | 650 | public init() {} |
Sources/OrgEditorAppKit/OrgEditor.swift +20 −1
| @@ -146,12 +146,23 @@ public final class OrgEditor: NSObject { | |||
| 146 | // Files open at their start, as in Emacs; replacing the storage leaves it at the end. | 146 | // Files open at their start, as in Emacs; replacing the storage leaves it at the end. |
| 147 | textView.setSelectedRange(NSRange(location: 0, length: 0)) | 147 | textView.setSelectedRange(NSRange(location: 0, length: 0)) |
| 148 | if isOrg { | 148 | if isOrg { |
| 149 | let words = Presentation.startupOptions(document.tree).map { $0.lowercased() } | ||
| 150 | indented = words.contains("indent") ? true : words.contains("noindent") ? false : options.startupIndented | ||
| 151 | hideStars = words.contains("hidestars") ? true : words.contains("showstars") ? false : options.hideLeadingStars | ||
| 149 | var startup = Presentation.startupFoldState(document.tree) ?? FoldState() | 152 | var startup = Presentation.startupFoldState(document.tree) ?? FoldState() |
| 153 | // VISIBILITY properties and archived trees, unless everything shows. | ||
| 154 | if Presentation.startupFoldState(document.tree) != nil, !words.contains("showeverything") { | ||
| 155 | startup = Presentation.applyVisibilityProperties(startup, in: document.tree) | ||
| 156 | } | ||
| 150 | let wrappers = Presentation.startupWrappers(document.tree, hideDrawers: options.hideDrawerStartup, hideBlocks: options.hideBlockStartup) | 157 | let wrappers = Presentation.startupWrappers(document.tree, hideDrawers: options.hideDrawerStartup, hideBlocks: options.hideBlockStartup) |
| 151 | startup.wrapperFolds = Set(wrappers.map(\.start)) | 158 | startup.wrapperFolds = Set(wrappers.map(\.start)) |
| 152 | if startup != FoldState() { setFolds(startup, wrappers: wrappers) } | 159 | if startup != FoldState() { setFolds(startup, wrappers: wrappers) } |
| 153 | shrinkAtStartup() | 160 | shrinkAtStartup() |
| 154 | if InlineImages.startup(document.tree) ?? options.startupWithInlineImages { showsImages = true } | 161 | if InlineImages.startup(document.tree) ?? options.startupWithInlineImages { showsImages = true } |
| 162 | if words.contains("align") || (options.startupAlignAllTables && !words.contains("noalign")) { | ||
| 163 | _ = perform(AlignAllTables()) | ||
| 164 | } | ||
| 165 | if !indented || !hideStars { restyleOutsideEditing(0..<utf16Length) } | ||
| 155 | } | 166 | } |
| 156 | } | 167 | } |
| 157 | 168 | ||
| @@ -610,6 +621,13 @@ public final class OrgEditor: NSObject { | |||
| 610 | } | 621 | } |
| 611 | } | 622 | } |
| 612 | 623 | ||
| 624 | // MARK: - STARTUP display | ||
| 625 | |||
| 626 | /// `org-indent-mode`: headings' bodies indented under their titles (`#+STARTUP: indent`). | ||
| 627 | private var indented = true | ||
| 628 | /// `org-hide-leading-stars` (`#+STARTUP: hidestars`); org-indent hides them as well. | ||
| 629 | private var hideStars = false | ||
| 630 | |||
| 613 | // MARK: - Inline images | 631 | // MARK: - Inline images |
| 614 | 632 | ||
| 615 | /// `org-toggle-inline-images`: lines that are only an image link show the image. | 633 | /// `org-toggle-inline-images`: lines that are only an image link show the image. |
| @@ -938,7 +956,7 @@ public final class OrgEditor: NSObject { | |||
| 938 | return | 956 | return |
| 939 | } | 957 | } |
| 940 | let text = storage.string as NSString | 958 | let text = storage.string as NSString |
| 941 | for run in Presentation.indents(document.tree, in: range) { | 959 | for run in Presentation.indents(document.tree, in: range) where indented { |
| 942 | guard let clipped = clip(run.range, to: range) else { continue } | 960 | guard let clipped = clip(run.range, to: range) else { continue } |
| 943 | guard run.firstLine == run.wrapped else { | 961 | guard run.firstLine == run.wrapped else { |
| 944 | storage.addAttribute(.paragraphStyle, value: paragraphStyle(run.firstLine, run.wrapped), range: clipped) | 962 | storage.addAttribute(.paragraphStyle, value: paragraphStyle(run.firstLine, run.wrapped), range: clipped) |
| @@ -959,6 +977,7 @@ public final class OrgEditor: NSObject { | |||
| 959 | var scriptExclusions: [Range<Int>] = [] | 977 | var scriptExclusions: [Range<Int>] = [] |
| 960 | for run in Presentation.styles(document.tree, in: range, highlighter: highlighter) { | 978 | for run in Presentation.styles(document.tree, in: range, highlighter: highlighter) { |
| 961 | guard let clipped = clip(run.range, to: range) else { continue } | 979 | guard let clipped = clip(run.range, to: range) else { continue } |
| 980 | if run.role == .leadingStars, !indented, !hideStars { continue } | ||
| 962 | if !showMarkup, hides(run.role), NSIntersectionRange(clipped, revealed).length == 0 { | 981 | if !showMarkup, hides(run.role), NSIntersectionRange(clipped, revealed).length == 0 { |
| 963 | storage.addAttributes(theme.hidden, range: clipped) | 982 | storage.addAttributes(theme.hidden, range: clipped) |
| 964 | } else { | 983 | } else { |
Sources/OrgPresentation/Folding.swift +36
| @@ -153,6 +153,42 @@ extension Presentation { | |||
| 153 | } | 153 | } |
| 154 | } | 154 | } |
| 155 | 155 | ||
| 156 | /// `org-cycle-set-visibility-according-to-property` and `org-cycle-hide-archived-subtrees` | ||
| 157 | /// after the startup visibility: each heading's `VISIBILITY` (`folded`, `children`, `content`, | ||
| 158 | /// `all`) in turn, then ARCHIVE-tagged subtrees folded. | ||
| 159 | public static func applyVisibilityProperties(_ state: FoldState, in tree: OrgTree) -> FoldState { | ||
| 160 | let outline = outline(tree) | ||
| 161 | let model = DocumentModel(tree: tree) | ||
| 162 | var next = state | ||
| 163 | for (index, heading) in model.headings.enumerated() { | ||
| 164 | guard index < outline.count, | ||
| 165 | let value = heading.properties.last(where: { $0.key.uppercased() == "VISIBILITY" })?.value.lowercased() else { continue } | ||
| 166 | let entry = outline[index] | ||
| 167 | let descendants = outline.filter { $0.start > entry.start && $0.start < entry.end }.map(\.start) | ||
| 168 | let children = outline.enumerated().filter { $0.element.parent == index }.map(\.element.start) | ||
| 169 | switch value { | ||
| 170 | case "folded": | ||
| 171 | next.folds.insert(entry.start) | ||
| 172 | case "children": | ||
| 173 | next.folds.remove(entry.start) | ||
| 174 | next.bodyFolds.remove(entry.start) | ||
| 175 | next.folds.formUnion(children) | ||
| 176 | case "content": | ||
| 177 | next.folds.subtract([entry.start] + descendants) | ||
| 178 | next.bodyFolds.formUnion([entry.start] + descendants) | ||
| 179 | case "all", "showall": | ||
| 180 | next.folds.subtract([entry.start] + descendants) | ||
| 181 | next.bodyFolds.subtract([entry.start] + descendants) | ||
| 182 | default: | ||
| 183 | break | ||
| 184 | } | ||
| 185 | } | ||
| 186 | for (index, heading) in model.headings.enumerated() where index < outline.count && heading.localTags.contains("ARCHIVE") { | ||
| 187 | next.folds.insert(outline[index].start) | ||
| 188 | } | ||
| 189 | return next | ||
| 190 | } | ||
| 191 | |||
| 156 | /// Headings of levels 1 through `levels` visible, no bodies. | 192 | /// Headings of levels 1 through `levels` visible, no bodies. |
| 157 | public static func foldState(showingLevels levels: Int, in tree: OrgTree) -> FoldState { | 193 | public static func foldState(showingLevels levels: Int, in tree: OrgTree) -> FoldState { |
| 158 | let outline = outline(tree) | 194 | let outline = outline(tree) |
Sources/Orgstar/ContentView.swift +5 −1
| @@ -40,6 +40,9 @@ struct ContentView: View { | |||
| 40 | @AppStorage("logRedeadline") private var logRedeadline = "nil" | 40 | @AppStorage("logRedeadline") private var logRedeadline = "nil" |
| 41 | @AppStorage("useSpeedCommands") private var useSpeedCommands = false | 41 | @AppStorage("useSpeedCommands") private var useSpeedCommands = false |
| 42 | @AppStorage("startupWithInlineImages") private var startupWithInlineImages = false | 42 | @AppStorage("startupWithInlineImages") private var startupWithInlineImages = false |
| 43 | @AppStorage("startupIndented") private var startupIndented = true | ||
| 44 | @AppStorage("hideLeadingStars") private var hideLeadingStars = false | ||
| 45 | @AppStorage("startupAlignAllTables") private var startupAlignAllTables = false | ||
| 43 | @AppStorage("hideDrawerStartup") private var hideDrawerStartup = true | 46 | @AppStorage("hideDrawerStartup") private var hideDrawerStartup = true |
| 44 | @AppStorage("hideBlockStartup") private var hideBlockStartup = false | 47 | @AppStorage("hideBlockStartup") private var hideBlockStartup = false |
| 45 | 48 | ||
| @@ -48,7 +51,8 @@ struct ContentView: View { | |||
| 48 | tagsColumn: tagsColumn, headingAfterSubtree: headingAfterSubtree, splitLine: splitLine, alphabeticalLists: alphabeticalLists, | 51 | tagsColumn: tagsColumn, headingAfterSubtree: headingAfterSubtree, splitLine: splitLine, alphabeticalLists: alphabeticalLists, |
| 49 | fillColumn: fillColumn, hiddenMarkers: hiddenMarkers, prettyEntities: prettyEntities, logDone: logDone, logIntoDrawer: logIntoDrawer, | 52 | fillColumn: fillColumn, hiddenMarkers: hiddenMarkers, prettyEntities: prettyEntities, logDone: logDone, logIntoDrawer: logIntoDrawer, |
| 50 | hideDrawerStartup: hideDrawerStartup, hideBlockStartup: hideBlockStartup, logReschedule: logReschedule, logRedeadline: logRedeadline, | 53 | hideDrawerStartup: hideDrawerStartup, hideBlockStartup: hideBlockStartup, logReschedule: logReschedule, logRedeadline: logRedeadline, |
| 51 | useSpeedCommands: useSpeedCommands, startupWithInlineImages: startupWithInlineImages | 54 | useSpeedCommands: useSpeedCommands, startupWithInlineImages: startupWithInlineImages, |
| 55 | startupIndented: startupIndented, hideLeadingStars: hideLeadingStars, startupAlignAllTables: startupAlignAllTables | ||
| 52 | ) | 56 | ) |
| 53 | } | 57 | } |
| 54 | /// A block being edited apart (`org-edit-special`). | 58 | /// A block being edited apart (`org-edit-special`). |
Sources/Orgstar/Preferences.swift +4 −2
| @@ -20,7 +20,8 @@ enum Preferences { | |||
| 20 | static func options( | 20 | static func options( |
| 21 | tagsColumn: Int, headingAfterSubtree: Bool, splitLine: Bool, alphabeticalLists: Bool, fillColumn: Int, | 21 | tagsColumn: Int, headingAfterSubtree: Bool, splitLine: Bool, alphabeticalLists: Bool, fillColumn: Int, |
| 22 | hiddenMarkers: Bool, prettyEntities: Bool, logDone: String, logIntoDrawer: String, | 22 | hiddenMarkers: Bool, prettyEntities: Bool, logDone: String, logIntoDrawer: String, |
| 23 | hideDrawerStartup: Bool, hideBlockStartup: Bool, logReschedule: String, logRedeadline: String, useSpeedCommands: Bool, startupWithInlineImages: Bool | 23 | hideDrawerStartup: Bool, hideBlockStartup: Bool, logReschedule: String, logRedeadline: String, useSpeedCommands: Bool, startupWithInlineImages: Bool, |
| 24 | startupIndented: Bool, hideLeadingStars: Bool, startupAlignAllTables: Bool | ||
| 24 | ) -> EditingOptions { | 25 | ) -> EditingOptions { |
| 25 | EditingOptions( | 26 | EditingOptions( |
| 26 | tagsColumn: tagsColumn, insertHeadingRespectContent: headingAfterSubtree, | 27 | tagsColumn: tagsColumn, insertHeadingRespectContent: headingAfterSubtree, |
| @@ -29,7 +30,8 @@ enum Preferences { | |||
| 29 | logDone: LogHow(rawValue: logDone), logIntoDrawer: logIntoDrawer.isEmpty ? nil : logIntoDrawer, | 30 | logDone: LogHow(rawValue: logDone), logIntoDrawer: logIntoDrawer.isEmpty ? nil : logIntoDrawer, |
| 30 | hideDrawerStartup: hideDrawerStartup, hideBlockStartup: hideBlockStartup, | 31 | hideDrawerStartup: hideDrawerStartup, hideBlockStartup: hideBlockStartup, |
| 31 | logReschedule: LogHow(rawValue: logReschedule), logRedeadline: LogHow(rawValue: logRedeadline), | 32 | logReschedule: LogHow(rawValue: logReschedule), logRedeadline: LogHow(rawValue: logRedeadline), |
| 32 | useSpeedCommands: useSpeedCommands, startupWithInlineImages: startupWithInlineImages | 33 | useSpeedCommands: useSpeedCommands, startupWithInlineImages: startupWithInlineImages, |
| 34 | startupIndented: startupIndented, hideLeadingStars: hideLeadingStars, startupAlignAllTables: startupAlignAllTables | ||
| 33 | ) | 35 | ) |
| 34 | } | 36 | } |
| 35 | } | 37 | } |
Tests/OrgPresentationTests/VisibilityPropertyTests.swift added +30
| @@ -0,0 +1,30 @@ | |||
| 1 | import Foundation | ||
| 2 | import Testing | ||
| 3 | import OrgCore | ||
| 4 | @testable import OrgPresentation | ||
| 5 | |||
| 6 | struct VisibilityPropertyTests { | ||
| 7 | static let text = "#+STARTUP: overview\n* A\n:PROPERTIES:\n:VISIBILITY: children\n:END:\nbody a\n** A1\na1 body\n*** A11\n** A2\n* B\n:PROPERTIES:\n:VISIBILITY: content\n:END:\nbody b\n** B1\nb1 body\n*** B11\nb11\n* C\n:PROPERTIES:\n:VISIBILITY: all\n:END:\nbody c\n** C1\nc1\n* D :ARCHIVE:\n** D1\n* E\n:PROPERTIES:\n:VISIBILITY: folded\n:END:\n** E1\n" | ||
| 8 | |||
| 9 | /// Line visibility after opening the file, as Emacs 31.1 / Org 9.8.7 shows it, except that | ||
| 10 | /// Emacs leaves B's `:PROPERTIES:` line (line 12) showing under `content`. | ||
| 11 | static let emacs = "111001100110000101011001111010000" | ||
| 12 | |||
| 13 | @Test func foldsByVisibilityProperties() { | ||
| 14 | let tree = OrgParser.parse(Self.text) | ||
| 15 | var state = Presentation.applyVisibilityProperties(Presentation.startupFoldState(tree)!, in: tree) | ||
| 16 | let wrappers = Presentation.startupWrappers(tree) | ||
| 17 | state.wrapperFolds = Set(wrappers.map(\.start)) | ||
| 18 | let hidden = Presentation.hiddenRanges(tree, folds: state.folds, bodyFolds: state.bodyFolds, wrapperRanges: wrappers.map(\.hidden)) | ||
| 19 | let ns = Self.text as NSString | ||
| 20 | var visibility = "" | ||
| 21 | var offset = 0 | ||
| 22 | while offset < ns.length { | ||
| 23 | visibility += hidden.contains { $0.contains(offset) } ? "0" : "1" | ||
| 24 | offset = NSMaxRange(ns.lineRange(for: NSRange(location: offset, length: 0))) | ||
| 25 | } | ||
| 26 | var expected = Array(Self.emacs) | ||
| 27 | expected[11] = "0" | ||
| 28 | #expect(visibility == String(expected)) | ||
| 29 | } | ||
| 30 | } | ||