mirror of
https://github.com/h3pdesign/Neon-Vision-Editor
synced 2026-04-21 13:27:16 +00:00
146 lines
5.4 KiB
Swift
146 lines
5.4 KiB
Swift
import SwiftUI
|
|
import Foundation
|
|
import UniformTypeIdentifiers
|
|
|
|
struct PlainTextDocument: FileDocument {
|
|
static var readableContentTypes: [UTType] { [.plainText, .text, .sourceCode] }
|
|
|
|
var text: String
|
|
|
|
init(text: String = "") {
|
|
self.text = text
|
|
}
|
|
|
|
init(configuration: ReadConfiguration) throws {
|
|
if let data = configuration.file.regularFileContents,
|
|
let decoded = String(data: data, encoding: .utf8) {
|
|
text = decoded
|
|
} else {
|
|
text = ""
|
|
}
|
|
}
|
|
|
|
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
|
|
let data = text.data(using: .utf8) ?? Data()
|
|
return FileWrapper(regularFileWithContents: data)
|
|
}
|
|
}
|
|
|
|
struct APISupportSettingsView: View {
|
|
@Binding var grokAPIToken: String
|
|
@Binding var openAIAPIToken: String
|
|
@Binding var geminiAPIToken: String
|
|
@Binding var anthropicAPIToken: String
|
|
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
Text("AI Provider API Keys").font(.headline)
|
|
Group {
|
|
LabeledContent("Grok") {
|
|
SecureField("sk-…", text: $grokAPIToken)
|
|
.textFieldStyle(.roundedBorder)
|
|
.frame(maxWidth: .infinity)
|
|
.onChange(of: grokAPIToken) { _, new in
|
|
SecureTokenStore.setToken(new, for: .grok)
|
|
}
|
|
}
|
|
LabeledContent("OpenAI") {
|
|
SecureField("sk-…", text: $openAIAPIToken)
|
|
.textFieldStyle(.roundedBorder)
|
|
.frame(maxWidth: .infinity)
|
|
.onChange(of: openAIAPIToken) { _, new in
|
|
SecureTokenStore.setToken(new, for: .openAI)
|
|
}
|
|
}
|
|
LabeledContent("Gemini") {
|
|
SecureField("AIza…", text: $geminiAPIToken)
|
|
.textFieldStyle(.roundedBorder)
|
|
.frame(maxWidth: .infinity)
|
|
.onChange(of: geminiAPIToken) { _, new in
|
|
SecureTokenStore.setToken(new, for: .gemini)
|
|
}
|
|
}
|
|
LabeledContent("Anthropic") {
|
|
SecureField("sk-ant-…", text: $anthropicAPIToken)
|
|
.textFieldStyle(.roundedBorder)
|
|
.frame(maxWidth: .infinity)
|
|
.onChange(of: anthropicAPIToken) { _, new in
|
|
SecureTokenStore.setToken(new, for: .anthropic)
|
|
}
|
|
}
|
|
}
|
|
.labelStyle(.titleAndIcon)
|
|
|
|
HStack {
|
|
Spacer()
|
|
Button("Close") {
|
|
dismiss()
|
|
}
|
|
}
|
|
}
|
|
.padding(20)
|
|
.frame(minWidth: 460)
|
|
}
|
|
}
|
|
|
|
struct FindReplacePanel: View {
|
|
@Binding var findQuery: String
|
|
@Binding var replaceQuery: String
|
|
@Binding var useRegex: Bool
|
|
@Binding var caseSensitive: Bool
|
|
@Binding var statusMessage: String
|
|
var onFindNext: () -> Void
|
|
var onReplace: () -> Void
|
|
var onReplaceAll: () -> Void
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("Find & Replace").font(.headline)
|
|
LabeledContent("Find") {
|
|
TextField("Search text", text: $findQuery)
|
|
.textFieldStyle(.roundedBorder)
|
|
}
|
|
LabeledContent("Replace") {
|
|
TextField("Replacement", text: $replaceQuery)
|
|
.textFieldStyle(.roundedBorder)
|
|
}
|
|
Toggle("Use Regex", isOn: $useRegex)
|
|
Toggle("Case Sensitive", isOn: $caseSensitive)
|
|
if !statusMessage.isEmpty {
|
|
Text(statusMessage)
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
HStack {
|
|
Button("Find Next") { onFindNext() }
|
|
Button("Replace") { onReplace() }.disabled(findQuery.isEmpty)
|
|
Button("Replace All") { onReplaceAll() }.disabled(findQuery.isEmpty)
|
|
Spacer()
|
|
Button("Close") { dismiss() }
|
|
}
|
|
}
|
|
.padding(16)
|
|
.frame(minWidth: 380)
|
|
}
|
|
}
|
|
|
|
extension Notification.Name {
|
|
static let moveCursorToLine = Notification.Name("moveCursorToLine")
|
|
static let caretPositionDidChange = Notification.Name("caretPositionDidChange")
|
|
static let pastedText = Notification.Name("pastedText")
|
|
static let toggleTranslucencyRequested = Notification.Name("toggleTranslucencyRequested")
|
|
static let clearEditorRequested = Notification.Name("clearEditorRequested")
|
|
static let toggleCodeCompletionRequested = Notification.Name("toggleCodeCompletionRequested")
|
|
static let showFindReplaceRequested = Notification.Name("showFindReplaceRequested")
|
|
static let toggleProjectStructureSidebarRequested = Notification.Name("toggleProjectStructureSidebarRequested")
|
|
static let showAPISettingsRequested = Notification.Name("showAPISettingsRequested")
|
|
static let selectAIModelRequested = Notification.Name("selectAIModelRequested")
|
|
}
|
|
|
|
extension NSRange {
|
|
func toOptional() -> NSRange? { self.location == NSNotFound ? nil : self }
|
|
}
|
|
|