Add mascot demo. Update assets for readme
This commit is contained in:
Binary file not shown.
@@ -12,14 +12,23 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
||||
private var keyEventMonitor: Any?
|
||||
private let backgroundColor = NSColor.black
|
||||
private let configuredShellPath: String
|
||||
private let launchDirectory: String
|
||||
|
||||
@Published var title: String = "shell"
|
||||
@Published var isRunning: Bool = true
|
||||
@Published var currentDirectory: String?
|
||||
|
||||
init(fontSize: CGFloat, theme: TerminalTheme, shellPath: String) {
|
||||
init(
|
||||
fontSize: CGFloat,
|
||||
theme: TerminalTheme,
|
||||
shellPath: String,
|
||||
initialDirectory: String? = nil,
|
||||
startImmediately: Bool = true
|
||||
) {
|
||||
terminalView = TerminalView(frame: NSRect(x: 0, y: 0, width: 600, height: 300))
|
||||
configuredShellPath = shellPath
|
||||
launchDirectory = Self.resolveInitialDirectory(initialDirectory)
|
||||
currentDirectory = launchDirectory
|
||||
super.init()
|
||||
|
||||
terminalView.terminalDelegate = self
|
||||
@@ -29,7 +38,11 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
||||
applyTheme(theme)
|
||||
installCommandArrowMonitor()
|
||||
|
||||
startShell()
|
||||
if startImmediately {
|
||||
startShell()
|
||||
} else {
|
||||
isRunning = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -52,12 +65,29 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
||||
args: ["-l"],
|
||||
environment: nil,
|
||||
execName: loginExecName,
|
||||
currentDirectory: NSHomeDirectory()
|
||||
currentDirectory: launchDirectory
|
||||
)
|
||||
process = proc
|
||||
title = shellName
|
||||
}
|
||||
|
||||
private static func resolveInitialDirectory(_ directory: String?) -> String {
|
||||
normalizedDirectory(directory) ?? NSHomeDirectory()
|
||||
}
|
||||
|
||||
private static func normalizedDirectory(_ directory: String?) -> String? {
|
||||
let trimmed = directory?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
guard !trimmed.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let url = URL(string: trimmed), url.isFileURL {
|
||||
return url.path(percentEncoded: false)
|
||||
}
|
||||
|
||||
return (trimmed as NSString).expandingTildeInPath
|
||||
}
|
||||
|
||||
private func resolveShell() -> String {
|
||||
let custom = configuredShellPath.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !custom.isEmpty && FileManager.default.isExecutableFile(atPath: custom) {
|
||||
@@ -145,7 +175,8 @@ class TerminalSession: NSObject, ObservableObject, LocalProcessDelegate, @precon
|
||||
}
|
||||
|
||||
func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {
|
||||
currentDirectory = directory
|
||||
guard let normalizedDirectory = Self.normalizedDirectory(directory) else { return }
|
||||
currentDirectory = normalizedDirectory
|
||||
}
|
||||
|
||||
func scrolled(source: TerminalView, position: Double) {}
|
||||
|
||||
@@ -3,12 +3,27 @@ import Combine
|
||||
|
||||
@MainActor
|
||||
protocol TerminalSessionFactoryType {
|
||||
func makeSession(fontSize: CGFloat, theme: TerminalTheme, shellPath: String) -> TerminalSession
|
||||
func makeSession(
|
||||
fontSize: CGFloat,
|
||||
theme: TerminalTheme,
|
||||
shellPath: String,
|
||||
initialDirectory: String?
|
||||
) -> TerminalSession
|
||||
}
|
||||
|
||||
struct LiveTerminalSessionFactory: TerminalSessionFactoryType {
|
||||
func makeSession(fontSize: CGFloat, theme: TerminalTheme, shellPath: String) -> TerminalSession {
|
||||
TerminalSession(fontSize: fontSize, theme: theme, shellPath: shellPath)
|
||||
func makeSession(
|
||||
fontSize: CGFloat,
|
||||
theme: TerminalTheme,
|
||||
shellPath: String,
|
||||
initialDirectory: String?
|
||||
) -> TerminalSession {
|
||||
TerminalSession(
|
||||
fontSize: fontSize,
|
||||
theme: theme,
|
||||
shellPath: shellPath,
|
||||
initialDirectory: initialDirectory
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +105,8 @@ final class WorkspaceController: ObservableObject {
|
||||
let session = sessionFactory.makeSession(
|
||||
fontSize: config.fontSize,
|
||||
theme: config.theme,
|
||||
shellPath: config.shellPath
|
||||
shellPath: config.shellPath,
|
||||
initialDirectory: activeTab?.currentDirectory
|
||||
)
|
||||
|
||||
titleObservers[session.id] = session.$title
|
||||
|
||||
@@ -313,7 +313,12 @@ private final class ScreenRegistryTestSettingsProvider: TerminalSessionConfigura
|
||||
|
||||
private struct ScreenRegistryUnusedTerminalSessionFactory: TerminalSessionFactoryType {
|
||||
@MainActor
|
||||
func makeSession(fontSize: CGFloat, theme: TerminalTheme, shellPath: String) -> TerminalSession {
|
||||
func makeSession(
|
||||
fontSize: CGFloat,
|
||||
theme: TerminalTheme,
|
||||
shellPath: String,
|
||||
initialDirectory: String?
|
||||
) -> TerminalSession {
|
||||
fatalError("ScreenRegistryTests should not create live terminal sessions.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,64 @@ final class WorkspaceRegistryTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class WorkspaceControllerTests: XCTestCase {
|
||||
func testNewTabUsesActiveTabCurrentDirectory() {
|
||||
let factory = RecordingTerminalSessionFactory()
|
||||
let controller = WorkspaceController(
|
||||
summary: WorkspaceSummary(name: "Main"),
|
||||
sessionFactory: factory,
|
||||
settingsProvider: TestSettingsProvider(),
|
||||
bootstrapDefaultTab: false
|
||||
)
|
||||
|
||||
controller.newTab()
|
||||
controller.activeTab?.currentDirectory = "/tmp/Raycast"
|
||||
|
||||
controller.newTab()
|
||||
|
||||
XCTAssertEqual(factory.requestedDirectories, [nil, "/tmp/Raycast"])
|
||||
XCTAssertEqual(controller.activeTab?.currentDirectory, "/tmp/Raycast")
|
||||
XCTAssertEqual(controller.tabs.count, 2)
|
||||
XCTAssertEqual(controller.activeTabIndex, 1)
|
||||
}
|
||||
|
||||
func testNewTabNormalizesCurrentDirectoryFileURL() {
|
||||
let factory = RecordingTerminalSessionFactory()
|
||||
let controller = WorkspaceController(
|
||||
summary: WorkspaceSummary(name: "Main"),
|
||||
sessionFactory: factory,
|
||||
settingsProvider: TestSettingsProvider(),
|
||||
bootstrapDefaultTab: false
|
||||
)
|
||||
let expectedPath = "/tmp/Raycast Folder"
|
||||
|
||||
controller.newTab()
|
||||
controller.activeTab?.currentDirectory = URL(fileURLWithPath: expectedPath).absoluteString
|
||||
|
||||
controller.newTab()
|
||||
|
||||
XCTAssertEqual(controller.activeTab?.currentDirectory, expectedPath)
|
||||
}
|
||||
|
||||
func testNewTabFallsBackToHomeDirectoryWhenWorkspaceHasNoTabs() {
|
||||
let factory = RecordingTerminalSessionFactory()
|
||||
let controller = WorkspaceController(
|
||||
summary: WorkspaceSummary(name: "Main"),
|
||||
sessionFactory: factory,
|
||||
settingsProvider: TestSettingsProvider(),
|
||||
bootstrapDefaultTab: false
|
||||
)
|
||||
|
||||
controller.newTab()
|
||||
|
||||
XCTAssertEqual(factory.requestedDirectories, [nil])
|
||||
XCTAssertEqual(controller.activeTab?.currentDirectory, NSHomeDirectory())
|
||||
XCTAssertEqual(controller.tabs.count, 1)
|
||||
XCTAssertEqual(controller.activeTabIndex, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private final class InMemoryWorkspaceStore: WorkspaceStoreType {
|
||||
var savedSummaries: [WorkspaceSummary] = []
|
||||
|
||||
@@ -138,9 +196,35 @@ private final class TestSettingsProvider: TerminalSessionConfigurationProviding
|
||||
let terminalSizePresets = TerminalSizePresetStore.loadDefaults()
|
||||
}
|
||||
|
||||
private final class RecordingTerminalSessionFactory: TerminalSessionFactoryType {
|
||||
private(set) var requestedDirectories: [String?] = []
|
||||
|
||||
@MainActor
|
||||
func makeSession(
|
||||
fontSize: CGFloat,
|
||||
theme: TerminalTheme,
|
||||
shellPath: String,
|
||||
initialDirectory: String?
|
||||
) -> TerminalSession {
|
||||
requestedDirectories.append(initialDirectory)
|
||||
return TerminalSession(
|
||||
fontSize: fontSize,
|
||||
theme: theme,
|
||||
shellPath: shellPath,
|
||||
initialDirectory: initialDirectory,
|
||||
startImmediately: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private struct UnusedTerminalSessionFactory: TerminalSessionFactoryType {
|
||||
@MainActor
|
||||
func makeSession(fontSize: CGFloat, theme: TerminalTheme, shellPath: String) -> TerminalSession {
|
||||
func makeSession(
|
||||
fontSize: CGFloat,
|
||||
theme: TerminalTheme,
|
||||
shellPath: String,
|
||||
initialDirectory: String?
|
||||
) -> TerminalSession {
|
||||
fatalError("WorkspaceRegistryTests should not create live terminal sessions.")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user