| @@ -180,7 +180,7 @@ public final class OrgEditor: NSObject { |
| 180 | indented = startup.indented |
180 | indented = startup.indented |
| 181 | hideStars = startup.hideStars |
181 | hideStars = startup.hideStars |
| 182 | if startup.folds != FoldState() { setFolds(startup.folds, wrappers: startup.wrappers) } |
182 | if startup.folds != FoldState() { setFolds(startup.folds, wrappers: startup.wrappers) } |
| 183 | shrinkAtStartup() |
183 | shrunk = ShrunkColumns.startup(document.tree) |
| 184 | if startup.inlineImages { showsImages = true } |
184 | if startup.inlineImages { showsImages = true } |
| 185 | if startup.alignTables { _ = perform(AlignAllTables()) } |
185 | if startup.alignTables { _ = perform(AlignAllTables()) } |
| 186 | if !indented || !hideStars { restyleOutsideEditing(0..<utf16Length) } |
186 | if !indented || !hideStars { restyleOutsideEditing(0..<utf16Length) } |
| @@ -812,18 +812,13 @@ public final class OrgEditor: NSObject { |
| 812 | |
812 | |
| 813 | // MARK: - Shrunk table columns |
813 | // MARK: - Shrunk table columns |
| 814 | |
814 | |
| 815 | /// Narrowed table columns (`org-table-shrink`): by table start, each column's segments. |
815 | /// Narrowed table columns (`org-table-shrink`). |
| 816 | /// Recomputed as the text changes; typing into one widens its column again, as editing |
816 | private var shrunk = ShrunkColumns() |
| 817 | /// org's overlays deletes them. Commands' edits keep columns narrow, as `org-table-align` |
| |
| 818 | /// shrinks them again. |
| |
| 819 | private var shrunk: [Int: [Int: [TableShrink.Segment]]] = [:] |
| |
| 820 | private var commandEditing = false |
817 | private var commandEditing = false |
| 821 | |
818 | |
| 822 | /// Narrows `columns` of `table` and widens its others. |
819 | /// Narrows `columns` of `table` and widens its others. |
| 823 | private func setShrunk(_ columns: [Int], in table: Range<Int>) { |
820 | private func setShrunk(_ columns: [Int], in table: Range<Int>) { |
| 824 | let text = textView.string |
821 | shrunk.set(columns, in: table, text: textView.string) |
| 825 | let segments = Dictionary(uniqueKeysWithValues: columns.map { ($0, TableShrink.segments(table, column: $0, in: text)) }) |
| |
| 826 | shrunk[table.lowerBound] = segments.isEmpty ? nil : segments |
| |
| 827 | restyleOutsideEditing(table) |
822 | restyleOutsideEditing(table) |
| 828 | textLayoutManager.invalidateLayout(for: textLayoutManager.documentRange) |
823 | textLayoutManager.invalidateLayout(for: textLayoutManager.documentRange) |
| 829 | textView.needsDisplay = true |
824 | textView.needsDisplay = true |
| @@ -832,73 +827,22 @@ public final class OrgEditor: NSObject { |
| 832 | /// C-c TAB in a table: `org-table-toggle-column-width`; `shrink` for `org-table-shrink`, |
827 | /// C-c TAB in a table: `org-table-toggle-column-width`; `shrink` for `org-table-shrink`, |
| 833 | /// `expand` for `org-table-expand`. |
828 | /// `expand` for `org-table-expand`. |
| 834 | private func tableColumnWidth(_ id: String) { |
829 | private func tableColumnWidth(_ id: String) { |
| 835 | let caret = textView.selectedRange().location |
830 | switch shrunk.change(for: id, at: textView.selectedRange().location, in: textView.string) { |
| 836 | let text = textView.string |
831 | case .notInTable: onMessage?("Not in a table") |
| 837 | guard let table = TableShrink.table(at: caret, in: text) else { |
832 | case .set(let columns, let table): setShrunk(columns, in: table) |
| 838 | onMessage?("Not in a table") |
833 | case .ask(let table, let current, let max): |
| 839 | return |
| |
| 840 | } |
| |
| 841 | let current = shrunk[table.lowerBound].map { Array($0.keys) } ?? [] |
| |
| 842 | switch id { |
| |
| 843 | case "org.table.shrink": setShrunk(TableShrink.cookieColumns(table, in: text), in: table) |
| |
| 844 | case "org.table.expand": setShrunk([], in: table) |
| |
| 845 | default: |
| |
| 846 | if let column = TableShrink.column(at: caret, in: text) { |
| |
| 847 | setShrunk(Array(Set(current).symmetricDifference([column])), in: table) |
| |
| 848 | return |
| |
| 849 | } |
| |
| 850 | let line = (text as NSString).lineRange(for: NSRange(location: caret, length: 0)) |
| |
| 851 | onPrompt?(Prompt(key: "columns", message: "Column ranges (e.g. 2-4 6-):")) { [weak self] answer in |
834 | onPrompt?(Prompt(key: "columns", message: "Column ranges (e.g. 2-4 6-):")) { [weak self] answer in |
| 852 | guard let self, let answer else { return } |
835 | guard let self, let answer else { return } |
| 853 | self.setShrunk(Array(Set(current).symmetricDifference(TableShrink.columns(answer, max: line.length / 2))), in: table) |
836 | self.setShrunk(ShrunkColumns.toggled(current, TableShrink.columns(answer, max: max)), in: table) |
| 854 | } |
837 | } |
| 855 | } |
838 | } |
| 856 | } |
839 | } |
| 857 | |
840 | |
| 858 | /// `#+STARTUP: shrink`: every table's columns with width cookies start narrow. |
| |
| 859 | private func shrinkAtStartup() { |
| |
| 860 | guard Presentation.startupOptions(document.tree).contains(where: { $0.lowercased() == "shrink" }) else { return } |
| |
| 861 | let text = textView.string |
| |
| 862 | var offset = 0 |
| |
| 863 | let ns = text as NSString |
| |
| 864 | while offset < ns.length { |
| |
| 865 | let line = ns.lineRange(for: NSRange(location: offset, length: 0)) |
| |
| 866 | defer { offset = max(NSMaxRange(line), offset + 1) } |
| |
| 867 | guard ns.substring(with: line).range(of: "^[ \\t]*\\|", options: .regularExpression) != nil, |
| |
| 868 | let table = TableShrink.table(at: line.location, in: text) else { continue } |
| |
| 869 | let columns = TableShrink.cookieColumns(table, in: text) |
| |
| 870 | if !columns.isEmpty { shrunk[table.lowerBound] = Dictionary(uniqueKeysWithValues: columns.map { ($0, TableShrink.segments(table, column: $0, in: text)) }) } |
| |
| 871 | offset = table.upperBound |
| |
| 872 | } |
| |
| 873 | } |
| |
| 874 | |
| |
| 875 | /// Moves shrunk columns through `edit`; returns the tables to restyle. |
| |
| 876 | private func mapShrunk(through edit: TextEdit) -> [Range<Int>] { |
| |
| 877 | guard !shrunk.isEmpty else { return [] } |
| |
| 878 | let text = textView.string |
| |
| 879 | let delta = edit.replacement.utf16.count - edit.range.count |
| |
| 880 | var mapped: [Int: [Int: [TableShrink.Segment]]] = [:] |
| |
| 881 | var tables: [Range<Int>] = [] |
| |
| 882 | for (start, columns) in shrunk { |
| |
| 883 | let kept = columns.filter { _, segments in |
| |
| 884 | commandEditing || !segments.contains { $0.range.lowerBound <= edit.range.upperBound && edit.range.lowerBound <= $0.range.upperBound } |
| |
| 885 | }.keys |
| |
| 886 | let touched = columns.values.joined().contains { $0.range.lowerBound <= edit.range.upperBound && edit.range.lowerBound <= $0.range.upperBound } |
| |
| 887 | let newStart = start < edit.range.lowerBound ? start : start >= edit.range.upperBound ? start + delta : edit.range.lowerBound |
| |
| 888 | guard let table = TableShrink.table(at: min(newStart, max(0, text.utf16.count - 1)), in: text) else { continue } |
| |
| 889 | if touched || kept.count != columns.count { tables.append(table) } |
| |
| 890 | guard !kept.isEmpty else { continue } |
| |
| 891 | mapped[table.lowerBound] = Dictionary(uniqueKeysWithValues: kept.map { ($0, TableShrink.segments(table, column: $0, in: text)) }) |
| |
| 892 | } |
| |
| 893 | shrunk = mapped |
| |
| 894 | return tables |
| |
| 895 | } |
| |
| 896 | |
| |
| 897 | /// Hides each shrunk segment's text and draws its display where the text was. |
841 | /// Hides each shrunk segment's text and draws its display where the text was. |
| 898 | private func applyShrunk(_ range: Range<Int>, in storage: NSTextStorage) { |
842 | private func applyShrunk(_ range: Range<Int>, in storage: NSTextStorage) { |
| 899 | guard !shrunk.isEmpty else { return } |
843 | guard !shrunk.isEmpty else { return } |
| 900 | let cell = ("0" as NSString).size(withAttributes: [.font: theme.font]).width |
844 | let cell = ("0" as NSString).size(withAttributes: [.font: theme.font]).width |
| 901 | for segment in shrunk.values.flatMap({ $0.values.joined() }) where segment.range.overlaps(range) && segment.range.upperBound <= storage.length { |
845 | for segment in shrunk.segments where segment.range.overlaps(range) && segment.range.upperBound <= storage.length { |
| 902 | let r = segment.range |
846 | let r = segment.range |
| 903 | let shown = segment.display.count |
847 | let shown = segment.display.count |
| 904 | let kept = min(shown, r.count) |
848 | let kept = min(shown, r.count) |
| @@ -1233,7 +1177,7 @@ public final class OrgEditor: NSObject { |
| 1233 | highlights = [] |
1177 | highlights = [] |
| 1234 | updateHidden() |
1178 | updateHidden() |
| 1235 | restyle(min(oldMapped.lowerBound, newAffected.lowerBound)..<min(max(oldMapped.upperBound, newAffected.upperBound), newText.length), in: storage) |
1179 | restyle(min(oldMapped.lowerBound, newAffected.lowerBound)..<min(max(oldMapped.upperBound, newAffected.upperBound), newText.length), in: storage) |
| 1236 | for table in mapShrunk(through: edit) { restyle(table.lowerBound..<min(table.upperBound, newText.length), in: storage) } |
1180 | for table in shrunk.map(through: edit, in: storage.string, command: commandEditing) { restyle(table.lowerBound..<min(table.upperBound, newText.length), in: storage) } |
| 1237 | for range in oldHighlights where range.upperBound <= newText.length { |
1181 | for range in oldHighlights where range.upperBound <= newText.length { |
| 1238 | let lines = newText.lineRange(for: NSRange(range)) |
1182 | let lines = newText.lineRange(for: NSRange(range)) |
| 1239 | restyle(lines.location..<NSMaxRange(lines), in: storage) |
1183 | restyle(lines.location..<NSMaxRange(lines), in: storage) |