2026-02-06 18:59:53 +00:00
|
|
|
import SwiftUI
|
2026-02-07 10:51:52 +00:00
|
|
|
#if os(macOS)
|
2026-02-06 18:59:53 +00:00
|
|
|
import AppKit
|
2026-02-07 10:51:52 +00:00
|
|
|
#elseif os(iOS)
|
|
|
|
|
import UIKit
|
|
|
|
|
#endif
|
2026-02-06 18:59:53 +00:00
|
|
|
|
2026-03-09 16:47:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/// MARK: - Types
|
|
|
|
|
|
2026-02-06 18:59:53 +00:00
|
|
|
extension ContentView {
|
2026-02-12 09:15:40 +00:00
|
|
|
private var compactActiveProviderName: String {
|
|
|
|
|
activeProviderName.components(separatedBy: " (").first ?? activeProviderName
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 14:44:43 +00:00
|
|
|
private var providerBadgeLabelText: String {
|
|
|
|
|
#if os(macOS)
|
|
|
|
|
if compactActiveProviderName == "Apple" {
|
|
|
|
|
return "AI Provider \(compactActiveProviderName)"
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
return compactActiveProviderName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var providerBadgeIsAppleCompletionActive: Bool {
|
|
|
|
|
compactActiveProviderName == "Apple" && isAutoCompletionEnabled
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var providerBadgeForegroundColor: Color {
|
|
|
|
|
providerBadgeIsAppleCompletionActive ? .green : .secondary
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var providerBadgeBackgroundColor: Color {
|
|
|
|
|
providerBadgeIsAppleCompletionActive ? Color.green.opacity(0.16) : Color.secondary.opacity(0.12)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var providerBadgeTooltip: String {
|
|
|
|
|
"AI Provider for Code Completion"
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 15:14:21 +00:00
|
|
|
#if os(macOS)
|
|
|
|
|
private var macToolbarSymbolColor: Color {
|
|
|
|
|
let isDarkMode = colorScheme == .dark
|
|
|
|
|
switch toolbarSymbolsColorMacRaw {
|
|
|
|
|
case "black":
|
|
|
|
|
return isDarkMode
|
|
|
|
|
? Color(.sRGB, white: 0.94, opacity: 1.0)
|
|
|
|
|
: .black
|
|
|
|
|
case "darkGray":
|
|
|
|
|
return isDarkMode
|
|
|
|
|
? Color(.sRGB, white: 0.84, opacity: 1.0)
|
|
|
|
|
: Color(.sRGB, white: 0.40, opacity: 1.0)
|
|
|
|
|
default:
|
|
|
|
|
return NeonUIStyle.accentBlue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
#if os(iOS)
|
2026-02-19 23:10:51 +00:00
|
|
|
private var iOSToolbarChromeStyle: GlassChromeStyle { .single }
|
2026-02-20 00:31:14 +00:00
|
|
|
private var iOSToolbarTintColor: Color {
|
|
|
|
|
if toolbarIconsBlueIOS {
|
|
|
|
|
return NeonUIStyle.accentBlue
|
|
|
|
|
}
|
2026-02-20 02:41:08 +00:00
|
|
|
return colorScheme == .dark ? Color.white.opacity(0.95) : Color.primary.opacity(0.92)
|
2026-02-20 00:31:14 +00:00
|
|
|
}
|
2026-02-19 23:10:51 +00:00
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
private var isIPadToolbarLayout: Bool {
|
2026-02-18 22:56:46 +00:00
|
|
|
guard UIDevice.current.userInterfaceIdiom == .pad else { return false }
|
|
|
|
|
// During first render on iOS, horizontalSizeClass can transiently be nil.
|
|
|
|
|
// Treat nil as regular so the full iPad toolbar appears immediately.
|
|
|
|
|
if horizontalSizeClass == .compact { return false }
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var iPhoneToolbarWidth: CGFloat {
|
|
|
|
|
UIApplication.shared.connectedScenes
|
|
|
|
|
.compactMap { $0 as? UIWindowScene }
|
|
|
|
|
.first(where: { $0.activationState == .foregroundActive })?
|
|
|
|
|
.screen.bounds.width ?? 390
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:12:47 +00:00
|
|
|
private var activeWindowWidth: CGFloat {
|
|
|
|
|
let scenes = UIApplication.shared.connectedScenes
|
|
|
|
|
.compactMap { $0 as? UIWindowScene }
|
|
|
|
|
.filter { $0.activationState == .foregroundActive }
|
|
|
|
|
let normalWindowWidths = scenes
|
|
|
|
|
.flatMap(\.windows)
|
|
|
|
|
.filter { window in
|
|
|
|
|
!window.isHidden &&
|
|
|
|
|
window.alpha > 0.01 &&
|
|
|
|
|
window.windowLevel == .normal &&
|
|
|
|
|
window.bounds.width > 0
|
|
|
|
|
}
|
|
|
|
|
.map { $0.bounds.width }
|
|
|
|
|
if let width = normalWindowWidths.max() {
|
|
|
|
|
return width
|
|
|
|
|
}
|
|
|
|
|
return scenes.first?.screen.bounds.width ?? 1024
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 22:56:46 +00:00
|
|
|
private var iPhonePromotedActionsCount: Int {
|
|
|
|
|
switch iPhoneToolbarWidth {
|
|
|
|
|
case 430...: return 4
|
|
|
|
|
case 395...: return 3
|
2026-02-21 19:12:47 +00:00
|
|
|
default: return 1
|
2026-02-18 22:56:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var iPhoneLanguagePickerWidth: CGFloat {
|
|
|
|
|
switch iPhoneToolbarWidth {
|
2026-02-21 19:12:47 +00:00
|
|
|
case 430...: return 108
|
|
|
|
|
case 395...: return 100
|
|
|
|
|
default: return 94
|
2026-02-18 22:56:46 +00:00
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var iPadToolbarMaxWidth: CGFloat {
|
2026-02-21 19:12:47 +00:00
|
|
|
// Use live window width (not full screen width) so Stage Manager/split sizes
|
|
|
|
|
// immediately rebalance promoted vs overflow actions.
|
|
|
|
|
let target = activeWindowWidth - 28
|
|
|
|
|
return min(max(target, 560), 1320)
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:31:14 +00:00
|
|
|
|
2026-02-19 14:29:53 +00:00
|
|
|
private enum IPadToolbarAction: String, CaseIterable, Hashable {
|
|
|
|
|
case openFile
|
2026-02-22 12:38:31 +00:00
|
|
|
case undo
|
2026-02-19 14:29:53 +00:00
|
|
|
case newTab
|
2026-03-08 14:31:01 +00:00
|
|
|
case closeAllTabs
|
2026-02-19 14:29:53 +00:00
|
|
|
case saveFile
|
2026-03-15 16:37:20 +00:00
|
|
|
case codeSnapshot
|
2026-02-28 19:48:06 +00:00
|
|
|
case markdownPreview
|
|
|
|
|
case fontDecrease
|
|
|
|
|
case fontIncrease
|
2026-02-19 14:29:53 +00:00
|
|
|
case toggleSidebar
|
|
|
|
|
case toggleProjectSidebar
|
|
|
|
|
case findReplace
|
|
|
|
|
case settings
|
|
|
|
|
case codeCompletion
|
|
|
|
|
case performanceMode
|
|
|
|
|
case lineWrap
|
|
|
|
|
case keyboardAccessory
|
|
|
|
|
case clearEditor
|
|
|
|
|
case insertTemplate
|
|
|
|
|
case brainDump
|
|
|
|
|
case welcomeTour
|
|
|
|
|
case translucentWindow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var iPadActionPriority: [IPadToolbarAction] {
|
|
|
|
|
[
|
|
|
|
|
.openFile,
|
2026-02-22 12:38:31 +00:00
|
|
|
.undo,
|
2026-02-19 14:29:53 +00:00
|
|
|
.newTab,
|
2026-03-08 14:31:01 +00:00
|
|
|
.closeAllTabs,
|
2026-02-19 14:29:53 +00:00
|
|
|
.saveFile,
|
2026-03-15 16:37:20 +00:00
|
|
|
.codeSnapshot,
|
2026-02-28 19:48:06 +00:00
|
|
|
.markdownPreview,
|
|
|
|
|
.fontDecrease,
|
|
|
|
|
.fontIncrease,
|
2026-02-19 14:29:53 +00:00
|
|
|
.toggleSidebar,
|
|
|
|
|
.toggleProjectSidebar,
|
2026-02-20 00:31:14 +00:00
|
|
|
.findReplace,
|
2026-02-19 14:29:53 +00:00
|
|
|
.settings,
|
|
|
|
|
.codeCompletion,
|
|
|
|
|
.lineWrap,
|
2026-02-20 00:31:14 +00:00
|
|
|
.keyboardAccessory,
|
2026-02-19 14:29:53 +00:00
|
|
|
.clearEditor,
|
|
|
|
|
.insertTemplate,
|
2026-02-19 23:10:51 +00:00
|
|
|
.performanceMode,
|
|
|
|
|
.brainDump,
|
|
|
|
|
.welcomeTour,
|
|
|
|
|
.translucentWindow
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 01:16:58 +00:00
|
|
|
private func toggleKeyboardAccessoryBar() {
|
|
|
|
|
showKeyboardAccessoryBarIOS.toggle()
|
|
|
|
|
NotificationCenter.default.post(
|
|
|
|
|
name: .keyboardAccessoryBarVisibilityChanged,
|
|
|
|
|
object: showKeyboardAccessoryBarIOS
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 10:27:55 +00:00
|
|
|
private func toggleBrainDumpModeIOSAware() {
|
|
|
|
|
#if os(iOS)
|
|
|
|
|
viewModel.isBrainDumpMode = false
|
|
|
|
|
UserDefaults.standard.set(false, forKey: "BrainDumpModeEnabled")
|
|
|
|
|
#else
|
|
|
|
|
viewModel.isBrainDumpMode.toggle()
|
|
|
|
|
UserDefaults.standard.set(viewModel.isBrainDumpMode, forKey: "BrainDumpModeEnabled")
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-19 23:10:51 +00:00
|
|
|
private var iPadPinnedOverflowActions: Set<IPadToolbarAction> {
|
|
|
|
|
[
|
|
|
|
|
.performanceMode,
|
2026-02-19 14:29:53 +00:00
|
|
|
.brainDump,
|
|
|
|
|
.welcomeTour,
|
|
|
|
|
.translucentWindow
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 19:48:06 +00:00
|
|
|
private var iPadAlwaysVisibleActions: [IPadToolbarAction] {
|
2026-03-08 14:31:01 +00:00
|
|
|
[.openFile, .newTab, .closeAllTabs, .saveFile, .findReplace, .settings]
|
2026-02-28 19:48:06 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 14:29:53 +00:00
|
|
|
private var iPadPromotedActionSlotCount: Int {
|
2026-02-20 00:31:14 +00:00
|
|
|
switch iPadToolbarMaxWidth {
|
2026-02-21 19:12:47 +00:00
|
|
|
case 1200...: return 11
|
|
|
|
|
case 1080...: return 10
|
|
|
|
|
case 980...: return 9
|
2026-02-20 00:31:14 +00:00
|
|
|
case 900...: return 9
|
|
|
|
|
case 820...: return 8
|
2026-02-21 19:12:47 +00:00
|
|
|
case 740...: return 7
|
|
|
|
|
case 660...: return 6
|
|
|
|
|
default: return 5
|
2026-02-20 00:31:14 +00:00
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 14:29:53 +00:00
|
|
|
private var iPadPromotedActions: [IPadToolbarAction] {
|
2026-02-28 19:48:06 +00:00
|
|
|
let eligible = iPadActionPriority.filter {
|
|
|
|
|
!iPadPinnedOverflowActions.contains($0) &&
|
|
|
|
|
!iPadAlwaysVisibleActions.contains($0)
|
|
|
|
|
}
|
2026-02-19 23:10:51 +00:00
|
|
|
return Array(eligible.prefix(iPadPromotedActionSlotCount))
|
2026-02-19 14:29:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private var iPadOverflowActions: [IPadToolbarAction] {
|
2026-02-28 19:48:06 +00:00
|
|
|
iPadActionPriority.filter {
|
|
|
|
|
!iPadAlwaysVisibleActions.contains($0) &&
|
|
|
|
|
(iPadPinnedOverflowActions.contains($0) || !iPadPromotedActions.contains($0))
|
|
|
|
|
}
|
2026-02-19 14:29:53 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 18:46:08 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var newTabControl: some View {
|
|
|
|
|
Button(action: { viewModel.addNewTab() }) {
|
|
|
|
|
Image(systemName: "plus.square.on.square")
|
|
|
|
|
}
|
2026-02-11 10:20:17 +00:00
|
|
|
.help("New Tab (Cmd+T)")
|
2026-03-06 19:20:14 +00:00
|
|
|
.accessibilityLabel("New tab")
|
|
|
|
|
.accessibilityHint("Creates a new editor tab")
|
2026-02-16 19:02:43 +00:00
|
|
|
.keyboardShortcut("t", modifiers: .command)
|
2026-02-11 10:20:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
|
private var settingsControl: some View {
|
2026-02-27 17:13:12 +00:00
|
|
|
Button(action: { openSettings() }) {
|
2026-02-11 10:20:17 +00:00
|
|
|
Image(systemName: "gearshape")
|
|
|
|
|
}
|
2026-02-27 17:13:12 +00:00
|
|
|
.help("Settings (Cmd+,)")
|
2026-03-06 19:20:14 +00:00
|
|
|
.accessibilityLabel("Settings")
|
|
|
|
|
.accessibilityHint("Opens app settings")
|
2026-02-27 17:13:12 +00:00
|
|
|
.keyboardShortcut(",", modifiers: .command)
|
2026-02-07 18:46:08 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var languagePickerControl: some View {
|
2026-03-04 19:40:17 +00:00
|
|
|
Menu {
|
2026-03-26 18:19:45 +00:00
|
|
|
let selectedLanguage = currentLanguagePickerBinding.wrappedValue
|
|
|
|
|
Button {
|
|
|
|
|
currentLanguagePickerBinding.wrappedValue = selectedLanguage
|
|
|
|
|
} label: {
|
|
|
|
|
Label(languageLabel(for: selectedLanguage), systemImage: "checkmark")
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
2026-03-04 19:40:17 +00:00
|
|
|
Button(action: { presentLanguageSearchSheet() }) {
|
|
|
|
|
Label("Language…", systemImage: "magnifyingglass")
|
|
|
|
|
}
|
|
|
|
|
.keyboardShortcut("l", modifiers: [.command, .shift])
|
2026-03-26 18:19:45 +00:00
|
|
|
Divider()
|
|
|
|
|
ForEach(languageOptions.filter { $0 != selectedLanguage }, id: \.self) { lang in
|
|
|
|
|
Button {
|
|
|
|
|
currentLanguagePickerBinding.wrappedValue = lang
|
|
|
|
|
} label: {
|
|
|
|
|
Text(languageLabel(for: lang))
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-04 19:40:17 +00:00
|
|
|
} label: {
|
|
|
|
|
Text(toolbarCompactLanguageLabel(currentLanguagePickerBinding.wrappedValue))
|
|
|
|
|
.lineLimit(1)
|
|
|
|
|
.truncationMode(.tail)
|
2026-03-12 19:59:15 +00:00
|
|
|
#if os(iOS)
|
|
|
|
|
.padding(.horizontal, 10)
|
|
|
|
|
.padding(.vertical, 5)
|
|
|
|
|
.background(.ultraThinMaterial, in: Capsule())
|
|
|
|
|
.overlay(
|
|
|
|
|
Capsule()
|
|
|
|
|
.stroke(iOSToolbarTintColor.opacity(0.35), lineWidth: 1)
|
|
|
|
|
)
|
2026-03-26 09:40:30 +00:00
|
|
|
.frame(width: isIPadToolbarLayout ? 112 : iPhoneLanguagePickerWidth)
|
2026-03-12 19:59:15 +00:00
|
|
|
#endif
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
.labelsHidden()
|
|
|
|
|
.help("Language")
|
2026-03-06 19:20:14 +00:00
|
|
|
.accessibilityLabel("Language picker")
|
|
|
|
|
.accessibilityHint("Choose syntax language for the current tab")
|
2026-02-21 19:12:47 +00:00
|
|
|
.layoutPriority(2)
|
2026-03-12 19:59:15 +00:00
|
|
|
#if os(iOS)
|
2026-03-26 18:19:45 +00:00
|
|
|
.tint(iOSToolbarTintColor)
|
2026-03-12 19:59:15 +00:00
|
|
|
.menuStyle(.button)
|
|
|
|
|
#endif
|
2026-02-19 14:29:53 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var activeProviderBadgeControl: some View {
|
2026-02-24 14:44:43 +00:00
|
|
|
Text(providerBadgeLabelText)
|
2026-02-07 10:51:52 +00:00
|
|
|
.font(.caption)
|
2026-02-24 14:44:43 +00:00
|
|
|
.foregroundColor(providerBadgeForegroundColor)
|
2026-02-12 09:15:40 +00:00
|
|
|
.lineLimit(1)
|
|
|
|
|
.truncationMode(.tail)
|
|
|
|
|
.minimumScaleFactor(0.9)
|
|
|
|
|
.fixedSize(horizontal: true, vertical: false)
|
2026-02-07 10:51:52 +00:00
|
|
|
.padding(.horizontal, 6)
|
|
|
|
|
.padding(.vertical, 2)
|
2026-02-24 14:44:43 +00:00
|
|
|
.background(providerBadgeBackgroundColor, in: Capsule())
|
|
|
|
|
.help(providerBadgeTooltip)
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
|
private var clearEditorControl: some View {
|
|
|
|
|
Button(action: {
|
2026-02-12 22:20:39 +00:00
|
|
|
requestClearEditorContent()
|
2026-02-07 10:51:52 +00:00
|
|
|
}) {
|
2026-02-20 16:37:44 +00:00
|
|
|
Image(systemName: "eraser")
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
.help("Clear Editor")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-09 10:21:50 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var insertTemplateControl: some View {
|
|
|
|
|
Button(action: { insertTemplateForCurrentLanguage() }) {
|
|
|
|
|
Image(systemName: "doc.badge.plus")
|
|
|
|
|
}
|
|
|
|
|
.help("Insert Template for Current Language")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 19:02:43 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var lineWrapControl: some View {
|
|
|
|
|
Button(action: {
|
|
|
|
|
viewModel.isLineWrapEnabled.toggle()
|
|
|
|
|
}) {
|
|
|
|
|
Image(systemName: "text.justify")
|
|
|
|
|
}
|
|
|
|
|
.help("Enable Wrap / Disable Wrap (Cmd+Opt+L)")
|
|
|
|
|
.keyboardShortcut("l", modifiers: [.command, .option])
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var openFileControl: some View {
|
|
|
|
|
Button(action: { openFileFromToolbar() }) {
|
|
|
|
|
Image(systemName: "folder")
|
|
|
|
|
}
|
2026-02-11 10:20:17 +00:00
|
|
|
.help("Open File… (Cmd+O)")
|
2026-03-06 19:20:14 +00:00
|
|
|
.accessibilityLabel("Open file")
|
|
|
|
|
.accessibilityHint("Opens a file picker")
|
2026-02-16 19:02:43 +00:00
|
|
|
.keyboardShortcut("o", modifiers: .command)
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-22 12:38:31 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var undoControl: some View {
|
|
|
|
|
Button(action: { undoFromToolbar() }) {
|
|
|
|
|
Image(systemName: "arrow.uturn.backward")
|
|
|
|
|
}
|
|
|
|
|
.help("Undo (Cmd+Z)")
|
|
|
|
|
.keyboardShortcut("z", modifiers: .command)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var saveFileControl: some View {
|
|
|
|
|
Button(action: { saveCurrentTabFromToolbar() }) {
|
|
|
|
|
Image(systemName: "square.and.arrow.down")
|
|
|
|
|
}
|
2026-03-29 10:39:47 +00:00
|
|
|
.disabled(viewModel.selectedTab == nil || viewModel.selectedTab?.isReadOnlyPreview == true)
|
2026-02-11 10:20:17 +00:00
|
|
|
.help("Save File (Cmd+S)")
|
2026-03-06 19:20:14 +00:00
|
|
|
.accessibilityLabel("Save file")
|
|
|
|
|
.accessibilityHint("Saves the current tab")
|
2026-02-16 19:02:43 +00:00
|
|
|
.keyboardShortcut("s", modifiers: .command)
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-08 14:31:01 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var closeAllTabsControl: some View {
|
|
|
|
|
Button(action: { requestCloseAllTabsFromToolbar() }) {
|
|
|
|
|
Image(systemName: "xmark.square")
|
|
|
|
|
}
|
|
|
|
|
.disabled(viewModel.tabs.isEmpty)
|
|
|
|
|
.help("Close All Tabs")
|
|
|
|
|
.accessibilityLabel("Close all tabs")
|
|
|
|
|
.accessibilityHint("Closes every open tab")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 19:48:06 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var fontDecreaseControl: some View {
|
|
|
|
|
Button(action: { adjustEditorFontSize(-1) }) {
|
|
|
|
|
Image(systemName: "textformat.size.smaller")
|
|
|
|
|
}
|
|
|
|
|
.help("Decrease Font Size")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
|
private var fontIncreaseControl: some View {
|
|
|
|
|
Button(action: { adjustEditorFontSize(1) }) {
|
|
|
|
|
Image(systemName: "textformat.size.larger")
|
|
|
|
|
}
|
|
|
|
|
.help("Increase Font Size")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var toggleSidebarControl: some View {
|
|
|
|
|
Button(action: { toggleSidebarFromToolbar() }) {
|
|
|
|
|
Image(systemName: "sidebar.left")
|
|
|
|
|
}
|
2026-02-11 10:20:17 +00:00
|
|
|
.help("Toggle Sidebar (Cmd+Opt+S)")
|
2026-02-16 19:02:43 +00:00
|
|
|
.keyboardShortcut("s", modifiers: [.command, .option])
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
|
private var toggleProjectSidebarControl: some View {
|
2026-02-20 01:16:58 +00:00
|
|
|
Button(action: { toggleProjectSidebarFromToolbar() }) {
|
2026-02-07 10:51:52 +00:00
|
|
|
Image(systemName: "sidebar.right")
|
|
|
|
|
}
|
|
|
|
|
.help("Toggle Project Structure Sidebar")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
|
private var findReplaceControl: some View {
|
|
|
|
|
Button(action: { showFindReplace = true }) {
|
|
|
|
|
Image(systemName: "magnifyingglass")
|
|
|
|
|
}
|
2026-02-11 10:20:17 +00:00
|
|
|
.help("Find & Replace (Cmd+F)")
|
2026-02-16 19:02:43 +00:00
|
|
|
.keyboardShortcut("f", modifiers: .command)
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-16 21:12:55 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var brainDumpControl: some View {
|
|
|
|
|
Button(action: {
|
2026-02-20 10:27:55 +00:00
|
|
|
toggleBrainDumpModeIOSAware()
|
2026-02-16 21:12:55 +00:00
|
|
|
}) {
|
|
|
|
|
Image(systemName: "note.text")
|
|
|
|
|
.symbolVariant(viewModel.isBrainDumpMode ? .fill : .none)
|
|
|
|
|
}
|
|
|
|
|
.help("Brain Dump Mode")
|
|
|
|
|
.accessibilityLabel("Brain Dump Mode")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 18:59:25 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var codeCompletionControl: some View {
|
|
|
|
|
Button(action: {
|
|
|
|
|
toggleAutoCompletion()
|
|
|
|
|
}) {
|
2026-02-19 08:09:35 +00:00
|
|
|
Image(systemName: "bolt.horizontal.circle")
|
2026-02-18 18:59:25 +00:00
|
|
|
.symbolVariant(isAutoCompletionEnabled ? .fill : .none)
|
|
|
|
|
}
|
2026-02-19 08:09:35 +00:00
|
|
|
.help(isAutoCompletionEnabled ? "Disable Code Completion" : "Enable Code Completion")
|
2026-02-18 18:59:25 +00:00
|
|
|
.accessibilityLabel("Code Completion")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 22:56:46 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var performanceModeControl: some View {
|
|
|
|
|
Button(action: {
|
|
|
|
|
forceLargeFileMode.toggle()
|
|
|
|
|
updateLargeFileMode(for: currentContentBinding.wrappedValue)
|
|
|
|
|
recordDiagnostic("Toolbar toggled performance mode: \(forceLargeFileMode ? "on" : "off")")
|
|
|
|
|
}) {
|
|
|
|
|
Image(systemName: forceLargeFileMode ? "speedometer" : "speedometer")
|
|
|
|
|
.symbolVariant(forceLargeFileMode ? .fill : .none)
|
|
|
|
|
}
|
|
|
|
|
.help("Performance Mode")
|
|
|
|
|
.accessibilityLabel("Performance Mode")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-28 19:48:06 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var markdownPreviewControl: some View {
|
|
|
|
|
Button(action: {
|
|
|
|
|
toggleMarkdownPreviewFromToolbar()
|
|
|
|
|
}) {
|
|
|
|
|
Image(systemName: showMarkdownPreviewPane ? "doc.richtext.fill" : "doc.richtext")
|
|
|
|
|
}
|
|
|
|
|
.disabled(currentLanguage != "markdown")
|
|
|
|
|
.help("Toggle Markdown Preview")
|
|
|
|
|
.accessibilityLabel("Markdown Preview")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 22:56:46 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var keyboardAccessoryControl: some View {
|
|
|
|
|
Button(action: {
|
2026-02-20 01:16:58 +00:00
|
|
|
toggleKeyboardAccessoryBar()
|
2026-02-18 22:56:46 +00:00
|
|
|
}) {
|
|
|
|
|
Image(systemName: showKeyboardAccessoryBarIOS ? "keyboard.chevron.compact.down.fill" : "keyboard.chevron.compact.down")
|
|
|
|
|
}
|
|
|
|
|
.help(showKeyboardAccessoryBarIOS ? "Hide Keyboard Snippet Bar" : "Show Keyboard Snippet Bar")
|
|
|
|
|
.accessibilityLabel("Keyboard Snippet Bar")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 21:12:55 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var welcomeTourControl: some View {
|
|
|
|
|
Button(action: {
|
|
|
|
|
showWelcomeTour = true
|
|
|
|
|
}) {
|
|
|
|
|
Image(systemName: "sparkles.rectangle.stack")
|
|
|
|
|
}
|
|
|
|
|
.help("Welcome Tour")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
|
private var translucentWindowControl: some View {
|
|
|
|
|
Button(action: {
|
|
|
|
|
enableTranslucentWindow.toggle()
|
|
|
|
|
UserDefaults.standard.set(enableTranslucentWindow, forKey: "EnableTranslucentWindow")
|
|
|
|
|
NotificationCenter.default.post(name: .toggleTranslucencyRequested, object: enableTranslucentWindow)
|
|
|
|
|
}) {
|
|
|
|
|
Image(systemName: enableTranslucentWindow ? "rectangle.fill" : "rectangle")
|
|
|
|
|
}
|
|
|
|
|
.help("Toggle Translucent Window Background")
|
|
|
|
|
.accessibilityLabel("Translucent Window Background")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 16:37:20 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var codeSnapshotControl: some View {
|
|
|
|
|
Button(action: { presentCodeSnapshotComposer() }) {
|
|
|
|
|
Image(systemName: "camera.viewfinder")
|
|
|
|
|
}
|
|
|
|
|
.disabled(!canCreateCodeSnapshot)
|
|
|
|
|
.help("Create Code Snapshot from Selection")
|
|
|
|
|
.accessibilityLabel("Create Code Snapshot")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
@ViewBuilder
|
2026-02-19 14:29:53 +00:00
|
|
|
private func iPadToolbarActionControl(_ action: IPadToolbarAction) -> some View {
|
|
|
|
|
switch action {
|
|
|
|
|
case .openFile: openFileControl
|
2026-02-22 12:38:31 +00:00
|
|
|
case .undo: undoControl
|
2026-02-19 14:29:53 +00:00
|
|
|
case .newTab: newTabControl
|
2026-03-08 14:31:01 +00:00
|
|
|
case .closeAllTabs: closeAllTabsControl
|
2026-02-19 14:29:53 +00:00
|
|
|
case .saveFile: saveFileControl
|
2026-03-15 16:37:20 +00:00
|
|
|
case .codeSnapshot: codeSnapshotControl
|
2026-02-28 19:48:06 +00:00
|
|
|
case .markdownPreview: markdownPreviewControl
|
|
|
|
|
case .fontDecrease: fontDecreaseControl
|
|
|
|
|
case .fontIncrease: fontIncreaseControl
|
2026-02-19 14:29:53 +00:00
|
|
|
case .toggleSidebar: toggleSidebarControl
|
|
|
|
|
case .toggleProjectSidebar: toggleProjectSidebarControl
|
|
|
|
|
case .findReplace: findReplaceControl
|
|
|
|
|
case .settings: settingsControl
|
|
|
|
|
case .codeCompletion: codeCompletionControl
|
|
|
|
|
case .performanceMode: performanceModeControl
|
|
|
|
|
case .lineWrap: lineWrapControl
|
|
|
|
|
case .keyboardAccessory: keyboardAccessoryControl
|
|
|
|
|
case .clearEditor: clearEditorControl
|
|
|
|
|
case .insertTemplate: insertTemplateControl
|
|
|
|
|
case .brainDump: brainDumpControl
|
|
|
|
|
case .welcomeTour: welcomeTourControl
|
|
|
|
|
case .translucentWindow: translucentWindowControl
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
|
private var iPadOverflowMenuControl: some View {
|
|
|
|
|
if !iPadOverflowActions.isEmpty {
|
|
|
|
|
Menu {
|
|
|
|
|
ForEach(iPadOverflowActions, id: \.self) { action in
|
|
|
|
|
switch action {
|
|
|
|
|
case .openFile:
|
|
|
|
|
Button(action: { openFileFromToolbar() }) {
|
|
|
|
|
Label("Open File…", systemImage: "folder")
|
|
|
|
|
}
|
2026-02-22 12:38:31 +00:00
|
|
|
case .undo:
|
|
|
|
|
Button(action: { undoFromToolbar() }) {
|
|
|
|
|
Label("Undo", systemImage: "arrow.uturn.backward")
|
|
|
|
|
}
|
|
|
|
|
.keyboardShortcut("z", modifiers: .command)
|
2026-02-19 14:29:53 +00:00
|
|
|
case .newTab:
|
|
|
|
|
Button(action: { viewModel.addNewTab() }) {
|
|
|
|
|
Label("New Tab", systemImage: "plus.square.on.square")
|
|
|
|
|
}
|
2026-03-08 14:31:01 +00:00
|
|
|
case .closeAllTabs:
|
|
|
|
|
Button(action: { requestCloseAllTabsFromToolbar() }) {
|
|
|
|
|
Label("Close All Tabs", systemImage: "xmark.square")
|
|
|
|
|
}
|
|
|
|
|
.disabled(viewModel.tabs.isEmpty)
|
2026-02-19 14:29:53 +00:00
|
|
|
case .saveFile:
|
|
|
|
|
Button(action: { saveCurrentTabFromToolbar() }) {
|
|
|
|
|
Label("Save File", systemImage: "square.and.arrow.down")
|
|
|
|
|
}
|
|
|
|
|
.disabled(viewModel.selectedTab == nil)
|
2026-03-15 16:37:20 +00:00
|
|
|
case .codeSnapshot:
|
|
|
|
|
Button(action: { presentCodeSnapshotComposer() }) {
|
|
|
|
|
Label("Create Code Snapshot", systemImage: "camera.viewfinder")
|
|
|
|
|
}
|
|
|
|
|
.disabled(!canCreateCodeSnapshot)
|
2026-02-28 19:48:06 +00:00
|
|
|
case .markdownPreview:
|
|
|
|
|
Button(action: { toggleMarkdownPreviewFromToolbar() }) {
|
|
|
|
|
Label(
|
|
|
|
|
"Markdown Preview",
|
|
|
|
|
systemImage: showMarkdownPreviewPane ? "doc.richtext.fill" : "doc.richtext"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
.disabled(currentLanguage != "markdown")
|
|
|
|
|
case .fontDecrease:
|
|
|
|
|
Button(action: { adjustEditorFontSize(-1) }) {
|
|
|
|
|
Label("Font -", systemImage: "textformat.size.smaller")
|
|
|
|
|
}
|
|
|
|
|
case .fontIncrease:
|
|
|
|
|
Button(action: { adjustEditorFontSize(1) }) {
|
|
|
|
|
Label("Font +", systemImage: "textformat.size.larger")
|
|
|
|
|
}
|
2026-02-19 14:29:53 +00:00
|
|
|
case .toggleSidebar:
|
|
|
|
|
Button(action: { toggleSidebarFromToolbar() }) {
|
|
|
|
|
Label("Toggle Sidebar", systemImage: "sidebar.left")
|
|
|
|
|
}
|
|
|
|
|
case .toggleProjectSidebar:
|
2026-02-20 01:16:58 +00:00
|
|
|
Button(action: { toggleProjectSidebarFromToolbar() }) {
|
2026-02-19 14:29:53 +00:00
|
|
|
Label("Toggle Project Structure Sidebar", systemImage: "sidebar.right")
|
|
|
|
|
}
|
|
|
|
|
case .findReplace:
|
|
|
|
|
Button(action: { showFindReplace = true }) {
|
|
|
|
|
Label("Find & Replace", systemImage: "magnifyingglass")
|
|
|
|
|
}
|
|
|
|
|
case .settings:
|
2026-02-27 17:13:12 +00:00
|
|
|
Button(action: { openSettings() }) {
|
2026-02-19 14:29:53 +00:00
|
|
|
Label("Settings", systemImage: "gearshape")
|
|
|
|
|
}
|
|
|
|
|
case .codeCompletion:
|
|
|
|
|
Button(action: { toggleAutoCompletion() }) {
|
|
|
|
|
Label(isAutoCompletionEnabled ? "Disable Code Completion" : "Enable Code Completion", systemImage: "text.badge.plus")
|
|
|
|
|
}
|
|
|
|
|
case .performanceMode:
|
|
|
|
|
Button(action: {
|
|
|
|
|
forceLargeFileMode.toggle()
|
|
|
|
|
updateLargeFileMode(for: currentContentBinding.wrappedValue)
|
|
|
|
|
}) {
|
|
|
|
|
Label(forceLargeFileMode ? "Disable Performance Mode" : "Enable Performance Mode", systemImage: "speedometer")
|
|
|
|
|
}
|
|
|
|
|
case .lineWrap:
|
|
|
|
|
Button(action: { viewModel.isLineWrapEnabled.toggle() }) {
|
|
|
|
|
Label("Enable Wrap / Disable Wrap", systemImage: "text.justify")
|
|
|
|
|
}
|
|
|
|
|
case .keyboardAccessory:
|
2026-02-20 01:16:58 +00:00
|
|
|
Button(action: { toggleKeyboardAccessoryBar() }) {
|
2026-02-19 14:29:53 +00:00
|
|
|
Label(
|
|
|
|
|
showKeyboardAccessoryBarIOS ? "Hide Keyboard Snippet Bar" : "Show Keyboard Snippet Bar",
|
|
|
|
|
systemImage: showKeyboardAccessoryBarIOS ? "keyboard.chevron.compact.down.fill" : "keyboard.chevron.compact.down"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
case .clearEditor:
|
|
|
|
|
Button(action: { requestClearEditorContent() }) {
|
2026-02-20 16:37:44 +00:00
|
|
|
Label("Clear Editor", systemImage: "eraser")
|
2026-02-19 14:29:53 +00:00
|
|
|
}
|
|
|
|
|
case .insertTemplate:
|
|
|
|
|
Button(action: { insertTemplateForCurrentLanguage() }) {
|
|
|
|
|
Label("Insert Template", systemImage: "doc.badge.plus")
|
|
|
|
|
}
|
|
|
|
|
case .brainDump:
|
|
|
|
|
Button(action: {
|
2026-02-20 10:27:55 +00:00
|
|
|
toggleBrainDumpModeIOSAware()
|
2026-02-19 14:29:53 +00:00
|
|
|
}) {
|
|
|
|
|
Label("Brain Dump Mode", systemImage: "note.text")
|
|
|
|
|
}
|
|
|
|
|
case .welcomeTour:
|
|
|
|
|
Button(action: { showWelcomeTour = true }) {
|
|
|
|
|
Label("Welcome Tour", systemImage: "sparkles.rectangle.stack")
|
|
|
|
|
}
|
|
|
|
|
case .translucentWindow:
|
|
|
|
|
Button(action: {
|
|
|
|
|
enableTranslucentWindow.toggle()
|
|
|
|
|
UserDefaults.standard.set(enableTranslucentWindow, forKey: "EnableTranslucentWindow")
|
|
|
|
|
NotificationCenter.default.post(name: .toggleTranslucencyRequested, object: enableTranslucentWindow)
|
|
|
|
|
}) {
|
|
|
|
|
Label("Translucent Window Background", systemImage: enableTranslucentWindow ? "rectangle.fill" : "rectangle")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-20 00:31:14 +00:00
|
|
|
|
2026-02-21 19:12:47 +00:00
|
|
|
Button(action: { saveCurrentTabAsFromToolbar() }) {
|
|
|
|
|
Label("Save As…", systemImage: "square.and.arrow.down.on.square")
|
|
|
|
|
}
|
|
|
|
|
.disabled(viewModel.selectedTab == nil)
|
|
|
|
|
.keyboardShortcut("s", modifiers: [.command, .shift])
|
|
|
|
|
|
2026-02-20 10:27:55 +00:00
|
|
|
Button(action: { dismissKeyboard() }) {
|
|
|
|
|
Label("Hide Keyboard", systemImage: "keyboard.chevron.compact.down")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:31:14 +00:00
|
|
|
Button(action: { toolbarIconsBlueIOS.toggle() }) {
|
|
|
|
|
Label("Blue Toolbar Icons", systemImage: toolbarIconsBlueIOS ? "checkmark.circle.fill" : "circle")
|
|
|
|
|
}
|
2026-02-19 14:29:53 +00:00
|
|
|
} label: {
|
|
|
|
|
Image(systemName: "ellipsis.circle")
|
|
|
|
|
}
|
|
|
|
|
.help("More Actions")
|
|
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
|
private var moreActionsControl: some View {
|
|
|
|
|
Menu {
|
2026-02-16 21:12:55 +00:00
|
|
|
Button(action: {
|
2026-02-27 17:13:12 +00:00
|
|
|
openSettings()
|
2026-02-16 21:12:55 +00:00
|
|
|
}) {
|
|
|
|
|
Label("Settings", systemImage: "gearshape")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Button(action: {
|
|
|
|
|
requestClearEditorContent()
|
|
|
|
|
}) {
|
2026-02-20 16:37:44 +00:00
|
|
|
Label("Clear Editor", systemImage: "eraser")
|
2026-02-16 21:12:55 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-09 10:21:50 +00:00
|
|
|
Button(action: { insertTemplateForCurrentLanguage() }) {
|
|
|
|
|
Label("Insert Template", systemImage: "doc.badge.plus")
|
|
|
|
|
}
|
2026-03-04 19:40:17 +00:00
|
|
|
|
|
|
|
|
Button(action: { presentLanguageSearchSheet() }) {
|
|
|
|
|
Label("Language…", systemImage: "magnifyingglass")
|
|
|
|
|
}
|
|
|
|
|
.keyboardShortcut("l", modifiers: [.command, .shift])
|
2026-02-09 10:21:50 +00:00
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
Button(action: { openFileFromToolbar() }) {
|
|
|
|
|
Label("Open File…", systemImage: "folder")
|
|
|
|
|
}
|
2026-02-16 19:02:43 +00:00
|
|
|
.keyboardShortcut("o", modifiers: .command)
|
2026-02-07 10:51:52 +00:00
|
|
|
|
2026-02-22 12:38:31 +00:00
|
|
|
Button(action: { undoFromToolbar() }) {
|
|
|
|
|
Label("Undo", systemImage: "arrow.uturn.backward")
|
|
|
|
|
}
|
|
|
|
|
.keyboardShortcut("z", modifiers: .command)
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
Button(action: { saveCurrentTabFromToolbar() }) {
|
|
|
|
|
Label("Save File", systemImage: "square.and.arrow.down")
|
|
|
|
|
}
|
|
|
|
|
.disabled(viewModel.selectedTab == nil)
|
2026-02-16 19:02:43 +00:00
|
|
|
.keyboardShortcut("s", modifiers: .command)
|
2026-02-07 10:51:52 +00:00
|
|
|
|
2026-03-15 16:37:20 +00:00
|
|
|
Button(action: { presentCodeSnapshotComposer() }) {
|
|
|
|
|
Label("Create Code Snapshot", systemImage: "camera.viewfinder")
|
|
|
|
|
}
|
|
|
|
|
.disabled(!canCreateCodeSnapshot)
|
|
|
|
|
|
2026-03-08 14:31:01 +00:00
|
|
|
Button(action: { toggleMarkdownPreviewFromToolbar() }) {
|
|
|
|
|
Label(
|
|
|
|
|
"Markdown Preview",
|
|
|
|
|
systemImage: showMarkdownPreviewPane ? "doc.richtext.fill" : "doc.richtext"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
.disabled(currentLanguage != "markdown")
|
|
|
|
|
|
|
|
|
|
Button(action: { requestCloseAllTabsFromToolbar() }) {
|
|
|
|
|
Label("Close All Tabs", systemImage: "xmark.square")
|
|
|
|
|
}
|
|
|
|
|
.disabled(viewModel.tabs.isEmpty)
|
|
|
|
|
|
2026-02-21 19:12:47 +00:00
|
|
|
Button(action: { saveCurrentTabAsFromToolbar() }) {
|
|
|
|
|
Label("Save As…", systemImage: "square.and.arrow.down.on.square")
|
|
|
|
|
}
|
|
|
|
|
.disabled(viewModel.selectedTab == nil)
|
|
|
|
|
.keyboardShortcut("s", modifiers: [.command, .shift])
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
Button(action: { toggleSidebarFromToolbar() }) {
|
|
|
|
|
Label("Toggle Sidebar", systemImage: "sidebar.left")
|
|
|
|
|
}
|
2026-02-16 19:02:43 +00:00
|
|
|
.keyboardShortcut("s", modifiers: [.command, .option])
|
2026-02-07 10:51:52 +00:00
|
|
|
|
2026-02-20 01:16:58 +00:00
|
|
|
Button(action: { toggleProjectSidebarFromToolbar() }) {
|
2026-02-07 10:51:52 +00:00
|
|
|
Label("Toggle Project Structure Sidebar", systemImage: "sidebar.right")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Button(action: { showFindReplace = true }) {
|
|
|
|
|
Label("Find & Replace", systemImage: "magnifyingglass")
|
|
|
|
|
}
|
2026-02-16 19:02:43 +00:00
|
|
|
.keyboardShortcut("f", modifiers: .command)
|
|
|
|
|
|
|
|
|
|
Button(action: { viewModel.isLineWrapEnabled.toggle() }) {
|
|
|
|
|
Label("Enable Wrap / Disable Wrap", systemImage: "text.justify")
|
|
|
|
|
}
|
|
|
|
|
.keyboardShortcut("l", modifiers: [.command, .option])
|
2026-02-07 10:51:52 +00:00
|
|
|
|
2026-02-18 22:56:46 +00:00
|
|
|
Button(action: { toggleAutoCompletion() }) {
|
|
|
|
|
Label(isAutoCompletionEnabled ? "Disable Code Completion" : "Enable Code Completion", systemImage: "text.badge.plus")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Button(action: {
|
2026-02-20 01:16:58 +00:00
|
|
|
toggleKeyboardAccessoryBar()
|
2026-02-18 22:56:46 +00:00
|
|
|
}) {
|
|
|
|
|
Label(
|
|
|
|
|
showKeyboardAccessoryBarIOS ? "Hide Keyboard Snippet Bar" : "Show Keyboard Snippet Bar",
|
|
|
|
|
systemImage: showKeyboardAccessoryBarIOS ? "keyboard.chevron.compact.down.fill" : "keyboard.chevron.compact.down"
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 10:27:55 +00:00
|
|
|
Button(action: { dismissKeyboard() }) {
|
|
|
|
|
Label("Hide Keyboard", systemImage: "keyboard.chevron.compact.down")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 22:56:46 +00:00
|
|
|
Button(action: {
|
|
|
|
|
forceLargeFileMode.toggle()
|
|
|
|
|
updateLargeFileMode(for: currentContentBinding.wrappedValue)
|
|
|
|
|
}) {
|
|
|
|
|
Label(forceLargeFileMode ? "Disable Performance Mode" : "Enable Performance Mode", systemImage: "speedometer")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
Button(action: {
|
2026-02-20 10:27:55 +00:00
|
|
|
toggleBrainDumpModeIOSAware()
|
2026-02-07 10:51:52 +00:00
|
|
|
}) {
|
|
|
|
|
Label("Brain Dump Mode", systemImage: "note.text")
|
|
|
|
|
}
|
2026-02-12 23:28:35 +00:00
|
|
|
|
|
|
|
|
Button(action: {
|
|
|
|
|
showWelcomeTour = true
|
|
|
|
|
}) {
|
|
|
|
|
Label("Welcome Tour", systemImage: "sparkles.rectangle.stack")
|
|
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
|
|
|
|
|
Button(action: {
|
|
|
|
|
enableTranslucentWindow.toggle()
|
|
|
|
|
UserDefaults.standard.set(enableTranslucentWindow, forKey: "EnableTranslucentWindow")
|
|
|
|
|
NotificationCenter.default.post(name: .toggleTranslucencyRequested, object: enableTranslucentWindow)
|
|
|
|
|
}) {
|
|
|
|
|
Label("Translucent Window Background", systemImage: enableTranslucentWindow ? "rectangle.fill" : "rectangle")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 00:31:14 +00:00
|
|
|
Button(action: {
|
|
|
|
|
toolbarIconsBlueIOS.toggle()
|
|
|
|
|
}) {
|
|
|
|
|
Label("Blue Toolbar Icons", systemImage: toolbarIconsBlueIOS ? "checkmark.circle.fill" : "circle")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
} label: {
|
|
|
|
|
Image(systemName: "ellipsis.circle")
|
|
|
|
|
}
|
|
|
|
|
.help("More Actions")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
|
private var iOSToolbarControls: some View {
|
2026-02-16 19:02:43 +00:00
|
|
|
openFileControl
|
2026-02-22 12:38:31 +00:00
|
|
|
undoControl
|
2026-02-18 22:56:46 +00:00
|
|
|
if iPhonePromotedActionsCount >= 2 { newTabControl }
|
|
|
|
|
if iPhonePromotedActionsCount >= 3 { saveFileControl }
|
|
|
|
|
if iPhonePromotedActionsCount >= 4 { findReplaceControl }
|
|
|
|
|
keyboardAccessoryControl
|
2026-03-26 18:19:45 +00:00
|
|
|
iOSVerticalSurfaceDivider
|
2026-02-19 23:10:51 +00:00
|
|
|
moreActionsControl
|
2026-02-18 22:56:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ViewBuilder
|
|
|
|
|
private var iPhonePrimaryToolbarCluster: some View {
|
|
|
|
|
GlassSurface(
|
|
|
|
|
enabled: shouldUseLiquidGlass,
|
|
|
|
|
material: primaryGlassMaterial,
|
|
|
|
|
fallbackColor: toolbarFallbackColor,
|
2026-02-19 23:10:51 +00:00
|
|
|
shape: .capsule,
|
|
|
|
|
chromeStyle: iOSToolbarChromeStyle
|
2026-02-18 22:56:46 +00:00
|
|
|
) {
|
|
|
|
|
HStack(spacing: 12) {
|
|
|
|
|
languagePickerControl
|
|
|
|
|
iOSToolbarControls
|
|
|
|
|
}
|
|
|
|
|
.padding(.horizontal, 12)
|
|
|
|
|
.padding(.vertical, 8)
|
|
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-20 10:27:55 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
var iPhoneUnifiedToolbarRow: some View {
|
|
|
|
|
iPhonePrimaryToolbarCluster
|
|
|
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
|
|
|
|
.scaleEffect(toolbarDensityScale)
|
|
|
|
|
.opacity(toolbarDensityOpacity)
|
|
|
|
|
.animation(.easeOut(duration: 0.18), value: toolbarDensityScale)
|
|
|
|
|
.animation(.easeOut(duration: 0.18), value: toolbarDensityOpacity)
|
|
|
|
|
.tint(iOSToolbarTintColor)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 10:51:52 +00:00
|
|
|
@ViewBuilder
|
|
|
|
|
private var iPadDistributedToolbarControls: some View {
|
|
|
|
|
languagePickerControl
|
2026-02-19 14:29:53 +00:00
|
|
|
ForEach(iPadPromotedActions, id: \.self) { action in
|
|
|
|
|
iPadToolbarActionControl(action)
|
2026-03-03 09:47:45 +00:00
|
|
|
.frame(minWidth: 40, minHeight: 40)
|
|
|
|
|
.contentShape(Rectangle())
|
2026-02-19 14:29:53 +00:00
|
|
|
}
|
2026-02-28 19:48:06 +00:00
|
|
|
ForEach(iPadAlwaysVisibleActions, id: \.self) { action in
|
|
|
|
|
iPadToolbarActionControl(action)
|
2026-03-03 09:47:45 +00:00
|
|
|
.frame(minWidth: 40, minHeight: 40)
|
|
|
|
|
.contentShape(Rectangle())
|
2026-02-28 19:48:06 +00:00
|
|
|
}
|
2026-02-19 14:29:53 +00:00
|
|
|
if !iPadOverflowActions.isEmpty {
|
2026-03-26 18:19:45 +00:00
|
|
|
iOSVerticalSurfaceDivider
|
2026-02-19 14:29:53 +00:00
|
|
|
.padding(.horizontal, 2)
|
|
|
|
|
iPadOverflowMenuControl
|
|
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
2026-02-11 10:20:17 +00:00
|
|
|
#endif
|
2026-03-04 19:40:17 +00:00
|
|
|
|
|
|
|
|
private func toolbarCompactLanguageLabel(_ lang: String) -> String {
|
|
|
|
|
switch lang {
|
|
|
|
|
case "swift": return "Sw"
|
|
|
|
|
case "python": return "Py"
|
|
|
|
|
case "javascript": return "JS"
|
|
|
|
|
case "typescript": return "TS"
|
|
|
|
|
case "php": return "PHP"
|
|
|
|
|
case "java": return "Jv"
|
|
|
|
|
case "kotlin": return "Kt"
|
|
|
|
|
case "go": return "Go"
|
|
|
|
|
case "ruby": return "Rb"
|
|
|
|
|
case "rust": return "Rs"
|
|
|
|
|
case "cobol": return "Cob"
|
|
|
|
|
case "dotenv": return "Env"
|
|
|
|
|
case "proto": return "Prt"
|
|
|
|
|
case "graphql": return "GQL"
|
|
|
|
|
case "rst": return "RST"
|
|
|
|
|
case "nginx": return "Ngnx"
|
|
|
|
|
case "sql": return "SQL"
|
|
|
|
|
case "html": return "HTML"
|
|
|
|
|
case "expressionengine": return "EE"
|
|
|
|
|
case "css": return "CSS"
|
|
|
|
|
case "c": return "C"
|
|
|
|
|
case "cpp": return "C++"
|
|
|
|
|
case "csharp": return "C#"
|
|
|
|
|
case "objective-c": return "ObjC"
|
|
|
|
|
case "json": return "JSON"
|
|
|
|
|
case "xml": return "XML"
|
|
|
|
|
case "yaml": return "YML"
|
|
|
|
|
case "toml": return "TML"
|
|
|
|
|
case "csv": return "CSV"
|
|
|
|
|
case "ini": return "INI"
|
|
|
|
|
case "vim": return "Vim"
|
|
|
|
|
case "log": return "Log"
|
|
|
|
|
case "ipynb": return "JNB"
|
|
|
|
|
case "markdown": return "MD"
|
2026-03-15 17:51:17 +00:00
|
|
|
case "tex": return "TeX"
|
2026-03-04 19:40:17 +00:00
|
|
|
case "bash": return "Sh"
|
|
|
|
|
case "zsh": return "zsh"
|
|
|
|
|
case "powershell": return "PS"
|
|
|
|
|
case "standard": return "Std"
|
|
|
|
|
case "plain": return "Txt"
|
|
|
|
|
default: return lang.capitalized
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 18:59:53 +00:00
|
|
|
@ToolbarContentBuilder
|
|
|
|
|
var editorToolbarContent: some ToolbarContent {
|
2026-02-07 10:51:52 +00:00
|
|
|
#if os(iOS)
|
2026-02-16 21:12:55 +00:00
|
|
|
if isIPadToolbarLayout {
|
2026-02-20 00:31:14 +00:00
|
|
|
if #available(iOS 26.0, *) {
|
|
|
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
|
|
|
GlassSurface(
|
|
|
|
|
enabled: shouldUseLiquidGlass,
|
|
|
|
|
material: primaryGlassMaterial,
|
|
|
|
|
fallbackColor: toolbarFallbackColor,
|
|
|
|
|
shape: .capsule,
|
|
|
|
|
chromeStyle: iOSToolbarChromeStyle
|
|
|
|
|
) {
|
|
|
|
|
HStack(spacing: 6) {
|
|
|
|
|
iPadDistributedToolbarControls
|
|
|
|
|
}
|
|
|
|
|
.padding(.horizontal, 8)
|
|
|
|
|
.padding(.vertical, 8)
|
2026-02-21 19:12:47 +00:00
|
|
|
.frame(maxWidth: iPadToolbarMaxWidth, alignment: .center)
|
2026-02-18 22:56:46 +00:00
|
|
|
}
|
2026-02-21 19:12:47 +00:00
|
|
|
.scaleEffect(toolbarDensityScale, anchor: .center)
|
2026-02-20 00:31:14 +00:00
|
|
|
.opacity(toolbarDensityOpacity)
|
|
|
|
|
.animation(.easeOut(duration: 0.18), value: toolbarDensityScale)
|
|
|
|
|
.animation(.easeOut(duration: 0.18), value: toolbarDensityOpacity)
|
|
|
|
|
.tint(iOSToolbarTintColor)
|
|
|
|
|
}
|
|
|
|
|
.sharedBackgroundVisibility(.hidden)
|
|
|
|
|
} else {
|
|
|
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
|
|
|
GlassSurface(
|
|
|
|
|
enabled: shouldUseLiquidGlass,
|
|
|
|
|
material: primaryGlassMaterial,
|
|
|
|
|
fallbackColor: toolbarFallbackColor,
|
|
|
|
|
shape: .capsule,
|
|
|
|
|
chromeStyle: iOSToolbarChromeStyle
|
|
|
|
|
) {
|
|
|
|
|
HStack(spacing: 6) {
|
|
|
|
|
iPadDistributedToolbarControls
|
|
|
|
|
}
|
|
|
|
|
.padding(.horizontal, 8)
|
|
|
|
|
.padding(.vertical, 8)
|
2026-02-21 19:12:47 +00:00
|
|
|
.frame(maxWidth: iPadToolbarMaxWidth, alignment: .center)
|
2026-02-20 00:31:14 +00:00
|
|
|
}
|
2026-02-21 19:12:47 +00:00
|
|
|
.scaleEffect(toolbarDensityScale, anchor: .center)
|
2026-02-20 00:31:14 +00:00
|
|
|
.opacity(toolbarDensityOpacity)
|
|
|
|
|
.animation(.easeOut(duration: 0.18), value: toolbarDensityScale)
|
|
|
|
|
.animation(.easeOut(duration: 0.18), value: toolbarDensityOpacity)
|
|
|
|
|
.tint(iOSToolbarTintColor)
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
2026-02-16 21:12:55 +00:00
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
}
|
|
|
|
|
#else
|
2026-03-06 18:13:00 +00:00
|
|
|
ToolbarItemGroup(placement: .primaryAction) {
|
|
|
|
|
Button(action: { openFileFromToolbar() }) {
|
|
|
|
|
Label("Open", systemImage: "folder")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-03-06 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
.help("Open File… (Cmd+O)")
|
|
|
|
|
|
|
|
|
|
Button(action: { viewModel.addNewTab() }) {
|
|
|
|
|
Label("New Tab", systemImage: "plus.square.on.square")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-03-06 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
.help("New Tab (Cmd+T)")
|
|
|
|
|
|
2026-03-08 14:31:01 +00:00
|
|
|
Button(action: { requestCloseAllTabsFromToolbar() }) {
|
|
|
|
|
Label("Close All Tabs", systemImage: "xmark.square")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-03-08 14:31:01 +00:00
|
|
|
}
|
|
|
|
|
.help("Close All Tabs")
|
|
|
|
|
|
2026-03-06 18:13:00 +00:00
|
|
|
Button(action: {
|
|
|
|
|
saveCurrentTabFromToolbar()
|
|
|
|
|
}) {
|
|
|
|
|
Label("Save", systemImage: "square.and.arrow.down")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-03-06 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
.disabled(viewModel.selectedTab == nil)
|
|
|
|
|
.help("Save File (Cmd+S)")
|
|
|
|
|
|
2026-03-15 17:51:17 +00:00
|
|
|
Button(action: { presentCodeSnapshotComposer() }) {
|
|
|
|
|
Label("Code Snapshot", systemImage: "camera.viewfinder")
|
|
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
|
|
|
|
}
|
|
|
|
|
.disabled(!canCreateCodeSnapshot)
|
|
|
|
|
.help("Create Code Snapshot from Selection")
|
|
|
|
|
|
2026-03-06 18:13:00 +00:00
|
|
|
Button(action: {
|
|
|
|
|
showFindReplace = true
|
|
|
|
|
}) {
|
|
|
|
|
Label("Find", systemImage: "magnifyingglass")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-03-06 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
.help("Find & Replace (Cmd+F)")
|
|
|
|
|
|
|
|
|
|
Button(action: {
|
|
|
|
|
openSettings()
|
|
|
|
|
}) {
|
|
|
|
|
Label("Settings", systemImage: "gearshape")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-03-06 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
.help("Settings")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 18:59:53 +00:00
|
|
|
ToolbarItemGroup(placement: .automatic) {
|
2026-03-04 19:40:17 +00:00
|
|
|
Menu {
|
2026-03-26 18:19:45 +00:00
|
|
|
let selectedLanguage = currentLanguagePickerBinding.wrappedValue
|
|
|
|
|
Button {
|
|
|
|
|
currentLanguagePickerBinding.wrappedValue = selectedLanguage
|
|
|
|
|
} label: {
|
|
|
|
|
Label(languageLabel(for: selectedLanguage), systemImage: "checkmark")
|
|
|
|
|
}
|
|
|
|
|
Button(action: { presentLanguageSearchSheet() }) {
|
|
|
|
|
Label("Language…", systemImage: "magnifyingglass")
|
|
|
|
|
}
|
|
|
|
|
Divider()
|
|
|
|
|
ForEach(languageOptions.filter { $0 != selectedLanguage }, id: \.self) { lang in
|
2026-03-04 19:40:17 +00:00
|
|
|
Button {
|
|
|
|
|
currentLanguagePickerBinding.wrappedValue = lang
|
|
|
|
|
} label: {
|
2026-03-26 18:19:45 +00:00
|
|
|
Text(languageLabel(for: lang))
|
2026-03-04 19:40:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} label: {
|
|
|
|
|
Text(toolbarCompactLanguageLabel(currentLanguagePickerBinding.wrappedValue))
|
|
|
|
|
.lineLimit(1)
|
|
|
|
|
.truncationMode(.tail)
|
2026-02-06 18:59:53 +00:00
|
|
|
}
|
|
|
|
|
.labelsHidden()
|
|
|
|
|
.help("Language")
|
|
|
|
|
.controlSize(.large)
|
2026-03-04 19:40:17 +00:00
|
|
|
.frame(width: 92)
|
2026-02-06 18:59:53 +00:00
|
|
|
.padding(.vertical, 2)
|
|
|
|
|
|
2026-03-04 19:40:17 +00:00
|
|
|
Button(action: { presentLanguageSearchSheet() }) {
|
|
|
|
|
Image(systemName: "magnifyingglass")
|
|
|
|
|
}
|
|
|
|
|
.keyboardShortcut("l", modifiers: [.command, .shift])
|
|
|
|
|
.help("Language… (Cmd+Shift+L)")
|
|
|
|
|
|
2026-02-24 14:44:43 +00:00
|
|
|
Text(providerBadgeLabelText)
|
2026-02-06 18:59:53 +00:00
|
|
|
.font(.caption)
|
2026-02-24 14:44:43 +00:00
|
|
|
.foregroundColor(providerBadgeForegroundColor)
|
2026-02-12 09:15:40 +00:00
|
|
|
.lineLimit(1)
|
|
|
|
|
.truncationMode(.tail)
|
|
|
|
|
.minimumScaleFactor(0.9)
|
|
|
|
|
.fixedSize(horizontal: true, vertical: false)
|
2026-02-06 18:59:53 +00:00
|
|
|
.padding(.horizontal, 6)
|
|
|
|
|
.padding(.vertical, 2)
|
2026-02-24 14:44:43 +00:00
|
|
|
.background(providerBadgeBackgroundColor, in: Capsule())
|
2026-02-12 23:28:35 +00:00
|
|
|
.padding(.leading, 6)
|
2026-02-24 14:44:43 +00:00
|
|
|
.help(providerBadgeTooltip)
|
2026-02-06 18:59:53 +00:00
|
|
|
|
2026-02-28 19:48:06 +00:00
|
|
|
#if os(macOS) || os(iOS)
|
|
|
|
|
if canShowMarkdownPreviewPane {
|
|
|
|
|
Button(action: {
|
|
|
|
|
toggleMarkdownPreviewFromToolbar()
|
|
|
|
|
}) {
|
|
|
|
|
Label("Markdown Preview", systemImage: showMarkdownPreviewPane ? "doc.richtext.fill" : "doc.richtext")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-24 14:44:43 +00:00
|
|
|
}
|
2026-02-28 19:48:06 +00:00
|
|
|
.disabled(currentLanguage != "markdown")
|
|
|
|
|
.help("Toggle Markdown Preview")
|
|
|
|
|
|
|
|
|
|
if showMarkdownPreviewPane && currentLanguage == "markdown" {
|
|
|
|
|
Menu {
|
|
|
|
|
Button("Default") { markdownPreviewTemplateRaw = "default" }
|
|
|
|
|
Button("Docs") { markdownPreviewTemplateRaw = "docs" }
|
|
|
|
|
Button("Article") { markdownPreviewTemplateRaw = "article" }
|
|
|
|
|
Button("Compact") { markdownPreviewTemplateRaw = "compact" }
|
2026-03-17 17:40:32 +00:00
|
|
|
Divider()
|
|
|
|
|
Button("GitHub Docs") { markdownPreviewTemplateRaw = "github-docs" }
|
|
|
|
|
Button("Academic Paper") { markdownPreviewTemplateRaw = "academic-paper" }
|
|
|
|
|
Button("Terminal Notes") { markdownPreviewTemplateRaw = "terminal-notes" }
|
|
|
|
|
Button("Magazine") { markdownPreviewTemplateRaw = "magazine" }
|
|
|
|
|
Button("Minimal Reader") { markdownPreviewTemplateRaw = "minimal-reader" }
|
|
|
|
|
Button("Presentation") { markdownPreviewTemplateRaw = "presentation" }
|
|
|
|
|
Button("Night Contrast") { markdownPreviewTemplateRaw = "night-contrast" }
|
|
|
|
|
Button("Warm Sepia") { markdownPreviewTemplateRaw = "warm-sepia" }
|
|
|
|
|
Button("Dense Compact") { markdownPreviewTemplateRaw = "dense-compact" }
|
|
|
|
|
Button("Developer Spec") { markdownPreviewTemplateRaw = "developer-spec" }
|
2026-02-28 19:48:06 +00:00
|
|
|
} label: {
|
|
|
|
|
Label("Preview Style", systemImage: "textformat.size")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-28 19:48:06 +00:00
|
|
|
}
|
|
|
|
|
.help("Markdown Preview Template")
|
|
|
|
|
}
|
2026-02-24 14:44:43 +00:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-02-22 12:38:31 +00:00
|
|
|
Button(action: { undoFromToolbar() }) {
|
|
|
|
|
Label("Undo", systemImage: "arrow.uturn.backward")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-22 12:38:31 +00:00
|
|
|
}
|
|
|
|
|
.help("Undo (Cmd+Z)")
|
|
|
|
|
.keyboardShortcut("z", modifiers: .command)
|
|
|
|
|
|
2026-02-14 13:24:01 +00:00
|
|
|
if ReleaseRuntimePolicy.isUpdaterEnabledForCurrentDistribution {
|
|
|
|
|
Button(action: {
|
|
|
|
|
showUpdaterDialog(checkNow: true)
|
|
|
|
|
}) {
|
2026-02-20 16:20:08 +00:00
|
|
|
Label("Updates", systemImage: "arrow.triangle.2.circlepath.circle")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-14 13:24:01 +00:00
|
|
|
}
|
|
|
|
|
.help("Check for Updates")
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 14:44:43 +00:00
|
|
|
#if os(macOS)
|
2026-02-06 18:59:53 +00:00
|
|
|
Button(action: {
|
2026-02-24 14:44:43 +00:00
|
|
|
openWindow(id: "blank-window")
|
2026-02-09 10:21:50 +00:00
|
|
|
}) {
|
2026-02-24 14:44:43 +00:00
|
|
|
Label("New Window", systemImage: "macwindow.badge.plus")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-09 10:21:50 +00:00
|
|
|
}
|
2026-02-24 14:44:43 +00:00
|
|
|
.help("New Window (Cmd+N)")
|
|
|
|
|
#endif
|
2026-02-09 10:21:50 +00:00
|
|
|
|
2026-02-24 14:44:43 +00:00
|
|
|
Button(action: { adjustEditorFontSize(-1) }) {
|
|
|
|
|
Label("Font -", systemImage: "textformat.size.smaller")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-06 18:59:53 +00:00
|
|
|
}
|
2026-02-24 14:44:43 +00:00
|
|
|
.help("Decrease Font Size")
|
2026-02-06 18:59:53 +00:00
|
|
|
|
2026-02-24 14:44:43 +00:00
|
|
|
Button(action: { adjustEditorFontSize(1) }) {
|
|
|
|
|
Label("Font +", systemImage: "textformat.size.larger")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-07 18:46:08 +00:00
|
|
|
}
|
2026-02-24 14:44:43 +00:00
|
|
|
.help("Increase Font Size")
|
2026-02-07 18:46:08 +00:00
|
|
|
|
2026-02-06 18:59:53 +00:00
|
|
|
Button(action: {
|
2026-02-24 14:44:43 +00:00
|
|
|
requestClearEditorContent()
|
2026-02-06 18:59:53 +00:00
|
|
|
}) {
|
2026-02-24 14:44:43 +00:00
|
|
|
Label("Clear", systemImage: "eraser")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-06 18:59:53 +00:00
|
|
|
}
|
2026-02-24 14:44:43 +00:00
|
|
|
.help("Clear Editor")
|
2026-02-06 18:59:53 +00:00
|
|
|
|
|
|
|
|
Button(action: {
|
2026-02-24 14:44:43 +00:00
|
|
|
insertTemplateForCurrentLanguage()
|
2026-02-06 18:59:53 +00:00
|
|
|
}) {
|
2026-02-24 14:44:43 +00:00
|
|
|
Label("Template", systemImage: "doc.badge.plus")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-06 18:59:53 +00:00
|
|
|
}
|
2026-02-24 14:44:43 +00:00
|
|
|
.help("Insert Template for Current Language")
|
2026-02-06 18:59:53 +00:00
|
|
|
|
|
|
|
|
Button(action: {
|
2026-02-07 10:51:52 +00:00
|
|
|
toggleSidebarFromToolbar()
|
2026-02-06 18:59:53 +00:00
|
|
|
}) {
|
2026-02-20 16:20:08 +00:00
|
|
|
Label("Sidebar", systemImage: "sidebar.left")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-06 19:20:03 +00:00
|
|
|
.symbolVariant(viewModel.showSidebar ? .fill : .none)
|
2026-02-06 18:59:53 +00:00
|
|
|
}
|
2026-02-11 10:20:17 +00:00
|
|
|
.help("Toggle Sidebar (Cmd+Opt+S)")
|
2026-02-06 18:59:53 +00:00
|
|
|
|
|
|
|
|
Button(action: {
|
2026-02-28 18:26:00 +00:00
|
|
|
toggleProjectSidebarFromToolbar()
|
2026-02-06 18:59:53 +00:00
|
|
|
}) {
|
2026-02-20 16:20:08 +00:00
|
|
|
Label("Project", systemImage: "sidebar.right")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-06 18:59:53 +00:00
|
|
|
.symbolVariant(showProjectStructureSidebar ? .fill : .none)
|
|
|
|
|
}
|
|
|
|
|
.help("Toggle Project Structure Sidebar")
|
|
|
|
|
|
|
|
|
|
Button(action: {
|
2026-02-18 18:59:25 +00:00
|
|
|
toggleAutoCompletion()
|
|
|
|
|
}) {
|
2026-02-20 16:20:08 +00:00
|
|
|
Label("AI", systemImage: "bolt.horizontal.circle")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-18 18:59:25 +00:00
|
|
|
.symbolVariant(isAutoCompletionEnabled ? .fill : .none)
|
|
|
|
|
}
|
2026-02-19 08:09:35 +00:00
|
|
|
.help(isAutoCompletionEnabled ? "Disable Code Completion" : "Enable Code Completion")
|
2026-02-18 18:59:25 +00:00
|
|
|
.accessibilityLabel("Code Completion")
|
|
|
|
|
|
|
|
|
|
Button(action: {
|
2026-02-19 08:58:59 +00:00
|
|
|
showBracketHelperBarMac.toggle()
|
|
|
|
|
}) {
|
2026-02-20 16:20:08 +00:00
|
|
|
Label("Brackets", systemImage: "chevron.left.chevron.right")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-19 08:58:59 +00:00
|
|
|
.symbolVariant(showBracketHelperBarMac ? .fill : .none)
|
|
|
|
|
}
|
|
|
|
|
.help(showBracketHelperBarMac ? "Hide Bracket Helper Bar" : "Show Bracket Helper Bar")
|
|
|
|
|
.accessibilityLabel("Bracket Helper Bar")
|
|
|
|
|
|
|
|
|
|
Button(action: {
|
2026-02-06 18:59:53 +00:00
|
|
|
viewModel.isBrainDumpMode.toggle()
|
|
|
|
|
UserDefaults.standard.set(viewModel.isBrainDumpMode, forKey: "BrainDumpModeEnabled")
|
|
|
|
|
}) {
|
2026-02-20 16:20:08 +00:00
|
|
|
Label("Brain Dump", systemImage: "note.text")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-06 18:59:53 +00:00
|
|
|
.symbolVariant(viewModel.isBrainDumpMode ? .fill : .none)
|
|
|
|
|
}
|
|
|
|
|
.help("Brain Dump Mode")
|
|
|
|
|
.accessibilityLabel("Brain Dump Mode")
|
|
|
|
|
|
|
|
|
|
Button(action: {
|
|
|
|
|
enableTranslucentWindow.toggle()
|
|
|
|
|
UserDefaults.standard.set(enableTranslucentWindow, forKey: "EnableTranslucentWindow")
|
|
|
|
|
NotificationCenter.default.post(name: .toggleTranslucencyRequested, object: enableTranslucentWindow)
|
|
|
|
|
}) {
|
2026-02-20 16:20:08 +00:00
|
|
|
Label("Translucency", systemImage: enableTranslucentWindow ? "rectangle.fill" : "rectangle")
|
2026-03-10 15:14:21 +00:00
|
|
|
.foregroundStyle(macToolbarSymbolColor)
|
2026-02-06 18:59:53 +00:00
|
|
|
}
|
|
|
|
|
.help("Toggle Translucent Window Background")
|
|
|
|
|
.accessibilityLabel("Translucent Window Background")
|
|
|
|
|
|
|
|
|
|
}
|
2026-02-07 10:51:52 +00:00
|
|
|
#endif
|
2026-02-06 18:59:53 +00:00
|
|
|
}
|
|
|
|
|
}
|