summaryrefslogtreecommitdiff
path: root/macos/Sources/Features/ClipboardConfirmation/ClipboardConfirmationView.swift
blob: 1a7272e1693582518bbf48c889c506ec73833439 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import SwiftUI

/// This delegate is notified of the completion result of the clipboard confirmation dialog.
protocol ClipboardConfirmationViewDelegate: AnyObject {
    func clipboardConfirmationComplete(_ action: ClipboardConfirmationView.Action, _ request: Ghostty.ClipboardRequest)
}

/// The SwiftUI view for showing a clipboard confirmation dialog.
struct ClipboardConfirmationView: View {
    enum Action : String {
        case cancel
        case confirm

        static func text(_ action: Action, _ reason: Ghostty.ClipboardRequest) -> String {
            switch (action, reason) {
            case (.cancel, .paste):
                return "Cancel"
            case (.cancel, .osc_52_read), (.cancel, .osc_52_write):
                return "Deny"
            case (.confirm, .paste):
                return "Paste"
            case (.confirm, .osc_52_read), (.confirm, .osc_52_write):
                return "Allow"
            }
        }
    }

    /// The contents of the paste.
    let contents: String

    /// The type of the clipboard request
    let request: Ghostty.ClipboardRequest

    /// Optional delegate to get results. If this is nil, then this view will never close on its own.
    weak var delegate: ClipboardConfirmationViewDelegate? = nil

    /// Used to track if we should rehide on disappear
    @State private var cursorHiddenCount: UInt = 0

    var body: some View {
        VStack {
            HStack {
                Image(systemName: "exclamationmark.triangle.fill")
                    .foregroundColor(.yellow)
                    .font(.system(size: 42))
                    .padding()
                    .frame(alignment: .center)

                Text(request.text())
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding()
            }

            ScrollView {
                Text(contents)
                    .textSelection(.enabled)
                    .font(.system(.body, design: .monospaced))
                    .padding(.all, 4)
            }

            HStack {
                Spacer()
                Button(Action.text(.cancel, request)) { onCancel() }
                    .keyboardShortcut(.cancelAction)
                Button(Action.text(.confirm, request)) { onPaste() }
                    .keyboardShortcut(.defaultAction)
                Spacer()
            }
            .padding(.bottom)
        }
        .onAppear {
            // I can't find a better way to handle this. There is no API to detect
            // if the cursor is hidden and OTHER THINGS do unhide the cursor. So we
            // try to unhide it completely here and hope for the best. Issue #1516.
            cursorHiddenCount = Cursor.unhideCompletely()

            // If we didn't unhide anything, we just send an unhide to be safe.
            // I don't think the count can go negative on NSCursor so this handles
            // scenarios cursor is hidden outside of our own NSCursor usage.
            if (cursorHiddenCount == 0) {
                _ = Cursor.unhide()
            }
        }
        .onDisappear {
            // Rehide if we unhid
            for _ in 0..<cursorHiddenCount {
                Cursor.hide()
            }
        }
    }

    private func onCancel() {
        delegate?.clipboardConfirmationComplete(.cancel, request)
    }

    private func onPaste() {
        delegate?.clipboardConfirmationComplete(.confirm, request)
    }
}