oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2
3#if os(macOS)
4 import AppKit
5#else
6 import UIKit
7#endif
8@MainActor extension SheetModel {
9 func clearCopyProvenance() {
10 copied = nil
11 copiedText = ""
12 }
13 func copy() {
14 let range = selection
15 let rows = (range.start.r...range.end.r).map { r in
16 (range.start.c...range.end.c).map { c in
17 CellWrite(r: r, c: c, raw: cells[Coordinate(r: r, c: c)]?.raw ?? "")
18 }
19 }
20 copied = rows
21 copiedText = TSV.encode(rows.map { $0.map(\.raw) })
22 #if os(macOS)
23 NSPasteboard.general.clearContents()
24 NSPasteboard.general.setString(copiedText, forType: .string)
25 #else
26 UIPasteboard.general.string = copiedText
27 #endif
28 }
29 func paste() async {
30 guard !dirty && !busy else {
31 error = "Save or discard your cell edit first."
32 return
33 }
34 #if os(macOS)
35 let text = NSPasteboard.general.string(forType: .string)
36 #else
37 let text = UIPasteboard.general.string
38 #endif
39 guard let text else {
40 error = "The clipboard has no text cells."
41 return
42 }
43 let values = TSV.decode(text)
44 guard values.reduce(0, { $0 + $1.count }) <= 500 else {
45 error = "Paste supports up to 500 cells at once."
46 return
47 }
48 var writes: [CellWrite] = []
49 for (r, row) in values.enumerated() {
50 for (c, raw) in row.enumerated() {
51 let dest = Coordinate(r: selection.start.r + r, c: selection.start.c + c)
52 guard dest.r < rows && dest.c < cols else {
53 error = "This paste extends beyond the visible sheet. Nothing was changed."
54 return
55 }
56 let source =
57 text == copiedText && copied?.indices.contains(r) == true
58 && copied![r].indices.contains(c) ? copied![r][c] : nil
59 writes.append(CellWrite(r: dest.r, c: dest.c, raw: raw, fromR: source?.r, fromC: source?.c))
60 }
61 }
62 _ = await write(writes)
63 }
64 func clear() async {
65 guard !dirty && !busy else {
66 error = "Save or discard your cell edit first."
67 return
68 }
69 _ = await write(
70 selection.coordinates.filter { !(cells[$0]?.raw ?? "").isEmpty }.map {
71 CellWrite(r: $0.r, c: $0.c, raw: "")
72 })
73 }
74 func fill(down: Bool) async {
75 guard !dirty && !busy else {
76 error = "Save or discard your cell edit first."
77 return
78 }
79 _ = await write(fillWrites(down: down))
80 }
81 func fillWrites(down: Bool) -> [CellWrite] {
82 let source = selection.start
83 let destinations =
84 selection.count == 1
85 ? [
86 Coordinate(
87 r: min(source.r + (down ? 1 : 0), rows - 1), c: min(source.c + (down ? 0 : 1), cols - 1))
88 ]
89 : selection.coordinates
90 return destinations.compactMap { point -> CellWrite? in
91 if source == point { return nil }
92 return CellWrite(
93 r: point.r, c: point.c, raw: cells[source]?.raw ?? "", fromR: source.r, fromC: source.c)
94 }
95 }
96}
97