diff --git a/Documentation/ProjectContext.md b/Documentation/ProjectContext.md
new file mode 100644
index 000000000..3fbb9450b
--- /dev/null
+++ b/Documentation/ProjectContext.md
@@ -0,0 +1,71 @@
+# Project Context
+
+This document outlines the desired behaviour of the "Project Context" feature.
+
+## Rationale
+
+Currently SubEthaEdit operates on a per file basis and thus does not provide any features on a project level like:
+
+- powerful project wide search and replace
+- constrain autocomplete for files in the same context
+- simple access and recognition of build and linting scripts for the project opened
+- project wide navigation (e.g. opening up of imported files, etc)
+- SCM integration
+- ...
+
+Therefore a concept and an implementation of a Project Context is needed.
+
+## Definition
+
+- Workspace: The workspace is the root entity for all project related information like the project base path. Documents can belong to at most one workspace.
+
+## Behaviour
+
+### Workspaces
+
+Workspaces are created if:
+
+- The see tool is used to open a folder `see ./myfolder`
+- If a folder is opened via one of the system provided functionalities (dragged on SEE icon, opened via Fild > Open, ...)
+
+If a folder within an already open workspace is opened, no new workspace will be created. Instead the workspace window of the existing workspace will be displayed and the folder will be highlighted
+
+Workspaces are dismissed if:
+- All documents of the workspace are closed AND
+- All Workspace windows are closed
+
+
+### Documents
+
+Every document can belong to at least one workspace. If it does so, in the upper right corner of the document window, a little "Project" icon is displayed. Clicking on this icon will reveal the Workspace Window and select the corresponding file.
+
+If a new file is created or opened and the path of the file is contained in a workspace, it will be assigned to that workspace. If there a multiple workspaces open, the topmost one will be used. For example if the path of the file is `/foo/bar/foo.txt` and a workspace exists with the base path `/foo` as well as one with the base path `/foo/bar` it will be assigned to the latter one.
+
+### Workspace Window
+
+The workspace displays the tree-like file structure of a workspace. Double-Clicking a file will open the corresponding document. There can only be one workspace window per workspace.
+
+### Collaboration
+
+At the moment, workspaces have no effect on how collaboration works.
+
+## Implementation
+Workspaces are represented by an `SEEWorkspace` object and managed by the `SEEWorkspaceController`. The diagram shows the relations between the Document & Workspace related objects.
+
+```
+ +-----------------------------+ +-----------------------------+
+ | | | |
+ | SEEWorkspaceController |<----1--+ SEEDocumentController |
+ | | | |
+ +-------------+---------------+ +--------------+--------------+
+ * *
+ | |
+ v v
+ +-----------------------------+ +-----------------------------+
+ | | | |
+ | SEEWorkspace +-*----->| NSDocument |
+ | | | |
+ +-----------------------------+ +-----------------------------+
+ ```
+
+
diff --git a/SubEthaEdit-Mac/Base.lproj/MainMenu.xib b/SubEthaEdit-Mac/Base.lproj/MainMenu.xib
index b180db467..accdfb2c4 100644
--- a/SubEthaEdit-Mac/Base.lproj/MainMenu.xib
+++ b/SubEthaEdit-Mac/Base.lproj/MainMenu.xib
@@ -1013,9 +1013,9 @@ GQ
-
+
-
+
@@ -1088,5 +1088,8 @@ GQ
+
+
+
diff --git a/SubEthaEdit-Mac/Info.plist b/SubEthaEdit-Mac/Info.plist
index e21e2a026..21c91854c 100644
--- a/SubEthaEdit-Mac/Info.plist
+++ b/SubEthaEdit-Mac/Info.plist
@@ -20,7 +20,7 @@
LSTypeIsPackage
NSDocumentClass
- PlainTextDocument
+ SEEWorkspaceDocument
CFBundleTypeName
diff --git a/SubEthaEdit-Mac/SEEPlainTextEditorTopBarViewController.xib b/SubEthaEdit-Mac/SEEPlainTextEditorTopBarViewController.xib
index 48a1a616c..7461a2b79 100644
--- a/SubEthaEdit-Mac/SEEPlainTextEditorTopBarViewController.xib
+++ b/SubEthaEdit-Mac/SEEPlainTextEditorTopBarViewController.xib
@@ -15,6 +15,7 @@
+
@@ -81,10 +82,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SubEthaEdit-Mac/Source/PlainTextDocument.h b/SubEthaEdit-Mac/Source/PlainTextDocument.h
index 87f33727c..1886fa4df 100644
--- a/SubEthaEdit-Mac/Source/PlainTextDocument.h
+++ b/SubEthaEdit-Mac/Source/PlainTextDocument.h
@@ -15,6 +15,7 @@
#import "FoldableTextStorage.h"
#import "FullTextStorage.h"
#import "SEEDocumentController.h"
+#import "SEEWorkspace.h"
#import "SEEAlertRecipe.h"
enum {
@@ -38,6 +39,7 @@ extern NSString * const PlainTextDocumentUserDidChangeSelectionNotification;
extern NSString * const PlainTextDocumentDefaultParagraphStyleDidChangeNotification;
extern NSString * const PlainTextDocumentDidChangeDisplayNameNotification;
extern NSString * const PlainTextDocumentDidChangeDocumentModeNotification;
+extern NSString * const PlainTextDocumentWorkspaceDidChangeNotification;
extern NSString * const WrittenByUserIDAttributeName;
extern NSString * const ChangedByUserIDAttributeName;
@@ -46,7 +48,7 @@ extern NSString * const PlainTextDocumentDidSaveNotification;
extern NSString * const PlainTextDocumentDidSaveShouldReloadWebPreviewNotification;
-@interface PlainTextDocument : NSDocument
+@interface PlainTextDocument : NSDocument
{
TCMMMSession *I_session;
struct {
@@ -151,7 +153,6 @@ extern NSString * const PlainTextDocumentDidSaveShouldReloadWebPreviewNotificati
@property (readwrite, strong) IBOutlet NSWindow *O_exportSheet;
@property (readwrite, strong) IBOutlet NSObjectController *O_exportSheetController;
@property (nonatomic, strong) NSMutableArray *persistentDocumentScopedBookmarkURLs;
-
@property (nonatomic, strong) SEEDocumentCreationFlags *attachedCreationFlags;
/*!
diff --git a/SubEthaEdit-Mac/Source/PlainTextDocument.m b/SubEthaEdit-Mac/Source/PlainTextDocument.m
index 31861ddd7..69b21077a 100644
--- a/SubEthaEdit-Mac/Source/PlainTextDocument.m
+++ b/SubEthaEdit-Mac/Source/PlainTextDocument.m
@@ -99,6 +99,8 @@
@"PlainTextDocumentDidChangeTextStorageNotification";
NSString * const PlainTextDocumentDefaultParagraphStyleDidChangeNotification =
@"PlainTextDocumentDefaultParagraphStyleDidChangeNotification";
+NSString * const PlainTextDocumentWorkspaceDidChangeNotification =
+ @"PlainTextDocumentWorkspaceDidChangeNotification";
NSString * const PlainTextDocumentDidSaveNotification =
@"PlainTextDocumentDidSaveNotification";
NSString * const PlainTextDocumentDidSaveShouldReloadWebPreviewNotification =
@@ -106,6 +108,8 @@
NSString * const WrittenByUserIDAttributeName = @"WrittenByUserID";
NSString * const ChangedByUserIDAttributeName = @"ChangedByUserID";
+static void * PlainTextDocumentWorkspaceObservationContext = &PlainTextDocumentWorkspaceObservationContext;
+
// Something that's used by our override of -shouldCloseWindowController:delegate:shouldCloseSelector:contextInfo: down below.
@interface PlainTextDocumentShouldCloseContext : NSObject {
@public
@@ -155,6 +159,8 @@ - (void)TCM_validateLineEndings;
@implementation PlainTextDocument
+@synthesize workspace;
+
+ (void)initialize {
if (self == [PlainTextDocument class]) {
@@ -316,6 +322,8 @@ - (void)TCM_initHelper {
I_undoManager = [(UndoManager *)[UndoManager alloc] initWithDocument:self];
[[[self session] loggingState] setInitialTextStorageDictionaryRepresentation:[self textStorageDictionaryRepresentation]];
[[[self session] loggingState] handleOperation:[SelectionOperation selectionOperationWithRange:NSMakeRange(0,0) userID:[TCMMMUserManager myUserID]]];
+
+ [self addObserver:self forKeyPath:@"workspace" options:0 context:PlainTextDocumentWorkspaceObservationContext];
}
@@ -778,6 +786,9 @@ - (void)updateTempDisplayName:(NSNotification *)aNotification {
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [self removeObserver:self forKeyPath:@"workspace" context:PlainTextDocumentWorkspaceObservationContext];
+
if (I_flags.isAnnounced) {
[[TCMMMPresenceManager sharedInstance] concealSession:I_session];
}
@@ -793,6 +804,14 @@ - (void)dealloc {
[[TCMMMPresenceManager sharedInstance] unregisterSession:I_session];
}
+-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+ if (context == PlainTextDocumentWorkspaceObservationContext) {
+ [[NSNotificationCenter defaultCenter] postNotificationName:PlainTextDocumentWorkspaceDidChangeNotification object:self];
+ } else {
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+ }
+}
+
#pragma mark - Encoding
- (BOOL)canBeConvertedToEncoding:(NSStringEncoding)encoding {
diff --git a/SubEthaEdit-Mac/Source/PlainTextEditor.m b/SubEthaEdit-Mac/Source/PlainTextEditor.m
index 471c96796..30c2ced4b 100644
--- a/SubEthaEdit-Mac/Source/PlainTextEditor.m
+++ b/SubEthaEdit-Mac/Source/PlainTextEditor.m
@@ -2598,7 +2598,8 @@ - (NSArray *)textView:(NSTextView *)textView completions:(NSArray *)words forPar
PlainTextDocument *document = nil;
while ((document = [documents nextObject])){
- if (document == myDocument) continue;
+ if (document == myDocument ||
+ ![document isKindOfClass:[PlainTextDocument class]]) continue;
NSEnumerator *matches = [document matchEnumeratorForAutocompleteString:partialWord];
OGRegularExpressionMatch *match = nil;
diff --git a/SubEthaEdit-Mac/Source/PlainTextWindowController.m b/SubEthaEdit-Mac/Source/PlainTextWindowController.m
index 045eef9a8..74e330e4b 100644
--- a/SubEthaEdit-Mac/Source/PlainTextWindowController.m
+++ b/SubEthaEdit-Mac/Source/PlainTextWindowController.m
@@ -19,6 +19,7 @@
#import "AppController.h"
#import "SEEEncodingDoctorDialogViewController.h"
#import "SEEDocumentController.h"
+#import "SEEWorkspaceDocument.h"
#import "PlainTextWindowControllerTabContext.h"
#import "NSMenuTCMAdditions.h"
#import "PlainTextLoadProgress.h"
@@ -289,8 +290,9 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
} else {
return NO;
}
- }
-
+ } else if (selector == @selector(showWorkspace:)) {
+ return self.plainTextDocument.workspace != nil;
+ }
return YES;
}
@@ -379,6 +381,18 @@ - (IBAction)copyDocumentURL:(id)aSender {
[documentURL writeToPasteboard:pboard];
}
+- (IBAction)showWorkspace:(id)sender {
+ SEEDocumentController * documentController = (SEEDocumentController *)[NSDocumentController sharedDocumentController];
+
+ [documentController openWorkspace:self.plainTextDocument.workspace
+ display:YES
+ withCompletionHandler:^(NSDocument *document, BOOL documentWasAlreadyOpen, NSError *error) {
+ if ([document isKindOfClass:[SEEWorkspaceDocument class]]) {
+ [(SEEWorkspaceDocument *)document selectFileWithURL:self.plainTextDocument.fileURL];
+ }
+ }];
+}
+
#pragma mark -
diff --git a/SubEthaEdit-Mac/Source/SEEDocumentController.h b/SubEthaEdit-Mac/Source/SEEDocumentController.h
index 04d1e0f15..1edc64aee 100644
--- a/SubEthaEdit-Mac/Source/SEEDocumentController.h
+++ b/SubEthaEdit-Mac/Source/SEEDocumentController.h
@@ -12,6 +12,8 @@
@class PlainTextWindowController;
@class MAAttachedWindow;
@class PlainTextDocument;
+@class SEEWorkspaceController;
+@class SEEWorkspace;
extern NSString *const RecentDocumentsDidChangeNotification;
@@ -34,6 +36,7 @@ extern NSString * const kSEETypeSEEMode;
@property (nonatomic, weak) IBOutlet NSMenu *recentDocumentMenu;
@property (nonatomic, readonly) NSStringEncoding encodingFromLastRunOpenPanel;
@property (nonatomic, readonly, copy) NSString *modeIdentifierFromLastRunOpenPanel;
+@property (nonatomic, readonly) SEEWorkspaceController *workspaceController;
+ (SEEDocumentController *)sharedInstance;
@@ -51,6 +54,8 @@ extern NSString * const kSEETypeSEEMode;
- (IBAction)openNormalDocument:(id)aSender;
- (IBAction)openAlternateDocument:(id)aSender;
+-(void)openWorkspace:(SEEWorkspace *)workspace display:(BOOL)displayDocument withCompletionHandler:(void (^)(NSDocument *, BOOL, NSError *))completionHandler;
+
- (void)addProxyDocumentWithSession:(TCMMMSession *)aSession;
- (NSArray *)documentsInMode:(DocumentMode *)aDocumentMode;
diff --git a/SubEthaEdit-Mac/Source/SEEDocumentController.m b/SubEthaEdit-Mac/Source/SEEDocumentController.m
index 6ad6cbe5d..f15716ea8 100644
--- a/SubEthaEdit-Mac/Source/SEEDocumentController.m
+++ b/SubEthaEdit-Mac/Source/SEEDocumentController.m
@@ -24,6 +24,7 @@
#import "SEEDocumentListWindowController.h"
#import "NSApplicationTCMAdditions.h"
#import "SEEScopedBookmarkManager.h"
+#import "SEEWorkspaceController.h"
#import // for objc_msgSend
@@ -46,6 +47,7 @@ @interface SEEDocumentController ()
@property (nonatomic, copy) NSURL *locationForNextOpenPanel;
@property (nonatomic, readwrite, nonatomic) NSStringEncoding encodingFromLastRunOpenPanel;
@property (nonatomic, readwrite, copy) NSString *modeIdentifierFromLastRunOpenPanel;
+@property (nonatomic, readwrite) SEEWorkspaceController *workspaceController;
@end
@@ -103,6 +105,7 @@ - (instancetype)init {
if (self) {
self.filenamesFromLastRunOpenPanel = [NSMutableArray array];
self.documentCreationFlagsLookupDict = [NSMutableDictionary dictionary];
+ self.workspaceController = [[SEEWorkspaceController alloc] initWithDocumentController:self];
I_propertiesForOpenedFiles = [NSMutableDictionary new];
I_suspendedSeeScriptCommands = [NSMutableDictionary new];
@@ -702,11 +705,9 @@ - (void)openDocumentWithContentsOfURL:(NSURL *)url display:(BOOL)displayDocument
completionHandler(nil, NO, nil);
}
} else if (!isFilePackage && isDirectory) {
- [self openDirectory:url];
-
- if (completionHandler) {
- completionHandler(nil, NO, nil);
- }
+ [self openWorkspaceWithURL:url display:displayDocument withCompletionHandler:completionHandler];
+
+
} else {
NSAppleEventDescriptor *eventDesc = [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent];
NSDictionary *parsed = [PlainTextDocument parseOpenDocumentEvent:eventDesc];
@@ -745,6 +746,10 @@ - (id)makeDocumentWithContentsOfURL:(NSURL *)url ofType:(NSString *)typeName err
return result;
}
+- (void)addDocument:(NSDocument *)document {
+ [super addDocument:document];
+ [self.workspaceController addDocument:document];
+}
#pragma mark - NSWindowRestoration
@@ -1043,7 +1048,7 @@ - (id)handleOpenScriptCommand:(NSScriptCommand *)command {
if (isFilePackage && [extension isEqualToString:modeExtension]) {
[self openModeFile:filename];
} else if ([[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&isDir] && isDir && !isFilePackage) {
- [self openDirectory:[NSURL fileURLWithPath:filename]];
+ [self openWorkspaceWithURL:[NSURL fileURLWithPath:filename] display:YES withCompletionHandler:nil];
} else {
[I_propertiesForOpenedFiles setObject:properties forKey:filename];
if (!isFilePackage) {
@@ -1577,6 +1582,7 @@ - (void)removeDocument:(NSDocument *)document {
}
[super removeDocument:document];
+ [self.workspaceController removeDocument:document];
if ([[self documents] count]==0) {
NSMenu *modeMenu=[[[NSApp mainMenu] itemWithTag:ModeMenuTag] submenu];
// remove all items that don't belong here anymore
@@ -1705,9 +1711,24 @@ - (void)openModeFile:(NSString *)fileName {
}
}
+- (void)openWorkspaceWithURL:(NSURL *)aURL display:(BOOL)displayDocument withCompletionHandler:(void (^)(NSDocument *, BOOL, NSError *))completionHandler{
+ SEEWorkspace *workspace = [self.workspaceController workspaceForURL:aURL createIfNeeded:YES];
+
+ [super openDocumentWithContentsOfURL:workspace.baseURL
+ display:displayDocument
+ completionHandler:completionHandler];
+}
+
+-(void)openWorkspace:(SEEWorkspace *)workspace display:(BOOL)displayDocument withCompletionHandler:(void (^)(NSDocument *, BOOL, NSError *))completionHandler {
+
+ [super openDocumentWithContentsOfURL:workspace.baseURL
+ display:displayDocument
+ completionHandler:completionHandler];
+}
+
- (void)openDirectory:(NSURL *)aURL {
- [self setLocationForNextOpenPanel:aURL];
- [self performSelector:@selector(openDocument:) withObject:nil afterDelay:0.0];
+ [self setLocationForNextOpenPanel:aURL];
+ [self performSelector:@selector(openDocument:) withObject:nil afterDelay:0.0];
DEBUGLOG(@"FileIOLogDomain", SimpleLogLevel, @"Opening directory: %@", aURL);
}
@@ -1741,4 +1762,5 @@ - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)data error:(NS
[document clearChangeMarks:self];
}
+
@end
diff --git a/SubEthaEdit-Mac/Source/SEEFSTree.h b/SubEthaEdit-Mac/Source/SEEFSTree.h
new file mode 100644
index 000000000..6b4635ce8
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEFSTree.h
@@ -0,0 +1,22 @@
+//
+// SEEFSTree.h
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 24.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import
+
+@class SEEFSTreeNode;
+
+
+
+@interface SEEFSTree : NSObject
+@property (nonatomic, readonly) SEEFSTreeNode *root;
+
+- (instancetype)initWithURL:(NSURL *)anURL;
+
+@end
+
+
diff --git a/SubEthaEdit-Mac/Source/SEEFSTree.m b/SubEthaEdit-Mac/Source/SEEFSTree.m
new file mode 100644
index 000000000..1919f063c
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEFSTree.m
@@ -0,0 +1,77 @@
+//
+// SEEFSTree.m
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 24.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import "SEEFSTree.h"
+#import "SEEFSTreeNode.h"
+
+@interface SEEFSTree ()
+-(void)pathDidChange:(NSString *)path withFlags:(FSEventStreamEventFlags)flags;
+@end
+
+static void fsEventsCallback(ConstFSEventStreamRef streamRef,
+ void *clientCallBackInfo,
+ size_t numEvents,
+ void *eventPaths,
+ const FSEventStreamEventFlags eventFlags[],
+ const FSEventStreamEventId eventIds[]){
+ NSArray* paths = (__bridge NSArray *)eventPaths;
+ SEEFSTree *tree = (__bridge SEEFSTree*)clientCallBackInfo;
+
+ for (size_t i = 0; i < numEvents; i++) {
+ [tree pathDidChange:paths[i] withFlags:eventFlags[i]];
+ }
+}
+
+@implementation SEEFSTree {
+ FSEventStreamRef fsEventStream;
+}
+
+- (instancetype)initWithURL:(NSURL *)anURL;
+{
+ self = [super init];
+ if (self) {
+ _root = [[SEEFSTreeNode alloc] initWithURL:anURL parent:nil];
+
+ NSArray *pathsToWatch = @[anURL.path];
+
+ CFAbsoluteTime latency = 0.5; // Latency in seconds
+ FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagUseCFTypes;
+
+ FSEventStreamContext ctx;
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.info = (__bridge void *)self;
+
+ fsEventStream = FSEventStreamCreate(NULL,
+ &fsEventsCallback,
+ &ctx,
+ (__bridge CFArrayRef)pathsToWatch,
+ kFSEventStreamEventIdSinceNow,
+ latency,
+ flags);
+ FSEventStreamScheduleWithRunLoop(fsEventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+ FSEventStreamStart(fsEventStream);
+ }
+ return self;
+}
+
+-(void)dealloc {
+ if(fsEventStream) {
+ FSEventStreamStop(fsEventStream);
+ FSEventStreamInvalidate(fsEventStream);
+ FSEventStreamRelease(fsEventStream);
+ fsEventStream = NULL;
+ }
+}
+
+-(void)pathDidChange:(NSString *)path withFlags:(FSEventStreamEventFlags)flags {
+ BOOL scanSubdirs = flags & kFSEventStreamEventFlagMustScanSubDirs;
+ [[self.root nodeForPath:path onlyIfCached:YES] reloadChildrenRecursive:scanSubdirs];
+}
+
+
+@end
diff --git a/SubEthaEdit-Mac/Source/SEEFSTreeNode.h b/SubEthaEdit-Mac/Source/SEEFSTreeNode.h
new file mode 100644
index 000000000..d23028a9b
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.h
@@ -0,0 +1,31 @@
+//
+// SEEFSTreeNode.h
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 19.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import
+
+
+
+@interface SEEFSTreeNode : NSObject
+- (instancetype)initWithURL:(NSURL *)anURL parent:(SEEFSTreeNode *)aParent;
+
+@property (nonatomic, assign) BOOL includeHidden;
+
+- (NSString *)name;
+- (NSImage *)icon;
+- (NSArray *)children;
+- (NSURL *)url;
+- (BOOL)isLeaf;
+- (BOOL)isRoot;
+- (void)reloadChildrenRecursive:(BOOL)includeChildren;
+
+- (SEEFSTreeNode * )nodeForPath:(NSString *)path;
+- (SEEFSTreeNode * )nodeForPath:(NSString *)path onlyIfCached:(BOOL)cached;
+- (NSIndexPath *)indexPath;
+@end
+
+
diff --git a/SubEthaEdit-Mac/Source/SEEFSTreeNode.m b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m
new file mode 100644
index 000000000..aa8fc915f
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m
@@ -0,0 +1,215 @@
+//
+// SEEFSTreeNode.m
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 19.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import "SEEFSTreeNode.h"
+
+@interface FSEvent : NSObject
+
+@property (nonatomic, readonly) NSString *path;
+@property (nonatomic, readonly) FSEventStreamEventFlags eventFlags;
+@property (nonatomic, readonly) FSEventStreamEventId eventIds;
+
+@end
+
+@interface SEEFSTreeNode ()
+@property (nonatomic) BOOL isFolder;
+@property (nonatomic) BOOL isHidden;
+
+@end
+
+static NSArray *resourceValueKeys;
+
+@implementation SEEFSTreeNode {
+ __weak SEEFSTreeNode *parent;
+ NSURL *url;
+ NSMutableArray *unfilteredChildren;
+ NSMutableArray *children;
+}
+
++(void)initialize {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ resourceValueKeys = @[NSURLIsDirectoryKey, NSURLIsHiddenKey];
+ });
+}
+
+- (instancetype)initWithURL:(NSURL *)anURL parent:(SEEFSTreeNode *)aParent
+{
+ self = [super init];
+ if (self) {
+ url = anURL;
+ parent = aParent;
+
+ NSDictionary *values = [url resourceValuesForKeys:resourceValueKeys
+ error:nil];
+
+ self.isFolder = [((NSNumber *)values[NSURLIsDirectoryKey]) boolValue];
+ self.isHidden = [((NSNumber *)values[NSURLIsHiddenKey]) boolValue];
+
+
+ }
+ return self;
+}
+
+- (NSString *)name {
+ return url.lastPathComponent;
+}
+
+- (NSArray *)children {
+ if(!children) {
+ children = [NSMutableArray new];
+ [self reloadChildrenRecursive:NO];
+ }
+
+ return children;
+}
+
+- (NSImage *)icon {
+ return [[NSWorkspace sharedWorkspace] iconForFile:url.filePathURL.path];
+}
+
+- (BOOL)isLeaf {
+ return !_isFolder;
+}
+
+-(BOOL)isRoot {
+ return parent == nil;
+}
+
+-(NSURL *)url {
+ return url;
+}
+
+- (BOOL)isEqual:(id)other
+{
+ if (other == self) {
+ return YES;
+ } else if ([other isKindOfClass:[SEEFSTreeNode class]]){
+ SEEFSTreeNode *o = other;
+ return [self.url isEqual: o.url] && (parent == o->parent) && (self.includeHidden == o.includeHidden);
+ } else {
+ return NO;
+ }
+}
+
+- (NSUInteger)hash
+{
+ return self.url.hash + parent.hash;
+}
+
+- (SEEFSTreeNode *)nodeNamed:(NSString *)name {
+ return [self nodeNamed:name onlyIfCached:NO];
+}
+
+- (SEEFSTreeNode *)nodeNamed:(NSString *)name onlyIfCached:(BOOL)cached{
+ NSArray *_children = cached ? children : self.children;
+ for (SEEFSTreeNode* node in _children) {
+ if([node.name isEqualToString:name]) {
+ return node;
+ }
+ }
+ return nil;
+}
+
+- (SEEFSTreeNode * )nodeForPath:(NSString *)path {
+ return [self nodeForPath:path onlyIfCached:NO];
+}
+
+- (SEEFSTreeNode *)nodeForPath:(NSString *)path onlyIfCached:(BOOL)cached {
+ NSMutableArray * components = [[path pathComponents] mutableCopy];
+ NSArray * ownComponents = url.pathComponents;
+
+ if ([components.lastObject isEqualToString:@"/"]) {
+ [components removeLastObject];
+ }
+
+ if (ownComponents.count <= components.count) {
+
+ NSRange r = NSMakeRange(0, ownComponents.count);
+ NSRange rest = NSMakeRange(ownComponents.count, components.count - ownComponents.count);
+ if([ownComponents isEqualToArray:[components subarrayWithRange:r]]) {
+
+ SEEFSTreeNode *currentNode = self;
+ for (NSString *component in [components subarrayWithRange:rest]) {
+ currentNode = [currentNode nodeNamed:component onlyIfCached:cached];
+ }
+ return currentNode;
+ }
+ }
+ return nil;
+}
+
+- (NSIndexPath *)indexPath {
+ NSIndexPath *indexPath = nil;
+
+
+ if (parent) {
+ NSUInteger ownIndex = [parent.children indexOfObject:self];
+ NSIndexPath *parentIndexPath = parent.indexPath;
+
+ if(parentIndexPath) {
+ indexPath = [parentIndexPath indexPathByAddingIndex:ownIndex];
+ } else {
+ indexPath = [NSIndexPath indexPathWithIndex:ownIndex];
+ }
+ }
+
+ return indexPath;
+}
+
+-(void)setIncludeHidden:(BOOL)includeHidden {
+ [self willChangeValueForKey:@"includeHidden"];
+ _includeHidden = includeHidden;
+ [self reloadChildrenRecursive:YES];
+ [self didChangeValueForKey:@"includeHidden"];
+}
+
+- (void)reloadChildrenRecursive:(BOOL)recursive {
+ [self willChangeValueForKey:@"children"];
+
+
+ NSArray *contents = [NSFileManager.defaultManager contentsOfDirectoryAtURL:url
+ includingPropertiesForKeys:resourceValueKeys
+ options:0
+ error:nil];
+ if(!children) {
+ return;
+ }
+
+ for (NSURL *url in contents) {
+ SEEFSTreeNode *node = [[SEEFSTreeNode alloc] initWithURL:url parent:self];
+ node.includeHidden = self.includeHidden;
+
+ if (![children containsObject:node]) {
+ [children addObject:node];
+ }
+ }
+
+ [children filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SEEFSTreeNode * evaluatedObject, NSDictionary * bindings) {
+
+ if (![contents containsObject:evaluatedObject.url]) {
+ return NO;
+ }
+
+ if(evaluatedObject.isHidden && !self.includeHidden) {
+ return NO;
+ }
+
+ return YES;
+ }]];
+
+ if(recursive) {
+ for (SEEFSTreeNode *node in children) {
+ [node reloadChildrenRecursive:recursive];
+ }
+ }
+
+ [self didChangeValueForKey:@"children"];
+}
+
+@end
diff --git a/SubEthaEdit-Mac/Source/SEEPlainTextEditorTopBarViewController.m b/SubEthaEdit-Mac/Source/SEEPlainTextEditorTopBarViewController.m
index afb38b172..9a6866f4e 100644
--- a/SubEthaEdit-Mac/Source/SEEPlainTextEditorTopBarViewController.m
+++ b/SubEthaEdit-Mac/Source/SEEPlainTextEditorTopBarViewController.m
@@ -8,6 +8,7 @@
#import "PlainTextDocument.h"
#import "DocumentMode.h"
#import "BorderedTextField.h"
+#import "SEEWorkspaceDocument.h"
@interface SEEPlainTextEditorTopBarViewController ()
@property (nonatomic, strong) IBOutlet BorderedTextField *writtenByTextField;
@@ -15,6 +16,7 @@ @interface SEEPlainTextEditorTopBarViewController ()
@property (nonatomic, strong) IBOutlet BorderedTextField *docinfoTextField;
@property (nonatomic, strong) IBOutlet NSButton *splitButton;
@property (nonatomic, strong) IBOutlet NSImageView *waitPipeIconImageView;
+@property (nonatomic, strong) IBOutlet NSButton *workspaceButton;
@property (nonatomic, strong) IBOutlet NSView *bottomBarLayerBackedView;
@property (nonatomic, strong) NSMutableSet *registeredNotifications;
@@ -55,10 +57,15 @@ - (instancetype)initWithPlainTextEditor:(PlainTextEditor *)anEditor {
[weakSelf updateSymbolPopUpContent];
[weakSelf adjustLayout];
}]];
+ [set addObject:[center addObserverForName:PlainTextDocumentWorkspaceDidChangeNotification
+ object:document queue:mainQueue
+ usingBlock:^(NSNotification *aNotification) {
+ [weakSelf updateSymbolPopUpContent];
+ [weakSelf adjustLayout];
+ }]];
set;
});
-
}
return self;
}
@@ -162,16 +169,27 @@ - (void)adjustLayout {
NSRect bounds = self.view.bounds;
CGFloat xPosition = NSMinX(bounds);
BOOL isWaiting = [document isWaiting];
+ BOOL hasWorkspace = (document.workspace != nil);
BOOL hasSymbols = [[document documentMode] hasSymbols];
BorderedTextField *positionTextField = self.positionTextField;
PopUpButton *symbolPopUpButton = self.symbolPopUpButton;
// document is waiting for some input from a pipe so show an indicator for that
self.waitPipeIconImageView.hidden = !isWaiting;
- [positionTextField setHasLeftBorder:isWaiting];
+ [positionTextField setHasLeftBorder:isWaiting || hasWorkspace];
if (isWaiting) {
xPosition += 19.;
}
+
+ self.workspaceButton.hidden = !hasWorkspace;
+
+ NSRect workspaceButtonFrame = self.workspaceButton.frame;
+ workspaceButtonFrame.origin.x = xPosition;
+ self.workspaceButton.frame = workspaceButtonFrame;
+
+ if (hasWorkspace) {
+ xPosition += 19.;
+ }
// if there are no symbols hide the symbols popup
[symbolPopUpButton setHidden:!hasSymbols];
@@ -240,6 +258,18 @@ - (IBAction)splitToggleButtonAction:(id)sender {
[self.editor.windowControllerTabContext toggleEditorSplit];
}
+- (IBAction)workspaceButtonAction:(id)sender {
+ SEEDocumentController * documentController = (SEEDocumentController *)[NSDocumentController sharedDocumentController];
+
+ [documentController openWorkspace:self.editor.document.workspace
+ display:YES
+ withCompletionHandler:^(NSDocument *document, BOOL documentWasAlreadyOpen, NSError *error) {
+ if ([document isKindOfClass:[SEEWorkspaceDocument class]]) {
+ [(SEEWorkspaceDocument *)document selectFileWithURL:self.editor.document.fileURL];
+ }
+ }];
+}
+
- (IBAction)keyboardActivateSymbolPopUp {
[self.symbolPopUpButton performClick:self.editor];
}
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspace.h b/SubEthaEdit-Mac/Source/SEEWorkspace.h
new file mode 100644
index 000000000..2f0d08d69
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspace.h
@@ -0,0 +1,39 @@
+//
+// SEEWorkspace.h
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 03.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import
+
+
+
+@class SEEWorkspace;
+
+@protocol SEEWorkspaceDocument
+@property (nonatomic, weak) SEEWorkspace *workspace;
+
+@optional
+-(BOOL) requiresWorkspace;
+
+@end
+
+@interface SEEWorkspace : NSObject
+
+@property (nonatomic, readonly) NSURL *baseURL;
+@property (nonatomic, readonly) NSArray *>*documents;
+
+-(instancetype)initWithBaseURL:(NSURL *)url;
+
+-(BOOL)containsDocument:(NSDocument *)doc;
+
+-(BOOL)containsURL:(NSURL *)url;
+
+-(void)addDocument:(NSDocument *)doc;
+-(void)removeDocument:(NSDocument *)doc;
+
+@end
+
+
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspace.m b/SubEthaEdit-Mac/Source/SEEWorkspace.m
new file mode 100644
index 000000000..6ffd93046
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspace.m
@@ -0,0 +1,63 @@
+//
+// SEEWorkspace.m
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 03.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import "SEEWorkspace.h"
+
+@implementation SEEWorkspace {
+ NSMutableArray *> *_documents;
+}
+
+- (instancetype)initWithBaseURL:(NSURL *)url
+{
+ self = [super init];
+ if (self) {
+ NSAssert(url.isFileURL, @"Workspaces can only be instanciated on file URLs");
+ _baseURL = [url.absoluteURL copy];
+ _documents = [NSMutableArray new];
+ }
+ return self;
+}
+
+-(BOOL)containsDocument:(NSDocument *)doc {
+ return [self.documents containsObject:doc];
+}
+
+-(BOOL)containsURL:(NSURL *)url {
+ if( url.isFileURL ) {
+ NSArray *baseComponents = _baseURL.pathComponents;
+ NSArray *components = url.absoluteURL.pathComponents;
+
+ NSUInteger index = 0;
+ if (components.count >= baseComponents.count) {
+ for (NSString *baseComponent in baseComponents) {
+ if (![baseComponent isEqualToString:components[index]]) {
+ return NO;
+ }
+ index++;
+ }
+ return YES;
+ }
+ }
+ return NO;
+}
+
+-(NSArray *)documents {
+ return [_documents copy];
+}
+
+-(void)addDocument:(NSDocument *)doc {
+ [_documents addObject:doc];
+ doc.workspace = self;
+}
+
+-(void)removeDocument:(NSDocument *)doc {
+ [_documents removeObject:doc];
+ doc.workspace = nil;
+}
+
+@end
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceController.h b/SubEthaEdit-Mac/Source/SEEWorkspaceController.h
new file mode 100644
index 000000000..14f70b20c
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceController.h
@@ -0,0 +1,44 @@
+//
+// SEEWorkspaceController.h
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 03.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+/*
+ +-----------------------------+ +-----------------------------+
+ | | | |
+ | SEEWorkspaceController |<----1--+ SEEDocumentController |
+ | | | |
+ +-------------+---------------+ +--------------+--------------+
+ * *
+ | |
+ v v
+ +-----------------------------+ +-----------------------------+
+ | | | |
+ | SEEWorkspace +-*----->| NSDocument |
+ | | | |
+ +-----------------------------+ +-----------------------------+
+
+ */
+
+#import
+
+@class SEEWorkspace;
+
+@interface SEEWorkspaceController : NSObject
+
+@property (nonatomic, weak, readonly) NSDocumentController *documentController;
+
+-(instancetype)initWithDocumentController:(NSDocumentController *)controller;
+
+-(SEEWorkspace *)workspaceForDocument:(NSDocument *)document;
+
+-(SEEWorkspace *)workspaceForURL:(NSURL *)url;
+-(SEEWorkspace *)workspaceForURL:(NSURL *)url createIfNeeded:(BOOL)create;
+
+-(void)addDocument:(NSDocument *)document;
+-(void)removeDocument:(NSDocument *)document;
+
+@end
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceController.m b/SubEthaEdit-Mac/Source/SEEWorkspaceController.m
new file mode 100644
index 000000000..0647580eb
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceController.m
@@ -0,0 +1,131 @@
+//
+// SEEWorkspaceController.m
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 03.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import "SEEWorkspaceController.h"
+#import "SEEWorkspaceDocument.h"
+#import "SEEWorkspace.h"
+#import "PlainTextDocument.h"
+
+@interface SEEWorkspaceController ()
+
+@end
+
+@implementation SEEWorkspaceController {
+@private
+ NSMutableSet *workspaces;
+}
+
+- (instancetype)initWithDocumentController:(NSDocumentController *)controller;
+{
+ self = [super init];
+ if (self) {
+ _documentController = controller;
+ workspaces = [NSMutableSet new];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [NSNotificationCenter.defaultCenter removeObserver:self];
+}
+
+-(SEEWorkspace *)workspaceForDocument:(NSDocument *)document {
+ for (SEEWorkspace *ws in workspaces) {
+ if ([ws containsDocument:document]) {
+ return ws;
+ }
+ }
+ return nil;
+}
+
+-(SEEWorkspace *)workspaceForURL:(NSURL *)url {
+ return [self workspaceForURL:url createIfNeeded:NO];
+}
+
+-(SEEWorkspace *)workspaceForURL:(NSURL *)url createIfNeeded:(BOOL)create {
+ // Choose the workspace with the longest common subpath by sorting all
+ // compatible workspaces by their absolute base URL and choosing the last one
+
+ NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:@"baseURL.absoluteString" ascending:YES];
+
+ NSSet *eligableWorkspaces = [workspaces objectsPassingTest:^BOOL(SEEWorkspace * _Nonnull ws, BOOL * _Nonnull stop) {
+ return [ws containsURL:url];
+ }];
+
+ NSArray *sortedWorkspaces = [eligableWorkspaces sortedArrayUsingDescriptors:@[sd]];
+
+ SEEWorkspace *workspace = sortedWorkspaces.lastObject;
+
+ if(create && !workspace) {
+ workspace = [[SEEWorkspace alloc] initWithBaseURL:url];
+ [workspaces addObject:workspace];
+ for (NSDocument *document in [self.documentController documents]) {
+ if([document conformsToProtocol:@protocol(SEEWorkspaceDocument)]) {
+ [self assignDocumentToWorkspace:(NSDocument *)document];
+ }
+ }
+
+ }
+ return workspace;
+}
+
+-(void)addDocument:(NSDocument *)document {
+ if([document conformsToProtocol:@protocol(SEEWorkspaceDocument)]) {
+ [self assignDocumentToWorkspace:(NSDocument *)document];
+
+ [NSNotificationCenter.defaultCenter
+ addObserver:self
+ selector:@selector(documentDidSaveNotification:)
+ name:PlainTextDocumentDidSaveNotification
+ object:document];
+ }
+}
+
+- (void)documentDidSaveNotification:(NSNotification *)notification {
+ NSDocument *document = notification.object;
+ [self assignDocumentToWorkspace:document];
+}
+
+
+-(void)removeDocument:(NSDocument *)document {
+ if([document conformsToProtocol:@protocol(SEEWorkspaceDocument)]) {
+ SEEWorkspace *workspace = [self workspaceForDocument:document];
+ [workspace removeDocument:(NSDocument *)document];
+
+
+ if (workspace && workspace.documents.count == 0) {
+ [workspaces removeObject:workspace];
+ }
+
+ [NSNotificationCenter.defaultCenter removeObserver:self
+ name:nil
+ object:document];
+ }
+
+
+}
+
+-(void)assignDocumentToWorkspace:(NSDocument *)document {
+ SEEWorkspace *currentWorkspace = [self workspaceForDocument:document];
+
+ // In case the document has been moved out of the workspace
+ if(![currentWorkspace containsURL:document.fileURL]) {
+ [currentWorkspace removeDocument:document];
+ currentWorkspace = nil;
+ }
+
+ if(!currentWorkspace) {
+ BOOL createIfNeeded = [document respondsToSelector:@selector(requiresWorkspace)] &&
+ document.requiresWorkspace;
+ SEEWorkspace *desiredWorkspace = [self workspaceForURL:document.fileURL createIfNeeded:createIfNeeded];
+ [desiredWorkspace addDocument:document];
+ }
+}
+
+@end
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h
new file mode 100644
index 000000000..e142c99b2
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h
@@ -0,0 +1,20 @@
+//
+// SEEWorkspaceDocument.h
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 18.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import
+#import "SEEWorkspace.h"
+
+
+
+@interface SEEWorkspaceDocument : NSDocument
+
+- (void)selectFileWithURL:(NSURL *)url;
+
+@end
+
+
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m
new file mode 100644
index 000000000..d24c17913
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m
@@ -0,0 +1,63 @@
+//
+// SEEWorkspaceDocument.m
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 18.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import "SEEWorkspaceDocument.h"
+#import "SEEWorkspaceFileTreeViewController.h"
+
+@implementation SEEWorkspaceDocument {
+ SEEWorkspaceFileTreeViewController *fileTreeController;
+}
+@synthesize workspace;
+
+-(BOOL)requiresWorkspace {
+ return YES;
+}
+
+- (NSString *)windowNibName {
+ return @"SEEWorkspaceDocument";
+}
+
+-(BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError * __autoreleasing *)outError {
+ return YES;
+}
+
+- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
+ [super windowControllerDidLoadNib:aController];
+ fileTreeController = [[SEEWorkspaceFileTreeViewController alloc] initWithWorkspace:self.workspace];
+
+ NSView *superview = aController.window.contentView;
+ NSView *contentView = fileTreeController.view;
+ contentView.frame = superview.bounds;
+ contentView.autoresizingMask = NSViewHeightSizable | NSViewWidthSizable;
+ [superview addSubview:contentView];
+}
+
+-(void)selectFileWithURL:(NSURL *)url {
+ [fileTreeController selectFileWithURL:url];
+}
+
+- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError {
+ // Insert code here to write your document to data of the specified type. If outError != NULL, ensure that you create and set an appropriate error if you return nil.
+ // Alternatively, you could remove this method and override -fileWrapperOfType:error:, -writeToURL:ofType:error:, or -writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead.
+ if (outError) {
+ *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil];
+ }
+ return nil;
+}
+
+- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError {
+ // Insert code here to read your document from the given data of the specified type. If outError != NULL, ensure that you create and set an appropriate error if you return NO.
+ // Alternatively, you could remove this method and override -readFromFileWrapper:ofType:error: or -readFromURL:ofType:error: instead.
+ // If you do, you should also override -isEntireFileLoaded to return NO if the contents are lazily loaded.
+ if (outError) {
+ *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil];
+ }
+ return NO;
+}
+
+@end
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.xib b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.xib
new file mode 100644
index 000000000..41f395e56
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.xib
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h
new file mode 100644
index 000000000..3c6e32b1b
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h
@@ -0,0 +1,25 @@
+//
+// SEEWorkspaceFileTreeViewController.h
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 03.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import
+
+@class SEEWorkspace;
+
+
+
+@interface SEEWorkspaceFileTreeViewController : NSViewController
+
+- (instancetype)initWithWorkspace:(SEEWorkspace *)workspace;
+
+@property (nonatomic, weak) SEEWorkspace *workspace;
+
+- (void)selectFileWithURL:(NSURL *)url;
+
+@end
+
+
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m
new file mode 100644
index 000000000..c34f42e47
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m
@@ -0,0 +1,120 @@
+//
+// SEEWorkspaceFileTreeViewController.m
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 03.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import "SEEWorkspaceFileTreeViewController.h"
+#import "SEEFSTreeNode.h"
+#import "SEEWorkspace.h"
+#import "SEEWorkspaceTreeDelegate.h"
+#import "SEEFSTree.h"
+#import "PopUpButton.h"
+
+static void *SEERootChildrenChangedContext = (void *)&SEERootChildrenChangedContext;
+
+@interface SEEWorkspaceFileTreeViewController ()
+
+@property (nonatomic, strong) IBOutlet NSTreeController *treeController;
+@property (nonatomic, strong) IBOutlet NSOutlineView *outlineView;
+@property (nonatomic, strong) IBOutlet PopUpButton *optionsButton;
+@property (nonatomic, strong) IBOutlet NSMenu *optionsMenu;
+
+@property (nonatomic, assign) BOOL showHidden;
+@end
+
+@implementation SEEWorkspaceFileTreeViewController{
+ SEEFSTree *tree;
+}
+
+- (instancetype)initWithWorkspace:(SEEWorkspace *)workspace {
+ self = [super initWithNibName:@"SEEWorkspaceFileTreeViewController" bundle:nil];
+ if (self) {
+ self.workspace = workspace;
+ }
+ return self;
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ [tree removeObserver:self forKeyPath:@"root.children"];
+ tree = [[SEEFSTree alloc] initWithURL:self.workspace.baseURL];
+ [tree addObserver:self forKeyPath:@"root.children" options:0 context:SEERootChildrenChangedContext];
+
+
+ [self.treeController setContent:tree.root.children];
+
+ [self.outlineView expandItem:[self.outlineView itemAtRow:0]];
+
+ self.treeController.sortDescriptors = @[
+ [NSSortDescriptor sortDescriptorWithKey:@"isLeaf" ascending:YES],
+ [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)]];
+}
+
+-(void)selectFileWithURL:(NSURL *)url {
+ NSIndexPath *indexPath = [tree.root nodeForPath:url.filePathURL.path].indexPath;
+
+ if (!indexPath) {
+ return;
+ }
+
+ NSTreeNode *controllerNode = self.treeController.arrangedObjects;
+ SEEFSTreeNode *node = tree.root;
+
+
+ for (NSUInteger position = 0; position < indexPath.length; position++) {
+ NSUInteger index = [indexPath indexAtPosition:position];
+ node = [[node children] objectAtIndex:index];
+
+ controllerNode = [[controllerNode childNodes] SEE_firstObjectPassingTest:^BOOL(NSTreeNode * treeNode) {
+ return [treeNode.representedObject isEqual:node];
+ }];
+ }
+
+ self.treeController.selectionIndexPath = controllerNode.indexPath;
+}
+
+- (IBAction)doubleClick:(NSOutlineView *)sender {
+ NSUInteger clickedRow = sender.clickedRow;
+ id item = [sender itemAtRow:clickedRow];
+ SEEFSTreeNode *node = [item representedObject];
+
+ if(!node) { return; }
+
+ if (!node.isLeaf) {
+ if ([sender isItemExpanded:item]) {
+ [sender collapseItem:item];
+ } else {
+ [sender expandItem:item];
+ }
+
+ return;
+ }
+
+ [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:node.url
+ display:YES completionHandler:^(NSDocument * document, BOOL documentWasAlreadyOpen, NSError * error) {
+
+ }];
+}
+
+-(IBAction)toggleShowHidden:(id)sender {
+ tree.root.includeHidden = !tree.root.includeHidden;
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
+{
+ if (context == SEERootChildrenChangedContext) {
+ [self.treeController rearrangeObjects];
+ } else {
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+ }
+}
+
+-(void)dealloc {
+ [tree removeObserver:self forKeyPath:@"root.children"];
+}
+
+@end
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib
new file mode 100644
index 000000000..f8697097e
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFiles.h b/SubEthaEdit-Mac/Source/SEEWorkspaceFiles.h
new file mode 100644
index 000000000..c64c53e76
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFiles.h
@@ -0,0 +1,17 @@
+//
+// SEEWorkspaceFiles.h
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 03.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SEEWorkspaceFiles : NSViewController
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFiles.m b/SubEthaEdit-Mac/Source/SEEWorkspaceFiles.m
new file mode 100644
index 000000000..eafe84d97
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFiles.m
@@ -0,0 +1,22 @@
+//
+// SEEWorkspaceFiles.m
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 03.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import "SEEWorkspaceFiles.h"
+
+@interface SEEWorkspaceFiles ()
+
+@end
+
+@implementation SEEWorkspaceFiles
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do view setup here.
+}
+
+@end
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFiles.xib b/SubEthaEdit-Mac/Source/SEEWorkspaceFiles.xib
new file mode 100644
index 000000000..47f1387d0
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFiles.xib
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFilesView.xib b/SubEthaEdit-Mac/Source/SEEWorkspaceFilesView.xib
new file mode 100644
index 000000000..7deca3ff9
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFilesView.xib
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.h b/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.h
new file mode 100644
index 000000000..9fb2447cd
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.h
@@ -0,0 +1,13 @@
+//
+// SEEWorkspaceTreeDelegate.h
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 19.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import
+
+@interface SEEWorkspaceTreeDelegate : NSObject
+
+@end
diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m b/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m
new file mode 100644
index 000000000..6ab4ad38e
--- /dev/null
+++ b/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m
@@ -0,0 +1,39 @@
+//
+// SEEWorkspaceTreeDelegate.m
+// SubEthaEdit
+//
+// Created by Matthias Bartelmeß on 19.08.19.
+// Copyright © 2019 SubEthaEdit Contributors. All rights reserved.
+//
+
+#import "SEEWorkspaceTreeDelegate.h"
+#import "SEEFSTreeNode.h"
+
+@implementation SEEWorkspaceTreeDelegate
+
+-(NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(NSTreeNode *)node {
+ SEEFSTreeNode *item = node.representedObject;
+ NSTableCellView *view;
+ NSString *identifier;
+
+ identifier = @"DataCell";
+ view = [outlineView makeViewWithIdentifier:identifier owner:nil];
+ view.textField.stringValue = item.name;
+ view.imageView.image = item.icon;
+
+
+ return view;
+}
+
+-(BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(NSTreeNode *)node {
+ SEEFSTreeNode *item = node.representedObject;
+ return item.isRoot;
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(NSTreeNode *)node {
+ SEEFSTreeNode *item = node.representedObject;
+ return !item.isRoot;
+}
+
+
+@end
diff --git a/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj b/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj
index d197bde41..b0014cab0 100644
--- a/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj
+++ b/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj
@@ -133,7 +133,16 @@
14E291EA0A07B480007C0065 /* ScriptCharacters.m in Sources */ = {isa = PBXBuildFile; fileRef = 14E291E80A07B480007C0065 /* ScriptCharacters.m */; };
14E294510A0CBFB0007C0065 /* SelectCommand.m in Sources */ = {isa = PBXBuildFile; fileRef = 14E2944F0A0CBFB0007C0065 /* SelectCommand.m */; };
14EC6E280A63F2E800B44481 /* ServicesMenu.strings in Resources */ = {isa = PBXBuildFile; fileRef = 14EC6E270A63F2E800B44481 /* ServicesMenu.strings */; };
- 311717F40A0E8C3600A9DA18 /* ModeSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 311717F20A0E8C3600A9DA18 /* ModeSettings.m */; };
+ 2C04023A23110CD500A31952 /* SEEFSTree.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C04023923110CD500A31952 /* SEEFSTree.m */; };
+ 2C0CC03D22F5CBA000F604C9 /* SEEWorkspace.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C0CC03C22F5CBA000F604C9 /* SEEWorkspace.m */; };
+ 2C0CC04022F624D000F604C9 /* SEEWorkspaceController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C0CC03F22F624D000F604C9 /* SEEWorkspaceController.m */; };
+ 2C0CC04C22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2C0CC04A22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.m */; };
+ 2C0CC04D22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C0CC04B22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.xib */; };
+ 2CE39D61230932950067DBE8 /* ModeSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 311717F20A0E8C3600A9DA18 /* ModeSettings.m */; };
+ 2CE39D66230953A60067DBE8 /* SEEWorkspaceDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CE39D65230953A60067DBE8 /* SEEWorkspaceDocument.m */; };
+ 2CE39D6B2309542F0067DBE8 /* SEEWorkspaceDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CE39D692309542F0067DBE8 /* SEEWorkspaceDocument.xib */; };
+ 2CE39D6E230B3C270067DBE8 /* SEEFSTreeNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CE39D6D230B3C270067DBE8 /* SEEFSTreeNode.m */; };
+ 2CE39D76230B433A0067DBE8 /* SEEWorkspaceTreeDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CE39D75230B433A0067DBE8 /* SEEWorkspaceTreeDelegate.m */; };
3134F7AB0CB1331900F8C116 /* PrecedenceRolloverButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 3134F7A90CB1331900F8C116 /* PrecedenceRolloverButton.m */; };
316A0D2A0C68A47100720268 /* DebugAttributeInspectorController.m in Sources */ = {isa = PBXBuildFile; fileRef = 316A0D280C68A47100720268 /* DebugAttributeInspectorController.m */; };
3170E46112846C670096089C /* SEEStyleSheet.m in Sources */ = {isa = PBXBuildFile; fileRef = 3170E46012846C670096089C /* SEEStyleSheet.m */; };
@@ -790,6 +799,22 @@
2A37F4B0FDCFA73011CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
2A37F4C4FDCFA73011CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
2A37F4C5FDCFA73011CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+ 2C04023823110CD500A31952 /* SEEFSTree.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SEEFSTree.h; sourceTree = ""; };
+ 2C04023923110CD500A31952 /* SEEFSTree.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SEEFSTree.m; sourceTree = ""; };
+ 2C0CC03B22F5CBA000F604C9 /* SEEWorkspace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SEEWorkspace.h; sourceTree = ""; };
+ 2C0CC03C22F5CBA000F604C9 /* SEEWorkspace.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SEEWorkspace.m; sourceTree = ""; };
+ 2C0CC03E22F624D000F604C9 /* SEEWorkspaceController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SEEWorkspaceController.h; sourceTree = ""; };
+ 2C0CC03F22F624D000F604C9 /* SEEWorkspaceController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SEEWorkspaceController.m; sourceTree = ""; };
+ 2C0CC04922F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SEEWorkspaceFileTreeViewController.h; sourceTree = ""; };
+ 2C0CC04A22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SEEWorkspaceFileTreeViewController.m; sourceTree = ""; };
+ 2C0CC04B22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SEEWorkspaceFileTreeViewController.xib; sourceTree = ""; };
+ 2CE39D64230953A60067DBE8 /* SEEWorkspaceDocument.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SEEWorkspaceDocument.h; sourceTree = ""; };
+ 2CE39D65230953A60067DBE8 /* SEEWorkspaceDocument.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SEEWorkspaceDocument.m; sourceTree = ""; };
+ 2CE39D692309542F0067DBE8 /* SEEWorkspaceDocument.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SEEWorkspaceDocument.xib; sourceTree = ""; };
+ 2CE39D6C230B3C270067DBE8 /* SEEFSTreeNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SEEFSTreeNode.h; sourceTree = ""; };
+ 2CE39D6D230B3C270067DBE8 /* SEEFSTreeNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SEEFSTreeNode.m; sourceTree = ""; };
+ 2CE39D74230B433A0067DBE8 /* SEEWorkspaceTreeDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SEEWorkspaceTreeDelegate.h; sourceTree = ""; };
+ 2CE39D75230B433A0067DBE8 /* SEEWorkspaceTreeDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SEEWorkspaceTreeDelegate.m; sourceTree = ""; };
311717F20A0E8C3600A9DA18 /* ModeSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ModeSettings.m; sourceTree = ""; };
311717F30A0E8C3600A9DA18 /* ModeSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ModeSettings.h; sourceTree = ""; };
3134F7A80CB1331800F8C116 /* PrecedenceRolloverButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = PrecedenceRolloverButton.h; sourceTree = ""; };
@@ -1527,6 +1552,9 @@
1428945F09575B9700D14688 /* URLDataProtocol.m */,
1428946009575B9700D14688 /* SEEWebPreviewViewController.h */,
1428946109575B9700D14688 /* SEEWebPreviewViewController.m */,
+ 2CE39D64230953A60067DBE8 /* SEEWorkspaceDocument.h */,
+ 2CE39D65230953A60067DBE8 /* SEEWorkspaceDocument.m */,
+ 2CE39D692309542F0067DBE8 /* SEEWorkspaceDocument.xib */,
);
name = Document;
sourceTree = "";
@@ -1800,6 +1828,26 @@
name = Frameworks;
sourceTree = "";
};
+ 2C0CC03522F5CB6400F604C9 /* Workspaces */ = {
+ isa = PBXGroup;
+ children = (
+ 2C0CC03B22F5CBA000F604C9 /* SEEWorkspace.h */,
+ 2C0CC03C22F5CBA000F604C9 /* SEEWorkspace.m */,
+ 2C0CC03E22F624D000F604C9 /* SEEWorkspaceController.h */,
+ 2C0CC03F22F624D000F604C9 /* SEEWorkspaceController.m */,
+ 2C0CC04922F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.h */,
+ 2C0CC04A22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.m */,
+ 2C0CC04B22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.xib */,
+ 2CE39D6C230B3C270067DBE8 /* SEEFSTreeNode.h */,
+ 2CE39D6D230B3C270067DBE8 /* SEEFSTreeNode.m */,
+ 2CE39D74230B433A0067DBE8 /* SEEWorkspaceTreeDelegate.h */,
+ 2CE39D75230B433A0067DBE8 /* SEEWorkspaceTreeDelegate.m */,
+ 2C04023823110CD500A31952 /* SEEFSTree.h */,
+ 2C04023923110CD500A31952 /* SEEFSTree.m */,
+ );
+ name = Workspaces;
+ sourceTree = "";
+ };
4C6BDC5718896DFF0088CBFC /* XIBs */ = {
isa = PBXGroup;
children = (
@@ -2045,6 +2093,7 @@
F2256A25216358910011D17F /* Sources */ = {
isa = PBXGroup;
children = (
+ 2C0CC03522F5CB6400F604C9 /* Workspaces */,
2A37F4ABFDCFA73011CA2CEA /* Classes */,
4C84D17918B63F83008C4FDC /* Document Hub */,
142892490957526300D14688 /* Document */,
@@ -2427,6 +2476,8 @@
4CD5F7FA17FB0D0C00F49E31 /* GeneralPrefs.xib in Resources */,
F2FEE7161892A747000E6BC8 /* SharingIconWrite.pdf in Resources */,
4CDC272018967D990060B044 /* SEEParticipantView.xib in Resources */,
+ 2CE39D6B2309542F0067DBE8 /* SEEWorkspaceDocument.xib in Resources */,
+ 2C0CC04D22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.xib in Resources */,
F22024D9191D0BFE0098982C /* ParticipantReadOnlyTurnOffPressed.png in Resources */,
F22024DE191D0BFE0098982C /* ParticipantReadOnlyTurnOnNormal@2x.png in Resources */,
F2EF08D119069E33004D5080 /* Localizable.stringsdict in Resources */,
@@ -2590,6 +2641,7 @@
142891340957457500D14688 /* NSDataTCMAdditions.m in Sources */,
142891350957457500D14688 /* NSDictionaryTCMAdditions.m in Sources */,
4CCD790018DB3F9E00A8B457 /* SEEScopedBookmarkAccessoryViewController.m in Sources */,
+ 2C04023A23110CD500A31952 /* SEEFSTree.m in Sources */,
4CD3CFF31887FE2E00908506 /* SEEOpenPanelAccessoryViewController.m in Sources */,
9E9F58AD2319EC3C00E838D1 /* SEEAlertRecipe.m in Sources */,
142891360957457500D14688 /* NSNetServiceTCMAdditions.m in Sources */,
@@ -2654,9 +2706,11 @@
142893A6095758C200D14688 /* StylePreferences.m in Sources */,
142893A7095758C200D14688 /* TableView.m in Sources */,
4C9F60EF191A6E8800979755 /* SEEAuthenticatedSaveMissingScriptRecoveryAttempter.m in Sources */,
+ 2C0CC04C22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.m in Sources */,
142893A8095758C200D14688 /* TCMPreferenceController.m in Sources */,
142893A9095758C200D14688 /* TCMPreferenceModule.m in Sources */,
142893B1095758FD00D14688 /* SelectionOperation.m in Sources */,
+ 2CE39D6E230B3C270067DBE8 /* SEEFSTreeNode.m in Sources */,
142893B2095758FD00D14688 /* TextOperation.m in Sources */,
142893B3095758FD00D14688 /* UserChangeOperation.m in Sources */,
4C0C4A2018B3BA5600E6049E /* SEENetworkDocumentListItem.m in Sources */,
@@ -2665,6 +2719,8 @@
142893C70957594500D14688 /* RegexSymbolDefinition.m in Sources */,
142893C80957594500D14688 /* RegexSymbolParser.m in Sources */,
142893C90957594500D14688 /* SymbolTableEntry.m in Sources */,
+ 2CE39D66230953A60067DBE8 /* SEEWorkspaceDocument.m in Sources */,
+ 2CE39D76230B433A0067DBE8 /* SEEWorkspaceTreeDelegate.m in Sources */,
4CB8EE55181032340051AB6B /* SEEPrintOptionsViewController.m in Sources */,
142893CA0957594500D14688 /* SyntaxDefinition.m in Sources */,
142893CB0957594500D14688 /* SyntaxHighlighter.m in Sources */,
@@ -2717,7 +2773,6 @@
5EDD30100A091BD60036E63A /* NSWindowScriptingAdditions.m in Sources */,
5EDD304F0A092AA50036E63A /* ScriptTextBase.m in Sources */,
14E294510A0CBFB0007C0065 /* SelectCommand.m in Sources */,
- 311717F40A0E8C3600A9DA18 /* ModeSettings.m in Sources */,
5EF31B220A7E2E2100D777F5 /* NSCursorSEEAdditions.m in Sources */,
5E754EA40AB5648F00C5119E /* SEEEncodingDoctorDialogViewController.m in Sources */,
F21DAFE618BB75E1002B3C41 /* SEEFindAndReplaceViewController.m in Sources */,
@@ -2746,6 +2801,7 @@
F2ABC5400C79D9AB007437A2 /* DebugHistoryController.m in Sources */,
4CB36C3218D84BF4000219DA /* SEEEditorSplitViewDelegate.m in Sources */,
F20948BC0C7B3DED00E78EBA /* TCMMMLogStatisticsEntry.m in Sources */,
+ 2C0CC04022F624D000F604C9 /* SEEWorkspaceController.m in Sources */,
F26B7D880CA3F7680039C8A1 /* NSBezierPathTCMAdditions.m in Sources */,
F29E6DD818E5CACE00F84231 /* TCMCGGeometryAdditions.m in Sources */,
31D2C7000CA7C9C8003CC5C9 /* PrecedenceRuleCell.m in Sources */,
@@ -2778,8 +2834,10 @@
F280ABC4131E6ED50091D145 /* SEEStyleSheetEditorWindowController.m in Sources */,
F214C1DB18BE180300908826 /* NSObject+TCMArcLifecycleAdditions.m in Sources */,
F2D56993133B880900CEC841 /* SEEStyleSheetSettings.m in Sources */,
+ 2C0CC03D22F5CBA000F604C9 /* SEEWorkspace.m in Sources */,
F22024EE191D175C0098982C /* TCMHoverButton.m in Sources */,
F29E6DD518E5C90200F84231 /* TCMDragImageView.m in Sources */,
+ 2CE39D61230932950067DBE8 /* ModeSettings.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/SubEthaEdit-Mac/de.lproj/MainMenu.xib b/SubEthaEdit-Mac/de.lproj/MainMenu.xib
index 043c64545..0dc0bb9fa 100644
--- a/SubEthaEdit-Mac/de.lproj/MainMenu.xib
+++ b/SubEthaEdit-Mac/de.lproj/MainMenu.xib
@@ -1010,9 +1010,9 @@ GQ
-
+
-
+
diff --git a/see/see/main.m b/see/see/main.m
index e95693e01..c56ad8416 100644
--- a/see/see/main.m
+++ b/see/see/main.m
@@ -561,30 +561,22 @@ static void openFiles(NSArray *fileNames, NSDictionary *options) {
}
if ([fileNames count] != 0) {
- BOOL isDir;
count = [fileNames count];
for (i = 0; i < count; i++) {
NSString *fileName = [fileNames objectAtIndex:i];
- if ([fileManager fileExistsAtPath:fileName isDirectory:&isDir]) {
- if (isDir) {
- if ([[fileName pathExtension] caseInsensitiveCompare:@"seetext"] == NSOrderedSame) {
- [files addObject:fileName];
- } else {
- //fprintf(stdout, "\"%s\" is a directory.\n", fileName);
- //fflush(stdout);
- }
- } else {
- NSError *error;
- NSString *destination;
- NSDictionary *attributes = [fileManager attributesOfItemAtPath:fileName error:&error];
- if ([attributes[NSFileType] isEqualToString:NSFileTypeSymbolicLink]) {
- if ((destination = [fileManager destinationOfSymbolicLinkAtPath:fileName error:&error])) {
- [files addObject:destination.isAbsolutePath ? destination : [[fileName stringByDeletingLastPathComponent] stringByAppendingPathComponent:destination]];
- }
- } else {
- [files addObject:fileName];
+ if ([fileManager fileExistsAtPath:fileName]) {
+
+ NSError *error;
+ NSString *destination;
+ NSDictionary *attributes = [fileManager attributesOfItemAtPath:fileName error:&error];
+ if ([attributes[NSFileType] isEqualToString:NSFileTypeSymbolicLink]) {
+ if ((destination = [fileManager destinationOfSymbolicLinkAtPath:fileName error:&error])) {
+ [files addObject:destination.isAbsolutePath ? destination : [[fileName stringByDeletingLastPathComponent] stringByAppendingPathComponent:destination]];
}
+ } else {
+ [files addObject:fileName];
}
+
} else {
[newFileNames addObject:fileName];
}