From ae01a4693bc231f54411110a23ef6ff39aab30c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Sun, 4 Aug 2019 22:15:32 +0200 Subject: [PATCH 01/14] wip --- SubEthaEdit-Mac/Source/Debug/SEESwitches.h | 14 ++ .../Source/SEEDocumentController.h | 2 + .../Source/SEEDocumentController.m | 16 ++- SubEthaEdit-Mac/Source/SEEWorkspace.h | 26 ++++ SubEthaEdit-Mac/Source/SEEWorkspace.m | 27 ++++ .../Source/SEEWorkspaceController.h | 24 ++++ .../Source/SEEWorkspaceController.m | 49 +++++++ .../SEEWorkspaceFileTreeViewController.h | 20 +++ .../SEEWorkspaceFileTreeViewController.m | 32 +++++ .../SEEWorkspaceFileTreeViewController.xib | 123 ++++++++++++++++++ SubEthaEdit-Mac/Source/SEEWorkspaceFiles.h | 17 +++ SubEthaEdit-Mac/Source/SEEWorkspaceFiles.m | 22 ++++ SubEthaEdit-Mac/Source/SEEWorkspaceFiles.xib | 19 +++ .../Source/SEEWorkspaceFilesView.xib | 15 +++ .../SubEthaEdit.xcodeproj/project.pbxproj | 33 ++++- SubEthaEdit-Mac/SubEthaEdit_Prefix.pch | 1 + 16 files changed, 437 insertions(+), 3 deletions(-) create mode 100644 SubEthaEdit-Mac/Source/Debug/SEESwitches.h create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspace.h create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspace.m create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceController.h create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceController.m create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceFiles.h create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceFiles.m create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceFiles.xib create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceFilesView.xib diff --git a/SubEthaEdit-Mac/Source/Debug/SEESwitches.h b/SubEthaEdit-Mac/Source/Debug/SEESwitches.h new file mode 100644 index 000000000..c5dd05213 --- /dev/null +++ b/SubEthaEdit-Mac/Source/Debug/SEESwitches.h @@ -0,0 +1,14 @@ +// +// SEESwitches.h +// SubEthaEdit +// +// Created by Matthias Bartelmeß on 03.08.19. +// Copyright © 2019 SubEthaEdit Contributors. All rights reserved. +// + +#ifndef SEESwitches_h +#define SEESwitches_h + +#define SEEWorkspacesEnabled 1 + +#endif /* SEESwitches_h */ diff --git a/SubEthaEdit-Mac/Source/SEEDocumentController.h b/SubEthaEdit-Mac/Source/SEEDocumentController.h index 04d1e0f15..bdc66817c 100644 --- a/SubEthaEdit-Mac/Source/SEEDocumentController.h +++ b/SubEthaEdit-Mac/Source/SEEDocumentController.h @@ -12,6 +12,7 @@ @class PlainTextWindowController; @class MAAttachedWindow; @class PlainTextDocument; +@class SEEWorkspaceController; extern NSString *const RecentDocumentsDidChangeNotification; @@ -34,6 +35,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; diff --git a/SubEthaEdit-Mac/Source/SEEDocumentController.m b/SubEthaEdit-Mac/Source/SEEDocumentController.m index 2cb3e6e93..9ecbc3356 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] init]; I_propertiesForOpenedFiles = [NSMutableDictionary new]; I_suspendedSeeScriptCommands = [NSMutableDictionary new]; @@ -1733,9 +1736,18 @@ - (void)openModeFile:(NSString *)fileName { } } +- (void)openWorkspace:(NSURL *)aURL { + [self.workspaceController workspaceForURL:aURL]; +} + - (void)openDirectory:(NSURL *)aURL { - [self setLocationForNextOpenPanel:aURL]; - [self performSelector:@selector(openDocument:) withObject:nil afterDelay:0.0]; + if(SEEWorkspacesEnabled) { + [self openWorkspace:aURL]; + } else { + [self setLocationForNextOpenPanel:aURL]; + [self performSelector:@selector(openDocument:) withObject:nil afterDelay:0.0]; + } + DEBUGLOG(@"FileIOLogDomain", SimpleLogLevel, @"Opening directory: %@", aURL); } diff --git a/SubEthaEdit-Mac/Source/SEEWorkspace.h b/SubEthaEdit-Mac/Source/SEEWorkspace.h new file mode 100644 index 000000000..7f8bbe89c --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspace.h @@ -0,0 +1,26 @@ +// +// SEEWorkspace.h +// SubEthaEdit +// +// Created by Matthias Bartelmeß on 03.08.19. +// Copyright © 2019 SubEthaEdit Contributors. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SEEWorkspace : NSObject { + NSMutableArray *documents; +} + +@property (nonatomic, readonly) NSURL *baseURL; +@property (nonatomic, readonly) NSArray *openDocuments; + +-(instancetype)initWithBaseURL:(NSURL *)url; + +-(BOOL)containsDocument:(NSDocument *)doc; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SubEthaEdit-Mac/Source/SEEWorkspace.m b/SubEthaEdit-Mac/Source/SEEWorkspace.m new file mode 100644 index 000000000..27031ed71 --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspace.m @@ -0,0 +1,27 @@ +// +// SEEWorkspace.m +// SubEthaEdit +// +// Created by Matthias Bartelmeß on 03.08.19. +// Copyright © 2019 SubEthaEdit Contributors. All rights reserved. +// + +#import "SEEWorkspace.h" + +@implementation SEEWorkspace + +- (instancetype)initWithBaseURL:(NSURL *)url +{ + self = [super init]; + if (self) { + _baseURL = url; + documents = [NSMutableArray new]; + } + return self; +} + +-(BOOL)containsDocument:(NSDocument *)doc { + return [documents containsObject:doc]; +} + +@end diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceController.h b/SubEthaEdit-Mac/Source/SEEWorkspaceController.h new file mode 100644 index 000000000..7f6c55560 --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceController.h @@ -0,0 +1,24 @@ +// +// SEEWorkspaceController.h +// SubEthaEdit +// +// Created by Matthias Bartelmeß on 03.08.19. +// Copyright © 2019 SubEthaEdit Contributors. All rights reserved. +// + +#import + +@class SEEWorkspace; + +@interface SEEWorkspaceController : NSObject { + + @private + NSMutableArray *workspaces; +} + +-(SEEWorkspace *)workspaceForDocument:(NSDocument *)document; + +// Creates a new workspace if none exists yet for the provided url +-(SEEWorkspace *)workspaceForURL:(NSURL *)url; + +@end diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceController.m b/SubEthaEdit-Mac/Source/SEEWorkspaceController.m new file mode 100644 index 000000000..537bb0149 --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceController.m @@ -0,0 +1,49 @@ +// +// SEEWorkspaceController.m +// SubEthaEdit +// +// Created by Matthias Bartelmeß on 03.08.19. +// Copyright © 2019 SubEthaEdit Contributors. All rights reserved. +// + +#import "SEEWorkspaceController.h" +#import "SEEWorkspace.h" + +@interface SEEWorkspaceController () + +@end + +@implementation SEEWorkspaceController + + +- (instancetype)init +{ + self = [super init]; + if (self) { + workspaces = [NSMutableArray new]; + } + return self; +} + +-(SEEWorkspace *)workspaceForDocument:(NSDocument *)document { + for (SEEWorkspace *ws in workspaces) { + if ([ws containsDocument:document]) { + return ws; + } + } + return nil; +} + +-(SEEWorkspace *)workspaceForURL:(NSURL *)url { + for (SEEWorkspace *ws in workspaces) { + if ([ws.baseURL isEqual:url]) { + return ws; + } + } + + SEEWorkspace *workspace = [[SEEWorkspace alloc] initWithBaseURL:url]; + [workspaces addObject:workspace]; + return workspace; +} + +@end diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h new file mode 100644 index 000000000..d636bcc13 --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h @@ -0,0 +1,20 @@ +// +// SEEWorkspaceFileTreeViewController.h +// SubEthaEdit +// +// Created by Matthias Bartelmeß on 03.08.19. +// Copyright © 2019 SubEthaEdit Contributors. All rights reserved. +// + +#import + +@class SEEWorkspace; + +NS_ASSUME_NONNULL_BEGIN + +@interface SEEWorkspaceFileTreeViewController : NSViewController + +@property (nonatomic, weak) SEEWorkspace *workspace; +@end + +NS_ASSUME_NONNULL_END diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m new file mode 100644 index 000000000..39850efec --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m @@ -0,0 +1,32 @@ +// +// SEEWorkspaceFileTreeViewController.m +// SubEthaEdit +// +// Created by Matthias Bartelmeß on 03.08.19. +// Copyright © 2019 SubEthaEdit Contributors. All rights reserved. +// + +#import "SEEWorkspaceFileTreeViewController.h" + +@interface SEEWorkspaceFileTreeViewController () + +@property (nonatomic, weak) IBOutlet NSTreeController *treeController; + +@end + +@implementation SEEWorkspaceFileTreeViewController + +- (instancetype)initWithWorkspace:(SEEWorkspace *)workspace { + self = [super initWithNibName:@"SEEWorkspaceFileTreeViewController" bundle:nil]; + if (self) { + self.workspace = workspace; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do view setup here. +} + +@end diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib new file mode 100644 index 000000000..98aa8d4f3 --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/SubEthaEdit.xcodeproj/project.pbxproj b/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj index 1560ca4de..b5f74a4e7 100644 --- a/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj +++ b/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj @@ -133,7 +133,10 @@ 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 */; }; + 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 */; }; 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 */; }; @@ -792,6 +795,14 @@ 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; }; + 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 = ""; }; + 2C0CC04122F6299E00F604C9 /* SEESwitches.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SEESwitches.h; path = Debug/SEESwitches.h; 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 = ""; }; 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 = ""; }; @@ -1445,6 +1456,7 @@ F20AB8781907C7C500444F4B /* SEEDebugImageGenerationWindowController.h */, F20AB8791907C7C500444F4B /* SEEDebugImageGenerationWindowController.m */, F2C44CBD21635F29006D59DB /* SEEDebugImageGenerationWindowController.xib */, + 2C0CC04122F6299E00F604C9 /* SEESwitches.h */, ); name = Debug; sourceTree = ""; @@ -1803,6 +1815,20 @@ 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 */, + ); + name = Workspaces; + sourceTree = ""; + }; 4C6BDC5718896DFF0088CBFC /* XIBs */ = { isa = PBXGroup; children = ( @@ -2051,6 +2077,7 @@ F2256A25216358910011D17F /* Sources */ = { isa = PBXGroup; children = ( + 2C0CC03522F5CB6400F604C9 /* Workspaces */, 2A37F4ABFDCFA73011CA2CEA /* Classes */, 4C84D17918B63F83008C4FDC /* Document Hub */, 142892490957526300D14688 /* Document */, @@ -2435,6 +2462,7 @@ 4CD5F7FA17FB0D0C00F49E31 /* GeneralPrefs.xib in Resources */, F2FEE7161892A747000E6BC8 /* SharingIconWrite.pdf in Resources */, 4CDC272018967D990060B044 /* SEEParticipantView.xib in Resources */, + 2C0CC04D22F62AC400F604C9 /* SEEWorkspaceFileTreeViewController.xib in Resources */, F22024D9191D0BFE0098982C /* ParticipantReadOnlyTurnOffPressed.png in Resources */, F22024DE191D0BFE0098982C /* ParticipantReadOnlyTurnOnNormal@2x.png in Resources */, F2EF08D119069E33004D5080 /* Localizable.stringsdict in Resources */, @@ -2662,6 +2690,7 @@ 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 */, @@ -2754,6 +2783,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 */, @@ -2786,6 +2816,7 @@ 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 */, ); diff --git a/SubEthaEdit-Mac/SubEthaEdit_Prefix.pch b/SubEthaEdit-Mac/SubEthaEdit_Prefix.pch index 30f8a399a..11fb056f8 100644 --- a/SubEthaEdit-Mac/SubEthaEdit_Prefix.pch +++ b/SubEthaEdit-Mac/SubEthaEdit_Prefix.pch @@ -26,6 +26,7 @@ #import "NSObject+TCMArcLifecycleAdditions.h" #import "NSOperationQueue+TCMAdditions.h" #import "TCMCGGeometryAdditions.h" + #import "SEESwitches.h" extern NSString * const AddressHistory; From df170507a73dabe7d47bc470e4717e4118c899a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Tue, 27 Aug 2019 22:01:44 +0200 Subject: [PATCH 02/14] WIP --- SubEthaEdit-Mac/Base.lproj/MainMenu.xib | 7 +- SubEthaEdit-Mac/Info.plist | 2 +- SubEthaEdit-Mac/Source/PlainTextDocument.h | 4 +- SubEthaEdit-Mac/Source/PlainTextDocument.m | 2 + .../Source/PlainTextWindowController.m | 10 ++ .../Source/SEEDocumentController.h | 3 + .../Source/SEEDocumentController.m | 43 ++++-- SubEthaEdit-Mac/Source/SEEFSTree.h | 22 +++ SubEthaEdit-Mac/Source/SEEFSTree.m | 75 ++++++++++ SubEthaEdit-Mac/Source/SEEFSTreeNode.h | 27 ++++ SubEthaEdit-Mac/Source/SEEFSTreeNode.m | 138 ++++++++++++++++++ SubEthaEdit-Mac/Source/SEEWorkspace.h | 23 ++- SubEthaEdit-Mac/Source/SEEWorkspace.m | 42 +++++- .../Source/SEEWorkspaceController.h | 30 +++- .../Source/SEEWorkspaceController.m | 50 +++++-- SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h | 17 +++ SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m | 59 ++++++++ .../Source/SEEWorkspaceDocument.xib | 32 ++++ .../SEEWorkspaceFileTreeViewController.h | 8 +- .../SEEWorkspaceFileTreeViewController.m | 38 ++++- .../SEEWorkspaceFileTreeViewController.xib | 32 ++-- .../Source/SEEWorkspaceTreeDelegate.h | 13 ++ .../Source/SEEWorkspaceTreeDelegate.m | 24 +++ .../SubEthaEdit.xcodeproj/project.pbxproj | 31 +++- SubEthaEdit-Mac/de.lproj/MainMenu.xib | 4 +- 25 files changed, 670 insertions(+), 66 deletions(-) create mode 100644 SubEthaEdit-Mac/Source/SEEFSTree.h create mode 100644 SubEthaEdit-Mac/Source/SEEFSTree.m create mode 100644 SubEthaEdit-Mac/Source/SEEFSTreeNode.h create mode 100644 SubEthaEdit-Mac/Source/SEEFSTreeNode.m create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceDocument.xib create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.h create mode 100644 SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m diff --git a/SubEthaEdit-Mac/Base.lproj/MainMenu.xib b/SubEthaEdit-Mac/Base.lproj/MainMenu.xib index 5046b3152..607223b37 100644 --- a/SubEthaEdit-Mac/Base.lproj/MainMenu.xib +++ b/SubEthaEdit-Mac/Base.lproj/MainMenu.xib @@ -1005,9 +1005,9 @@ - + - + @@ -1080,5 +1080,8 @@ + + + diff --git a/SubEthaEdit-Mac/Info.plist b/SubEthaEdit-Mac/Info.plist index 8150582bd..1917ad356 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/Source/PlainTextDocument.h b/SubEthaEdit-Mac/Source/PlainTextDocument.h index bfc13099d..c68a476f4 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" enum { UnknownStringEncoding = NoStringEncoding, @@ -45,7 +46,7 @@ extern NSString * const PlainTextDocumentDidSaveNotification; extern NSString * const PlainTextDocumentDidSaveShouldReloadWebPreviewNotification; -@interface PlainTextDocument : NSDocument +@interface PlainTextDocument : NSDocument { TCMMMSession *I_session; struct { @@ -150,7 +151,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 7d0f6627a..d3b5771af 100644 --- a/SubEthaEdit-Mac/Source/PlainTextDocument.m +++ b/SubEthaEdit-Mac/Source/PlainTextDocument.m @@ -146,6 +146,8 @@ - (void)TCM_validateLineEndings; @implementation PlainTextDocument +@synthesize workspace; + + (void)initialize { if (self == [PlainTextDocument class]) { diff --git a/SubEthaEdit-Mac/Source/PlainTextWindowController.m b/SubEthaEdit-Mac/Source/PlainTextWindowController.m index cefbaca43..d51a633db 100644 --- a/SubEthaEdit-Mac/Source/PlainTextWindowController.m +++ b/SubEthaEdit-Mac/Source/PlainTextWindowController.m @@ -296,6 +296,8 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { } else { return NO; } + } else if (selector == @selector(showWorkspace:)) { + return self.plainTextDocument.workspace != nil; } else if (selector == @selector(showDocumentAtIndex:)) { int documentNumberToShow = [[menuItem representedObject] intValue]; id document = nil; @@ -402,6 +404,14 @@ - (IBAction)copyDocumentURL:(id)aSender { [documentURL writeToPasteboard:pboard]; } +- (IBAction)showWorkspace:(id)sender { + SEEDocumentController * documentController = (SEEDocumentController *)[NSDocumentController sharedDocumentController]; + + [documentController openWorkspace:self.plainTextDocument.workspace + display:YES + withCompletionHandler:nil]; +} + #pragma mark - diff --git a/SubEthaEdit-Mac/Source/SEEDocumentController.h b/SubEthaEdit-Mac/Source/SEEDocumentController.h index bdc66817c..1edc64aee 100644 --- a/SubEthaEdit-Mac/Source/SEEDocumentController.h +++ b/SubEthaEdit-Mac/Source/SEEDocumentController.h @@ -13,6 +13,7 @@ @class MAAttachedWindow; @class PlainTextDocument; @class SEEWorkspaceController; +@class SEEWorkspace; extern NSString *const RecentDocumentsDidChangeNotification; @@ -53,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 9ecbc3356..a2acbfc7d 100644 --- a/SubEthaEdit-Mac/Source/SEEDocumentController.m +++ b/SubEthaEdit-Mac/Source/SEEDocumentController.m @@ -105,7 +105,7 @@ - (instancetype)init { if (self) { self.filenamesFromLastRunOpenPanel = [NSMutableArray array]; self.documentCreationFlagsLookupDict = [NSMutableDictionary dictionary]; - self.workspaceController = [[SEEWorkspaceController alloc] init]; + self.workspaceController = [[SEEWorkspaceController alloc] initWithDocumentController:self]; I_propertiesForOpenedFiles = [NSMutableDictionary new]; I_suspendedSeeScriptCommands = [NSMutableDictionary new]; @@ -714,11 +714,14 @@ - (void)openDocumentWithContentsOfURL:(NSURL *)url display:(BOOL)displayDocument completionHandler(nil, NO, nil); } } else if (!isFilePackage && isDirectory) { - [self openDirectory:url]; + if(SEEWorkspacesEnabled) { + [self openWorkspaceWithURL:url display:displayDocument withCompletionHandler:completionHandler]; + } else { + + [self openDirectory:url]; - if (completionHandler) { - completionHandler(nil, NO, nil); - } + } + } else { NSAppleEventDescriptor *eventDesc = [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent]; NSDictionary *parsed = [PlainTextDocument parseOpenDocumentEvent:eventDesc]; @@ -757,6 +760,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 @@ -1608,6 +1615,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 @@ -1736,18 +1744,24 @@ - (void)openModeFile:(NSString *)fileName { } } -- (void)openWorkspace:(NSURL *)aURL { - [self.workspaceController workspaceForURL:aURL]; +- (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)openDirectory:(NSURL *)aURL { - if(SEEWorkspacesEnabled) { - [self openWorkspace:aURL]; - } else { - [self setLocationForNextOpenPanel:aURL]; - [self performSelector:@selector(openDocument:) withObject:nil afterDelay:0.0]; - } +-(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]; DEBUGLOG(@"FileIOLogDomain", SimpleLogLevel, @"Opening directory: %@", aURL); } @@ -1781,4 +1795,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..0b7b8c79b --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEFSTree.m @@ -0,0 +1,75 @@ +// +// 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]; + + NSArray *pathsToWatch = @[anURL.path]; + + CFAbsoluteTime latency = 0.5; // Latency in seconds + FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagUseCFTypes; + + FSEventStreamContext 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 { + [[self.root nodeForPath:path onlyIfCached:YES] reload]; +} + + +@end diff --git a/SubEthaEdit-Mac/Source/SEEFSTreeNode.h b/SubEthaEdit-Mac/Source/SEEFSTreeNode.h new file mode 100644 index 000000000..6ccdcbfde --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.h @@ -0,0 +1,27 @@ +// +// 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; + +- (NSString *)name; +- (NSImage *)icon; +- (NSArray *)children; +- (NSURL *)url; +- (BOOL)isLeaf; +- (void)reload; + +- (SEEFSTreeNode * )nodeForPath:(NSString *)path; +- (SEEFSTreeNode * )nodeForPath:(NSString *)path onlyIfCached:(BOOL)cached; +@end + + diff --git a/SubEthaEdit-Mac/Source/SEEFSTreeNode.m b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m new file mode 100644 index 000000000..d59f787fc --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m @@ -0,0 +1,138 @@ +// +// 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; +@end + + +@implementation SEEFSTreeNode { + NSURL *url; + NSArray *children; +} + +- (instancetype)initWithURL:(NSURL *)anURL +{ + self = [super init]; + if (self) { + url = anURL; + + NSDictionary *values = [url resourceValuesForKeys:@[NSURLIsDirectoryKey] + error:nil]; + + self.isFolder = [((NSNumber *)values[NSURLIsDirectoryKey]) boolValue]; + + + } + return self; +} + + + +- (NSString *)name { + return url.lastPathComponent; +} + +- (NSArray *)children { + if(!children) { + NSArray *contents = [NSFileManager.defaultManager contentsOfDirectoryAtURL:url + includingPropertiesForKeys:@[NSURLIsDirectoryKey] + options:0 + error:nil]; + NSMutableArray * _children = [NSMutableArray arrayWithCapacity:contents.count]; + for (NSURL *url in contents) { + [_children addObject:[[SEEFSTreeNode alloc] initWithURL:url]]; + } + children = [_children sortedArrayUsingComparator:^NSComparisonResult(SEEFSTreeNode *obj1, SEEFSTreeNode *obj2) { + NSComparisonResult result = NSOrderedSame; + if (obj1.isLeaf && !obj2.isLeaf) { + result = NSOrderedDescending; + } else if(!obj1.isLeaf && obj2.isLeaf) { + result = NSOrderedAscending; + } else { + result = [obj1.name compare:obj2.name]; + } + + return result; + }]; + } + + return children; +} + +- (NSImage *)icon { + return [[NSWorkspace sharedWorkspace] iconForFile:url.filePathURL.path]; +} + +- (BOOL)isLeaf { + return !_isFolder; +} + +-(NSURL *)url { + return url; +} + +- (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 nodeNamed: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; +} + +- (void)reload { + [self willChangeValueForKey:@"children"]; + children = nil; + [self didChangeValueForKey:@"children"]; +} + +@end diff --git a/SubEthaEdit-Mac/Source/SEEWorkspace.h b/SubEthaEdit-Mac/Source/SEEWorkspace.h index 7f8bbe89c..e3fd91046 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspace.h +++ b/SubEthaEdit-Mac/Source/SEEWorkspace.h @@ -8,19 +8,34 @@ #import -NS_ASSUME_NONNULL_BEGIN + + +@class SEEWorkspace; + +@protocol SEEWorkspaceDocument +@property (nonatomic, weak) SEEWorkspace *workspace; + +@optional +-(BOOL) requiresWorkspace; + +@end @interface SEEWorkspace : NSObject { - NSMutableArray *documents; + NSMutableArray *> *_documents; } @property (nonatomic, readonly) NSURL *baseURL; -@property (nonatomic, readonly) NSArray *openDocuments; +@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 -NS_ASSUME_NONNULL_END + diff --git a/SubEthaEdit-Mac/Source/SEEWorkspace.m b/SubEthaEdit-Mac/Source/SEEWorkspace.m index 27031ed71..7195c2f1f 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspace.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspace.m @@ -14,14 +14,48 @@ - (instancetype)initWithBaseURL:(NSURL *)url { self = [super init]; if (self) { - _baseURL = url; - documents = [NSMutableArray new]; + 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 [documents containsObject:doc]; +-(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 index 7f6c55560..14f70b20c 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceController.h +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceController.h @@ -6,19 +6,39 @@ // Copyright © 2019 SubEthaEdit Contributors. All rights reserved. // +/* + +-----------------------------+ +-----------------------------+ + | | | | + | SEEWorkspaceController |<----1--+ SEEDocumentController | + | | | | + +-------------+---------------+ +--------------+--------------+ + * * + | | + v v + +-----------------------------+ +-----------------------------+ + | | | | + | SEEWorkspace +-*----->| NSDocument | + | | | | + +-----------------------------+ +-----------------------------+ + + */ + #import @class SEEWorkspace; -@interface SEEWorkspaceController : NSObject { +@interface SEEWorkspaceController : NSObject + +@property (nonatomic, weak, readonly) NSDocumentController *documentController; - @private - NSMutableArray *workspaces; -} +-(instancetype)initWithDocumentController:(NSDocumentController *)controller; -(SEEWorkspace *)workspaceForDocument:(NSDocument *)document; -// Creates a new workspace if none exists yet for the provided url -(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 index 537bb0149..4b5e07610 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceController.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceController.m @@ -7,19 +7,25 @@ // #import "SEEWorkspaceController.h" +#import "SEEWorkspaceDocument.h" #import "SEEWorkspace.h" @interface SEEWorkspaceController () @end -@implementation SEEWorkspaceController +@implementation SEEWorkspaceController { +@private + NSMutableArray *workspaces; +} +// TODO: prune empty workspaces -- (instancetype)init +- (instancetype)initWithDocumentController:(NSDocumentController *)controller; { self = [super init]; if (self) { + _documentController = controller; workspaces = [NSMutableArray new]; } return self; @@ -35,15 +41,41 @@ -(SEEWorkspace *)workspaceForDocument:(NSDocument *)document { } -(SEEWorkspace *)workspaceForURL:(NSURL *)url { - for (SEEWorkspace *ws in workspaces) { - if ([ws.baseURL isEqual:url]) { - return ws; - } - } + return [self workspaceForURL:url createIfNeeded:NO]; +} + +-(SEEWorkspace *)workspaceForURL:(NSURL *)url createIfNeeded:(BOOL)create { + SEEWorkspace *workspace = [workspaces SEE_firstObjectPassingTest:^BOOL(SEEWorkspace *ws) { + return [ws containsURL:url]; + }]; - SEEWorkspace *workspace = [[SEEWorkspace alloc] initWithBaseURL:url]; - [workspaces addObject:workspace]; + if(create && !workspace) { + workspace = [[SEEWorkspace alloc] initWithBaseURL:url]; + [workspaces addObject:workspace]; + } return workspace; } +-(void)addDocument:(NSDocument *)document { + if([document conformsToProtocol:@protocol(SEEWorkspaceDocument)]) { + [self assignDocumentToWorkspace:(NSDocument *)document]; + } +} + +-(void)removeDocument:(NSDocument *)document { + if([document conformsToProtocol:@protocol(SEEWorkspaceDocument)]) { + [[self workspaceForDocument:document] removeDocument:(NSDocument *)document]; + } +} + +-(void)assignDocumentToWorkspace:(NSDocument *)document { + SEEWorkspace *currentWorkspace = [self workspaceForDocument:document]; + 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..d8a5a9807 --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h @@ -0,0 +1,17 @@ +// +// 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 +@end + + diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m new file mode 100644 index 000000000..147995dac --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m @@ -0,0 +1,59 @@ +// +// 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]; +} + +- (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..ba9dd5943 --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.xib @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h index d636bcc13..201cc2a52 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h @@ -10,11 +10,13 @@ @class SEEWorkspace; -NS_ASSUME_NONNULL_BEGIN -@interface SEEWorkspaceFileTreeViewController : NSViewController + +@interface SEEWorkspaceFileTreeViewController : NSViewController + +- (instancetype)initWithWorkspace:(SEEWorkspace *)workspace; @property (nonatomic, weak) SEEWorkspace *workspace; @end -NS_ASSUME_NONNULL_END + diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m index 39850efec..4c462f7f8 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m @@ -7,14 +7,21 @@ // #import "SEEWorkspaceFileTreeViewController.h" +#import "SEEFSTreeNode.h" +#import "SEEWorkspace.h" +#import "SEEWorkspaceTreeDelegate.h" +#import "SEEFSTree.h" @interface SEEWorkspaceFileTreeViewController () -@property (nonatomic, weak) IBOutlet NSTreeController *treeController; +@property (nonatomic, strong) IBOutlet NSTreeController *treeController; +@property (nonatomic, strong) IBOutlet NSOutlineView *outlineView; @end -@implementation SEEWorkspaceFileTreeViewController +@implementation SEEWorkspaceFileTreeViewController{ + SEEFSTree *tree; +} - (instancetype)initWithWorkspace:(SEEWorkspace *)workspace { self = [super initWithNibName:@"SEEWorkspaceFileTreeViewController" bundle:nil]; @@ -26,7 +33,32 @@ - (instancetype)initWithWorkspace:(SEEWorkspace *)workspace { - (void)viewDidLoad { [super viewDidLoad]; - // Do view setup here. + tree = [[SEEFSTree alloc] initWithURL:self.workspace.baseURL]; + [self.treeController setContent:tree.root]; + +} + +- (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) { + + }]; } @end diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib index 98aa8d4f3..e4ac7160f 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib @@ -8,7 +8,8 @@ - + + @@ -18,21 +19,21 @@ - - - + + + - + - - + + - + @@ -71,9 +72,6 @@ - - - @@ -91,13 +89,14 @@ - - - - + + + + + @@ -115,7 +114,8 @@ - + + 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..7132c80cb --- /dev/null +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m @@ -0,0 +1,24 @@ +// +// 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; + NSString *identifier = @"DataCell"; + NSTableCellView *view = [outlineView makeViewWithIdentifier:identifier + owner:nil]; + view.textField.stringValue = item.name; + view.imageView.image = item.icon; + return view; +} + +@end diff --git a/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj b/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj index b5f74a4e7..c8af435b1 100644 --- a/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj +++ b/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj @@ -133,10 +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 */; }; + 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 */; }; @@ -795,6 +801,8 @@ 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 = ""; }; @@ -803,6 +811,13 @@ 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 = ""; }; @@ -1544,6 +1559,9 @@ 1428945F09575B9700D14688 /* URLDataProtocol.m */, 1428946009575B9700D14688 /* SEEWebPreviewViewController.h */, 1428946109575B9700D14688 /* SEEWebPreviewViewController.m */, + 2CE39D64230953A60067DBE8 /* SEEWorkspaceDocument.h */, + 2CE39D65230953A60067DBE8 /* SEEWorkspaceDocument.m */, + 2CE39D692309542F0067DBE8 /* SEEWorkspaceDocument.xib */, ); name = Document; sourceTree = ""; @@ -1825,6 +1843,12 @@ 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 = ""; @@ -2462,6 +2486,7 @@ 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 */, @@ -2627,6 +2652,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 */, 142891360957457500D14688 /* NSNetServiceTCMAdditions.m in Sources */, 142891370957457500D14688 /* NSStringTCMAdditions.m in Sources */, @@ -2694,6 +2720,7 @@ 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 */, @@ -2702,6 +2729,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 */, @@ -2754,7 +2783,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 */, @@ -2819,6 +2847,7 @@ 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 a49febe64..08b105221 100644 --- a/SubEthaEdit-Mac/de.lproj/MainMenu.xib +++ b/SubEthaEdit-Mac/de.lproj/MainMenu.xib @@ -1002,9 +1002,9 @@ - + - + From e65faa997afd2faed87db35835604101b7e091e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Tue, 27 Aug 2019 22:05:14 +0200 Subject: [PATCH 03/14] wip --- SubEthaEdit-Mac/Source/Debug/SEESwitches.h | 14 -------------- SubEthaEdit-Mac/Source/SEEDocumentController.m | 11 +++-------- .../SubEthaEdit.xcodeproj/project.pbxproj | 2 -- SubEthaEdit-Mac/SubEthaEdit_Prefix.pch | 1 - 4 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 SubEthaEdit-Mac/Source/Debug/SEESwitches.h diff --git a/SubEthaEdit-Mac/Source/Debug/SEESwitches.h b/SubEthaEdit-Mac/Source/Debug/SEESwitches.h deleted file mode 100644 index c5dd05213..000000000 --- a/SubEthaEdit-Mac/Source/Debug/SEESwitches.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// SEESwitches.h -// SubEthaEdit -// -// Created by Matthias Bartelmeß on 03.08.19. -// Copyright © 2019 SubEthaEdit Contributors. All rights reserved. -// - -#ifndef SEESwitches_h -#define SEESwitches_h - -#define SEEWorkspacesEnabled 1 - -#endif /* SEESwitches_h */ diff --git a/SubEthaEdit-Mac/Source/SEEDocumentController.m b/SubEthaEdit-Mac/Source/SEEDocumentController.m index a2acbfc7d..ed8f8dd4e 100644 --- a/SubEthaEdit-Mac/Source/SEEDocumentController.m +++ b/SubEthaEdit-Mac/Source/SEEDocumentController.m @@ -714,13 +714,8 @@ - (void)openDocumentWithContentsOfURL:(NSURL *)url display:(BOOL)displayDocument completionHandler(nil, NO, nil); } } else if (!isFilePackage && isDirectory) { - if(SEEWorkspacesEnabled) { - [self openWorkspaceWithURL:url display:displayDocument withCompletionHandler:completionHandler]; - } else { - - [self openDirectory:url]; - - } + [self openWorkspaceWithURL:url display:displayDocument withCompletionHandler:completionHandler]; + } else { NSAppleEventDescriptor *eventDesc = [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent]; @@ -1078,7 +1073,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) { diff --git a/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj b/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj index c8af435b1..67b20e5d8 100644 --- a/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj +++ b/SubEthaEdit-Mac/SubEthaEdit.xcodeproj/project.pbxproj @@ -807,7 +807,6 @@ 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 = ""; }; - 2C0CC04122F6299E00F604C9 /* SEESwitches.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SEESwitches.h; path = Debug/SEESwitches.h; 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 = ""; }; @@ -1471,7 +1470,6 @@ F20AB8781907C7C500444F4B /* SEEDebugImageGenerationWindowController.h */, F20AB8791907C7C500444F4B /* SEEDebugImageGenerationWindowController.m */, F2C44CBD21635F29006D59DB /* SEEDebugImageGenerationWindowController.xib */, - 2C0CC04122F6299E00F604C9 /* SEESwitches.h */, ); name = Debug; sourceTree = ""; diff --git a/SubEthaEdit-Mac/SubEthaEdit_Prefix.pch b/SubEthaEdit-Mac/SubEthaEdit_Prefix.pch index 11fb056f8..30f8a399a 100644 --- a/SubEthaEdit-Mac/SubEthaEdit_Prefix.pch +++ b/SubEthaEdit-Mac/SubEthaEdit_Prefix.pch @@ -26,7 +26,6 @@ #import "NSObject+TCMArcLifecycleAdditions.h" #import "NSOperationQueue+TCMAdditions.h" #import "TCMCGGeometryAdditions.h" - #import "SEESwitches.h" extern NSString * const AddressHistory; From 947bbe83542d2757e7c31c6995c49ae242f5811c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Wed, 28 Aug 2019 20:02:45 +0200 Subject: [PATCH 04/14] [FEAT] see tool can open directories --- see/see/main.m | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/see/see/main.m b/see/see/main.m index b9549467b..6b9471879 100644 --- a/see/see/main.m +++ b/see/see/main.m @@ -570,30 +570,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]; } From f453959e3cc73fda6cdcb49e36fbb8e56de20321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Wed, 28 Aug 2019 20:16:28 +0200 Subject: [PATCH 05/14] [FIX] Crash due to uninitialized memory --- SubEthaEdit-Mac/Source/SEEFSTree.m | 1 + 1 file changed, 1 insertion(+) diff --git a/SubEthaEdit-Mac/Source/SEEFSTree.m b/SubEthaEdit-Mac/Source/SEEFSTree.m index 0b7b8c79b..6d10ae61d 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTree.m +++ b/SubEthaEdit-Mac/Source/SEEFSTree.m @@ -43,6 +43,7 @@ - (instancetype)initWithURL:(NSURL *)anURL; FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagUseCFTypes; FSEventStreamContext ctx; + memset(&ctx, 0, sizeof(ctx)); ctx.info = (__bridge void *)self; fsEventStream = FSEventStreamCreate(NULL, From ba5a4ad566cda436efc793a2956823c312065a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Wed, 28 Aug 2019 20:16:52 +0200 Subject: [PATCH 06/14] [FIX] declare Workspace Windows as auxiliary --- SubEthaEdit-Mac/Source/SEEWorkspaceDocument.xib | 1 + 1 file changed, 1 insertion(+) diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.xib b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.xib index ba9dd5943..41f395e56 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.xib +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.xib @@ -15,6 +15,7 @@ + From 0a67bf063bf0d279ed3f8ee0092d5178ca6511b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Fri, 30 Aug 2019 16:09:10 +0200 Subject: [PATCH 07/14] WIP --- ...SEEPlainTextEditorTopBarViewController.xib | 13 +++++++++ SubEthaEdit-Mac/Source/PlainTextDocument.h | 1 + SubEthaEdit-Mac/Source/PlainTextDocument.m | 17 +++++++++++ .../Source/PlainTextWindowController.m | 7 ++++- SubEthaEdit-Mac/Source/SEEFSTree.m | 2 +- SubEthaEdit-Mac/Source/SEEFSTreeNode.h | 3 +- SubEthaEdit-Mac/Source/SEEFSTreeNode.m | 21 ++++++++++++-- .../SEEPlainTextEditorTopBarViewController.m | 29 +++++++++++++++++-- .../Source/SEEWorkspaceController.m | 6 ++++ SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h | 3 ++ SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m | 4 +++ .../SEEWorkspaceFileTreeViewController.h | 3 ++ .../SEEWorkspaceFileTreeViewController.m | 5 ++++ 13 files changed, 106 insertions(+), 8 deletions(-) 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 c68a476f4..6977c7f11 100644 --- a/SubEthaEdit-Mac/Source/PlainTextDocument.h +++ b/SubEthaEdit-Mac/Source/PlainTextDocument.h @@ -38,6 +38,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; diff --git a/SubEthaEdit-Mac/Source/PlainTextDocument.m b/SubEthaEdit-Mac/Source/PlainTextDocument.m index d3b5771af..d018943ae 100644 --- a/SubEthaEdit-Mac/Source/PlainTextDocument.m +++ b/SubEthaEdit-Mac/Source/PlainTextDocument.m @@ -97,6 +97,8 @@ @"PlainTextDocumentDidChangeTextStorageNotification"; NSString * const PlainTextDocumentDefaultParagraphStyleDidChangeNotification = @"PlainTextDocumentDefaultParagraphStyleDidChangeNotification"; +NSString * const PlainTextDocumentWorkspaceDidChangeNotification = + @"PlainTextDocumentWorkspaceDidChangeNotification"; NSString * const PlainTextDocumentDidSaveNotification = @"PlainTextDocumentDidSaveNotification"; NSString * const PlainTextDocumentDidSaveShouldReloadWebPreviewNotification = @@ -104,6 +106,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 @@ -307,6 +311,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]; } @@ -774,6 +780,9 @@ - (void)dealloc { } [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [self removeObserver:self forKeyPath:@"workspace" context:PlainTextDocumentWorkspaceObservationContext]; + if (I_flags.isAnnounced) { [[TCMMMPresenceManager sharedInstance] concealSession:[self session]]; } @@ -883,6 +892,14 @@ - (void)presentScheduledAlertForWindow:(NSWindow *)window { action(window); } +-(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/PlainTextWindowController.m b/SubEthaEdit-Mac/Source/PlainTextWindowController.m index d51a633db..f083602bb 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" @@ -409,7 +410,11 @@ - (IBAction)showWorkspace:(id)sender { [documentController openWorkspace:self.plainTextDocument.workspace display:YES - withCompletionHandler:nil]; + withCompletionHandler:^(NSDocument *document, BOOL documentWasAlreadyOpen, NSError *error) { + if ([document isKindOfClass:[SEEWorkspaceDocument class]]) { + [(SEEWorkspaceDocument *)document selectFileWithURL:self.plainTextDocument.fileURL]; + } + }]; } diff --git a/SubEthaEdit-Mac/Source/SEEFSTree.m b/SubEthaEdit-Mac/Source/SEEFSTree.m index 6d10ae61d..04b6986b1 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTree.m +++ b/SubEthaEdit-Mac/Source/SEEFSTree.m @@ -35,7 +35,7 @@ - (instancetype)initWithURL:(NSURL *)anURL; { self = [super init]; if (self) { - _root = [[SEEFSTreeNode alloc] initWithURL:anURL]; + _root = [[SEEFSTreeNode alloc] initWithURL:anURL parent:nil]; NSArray *pathsToWatch = @[anURL.path]; diff --git a/SubEthaEdit-Mac/Source/SEEFSTreeNode.h b/SubEthaEdit-Mac/Source/SEEFSTreeNode.h index 6ccdcbfde..5d1cfff35 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTreeNode.h +++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.h @@ -11,7 +11,7 @@ @interface SEEFSTreeNode : NSObject -- (instancetype)initWithURL:(NSURL *)anURL; +- (instancetype)initWithURL:(NSURL *)anURL parent:(SEEFSTreeNode *)aParent; - (NSString *)name; - (NSImage *)icon; @@ -22,6 +22,7 @@ - (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 index d59f787fc..515fd238a 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTreeNode.m +++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m @@ -22,15 +22,17 @@ @interface SEEFSTreeNode () @implementation SEEFSTreeNode { + __weak SEEFSTreeNode *parent; NSURL *url; NSArray *children; } -- (instancetype)initWithURL:(NSURL *)anURL +- (instancetype)initWithURL:(NSURL *)anURL parent:(SEEFSTreeNode *)aParent { self = [super init]; if (self) { url = anURL; + parent = aParent; NSDictionary *values = [url resourceValuesForKeys:@[NSURLIsDirectoryKey] error:nil]; @@ -56,7 +58,7 @@ - (NSArray *)children { error:nil]; NSMutableArray * _children = [NSMutableArray arrayWithCapacity:contents.count]; for (NSURL *url in contents) { - [_children addObject:[[SEEFSTreeNode alloc] initWithURL:url]]; + [_children addObject:[[SEEFSTreeNode alloc] initWithURL:url parent:self]]; } children = [_children sortedArrayUsingComparator:^NSComparisonResult(SEEFSTreeNode *obj1, SEEFSTreeNode *obj2) { NSComparisonResult result = NSOrderedSame; @@ -102,7 +104,7 @@ - (SEEFSTreeNode *)nodeNamed:(NSString *)name onlyIfCached:(BOOL)cached{ } - (SEEFSTreeNode * )nodeForPath:(NSString *)path { - return [self nodeNamed:path onlyIfCached:NO]; + return [self nodeForPath:path onlyIfCached:NO]; } - (SEEFSTreeNode *)nodeForPath:(NSString *)path onlyIfCached:(BOOL)cached { @@ -129,6 +131,19 @@ - (SEEFSTreeNode *)nodeForPath:(NSString *)path onlyIfCached:(BOOL)cached { return nil; } +- (NSIndexPath *)indexPath { + NSIndexPath *indexPath = parent.indexPath; + + if (parent) { + NSUInteger ownIndex = [parent.children indexOfObject:self]; + indexPath = [indexPath indexPathByAddingIndex:ownIndex]; + } else { + indexPath = [NSIndexPath indexPathWithIndex:0]; + } + + return indexPath; +} + - (void)reload { [self willChangeValueForKey:@"children"]; children = nil; diff --git a/SubEthaEdit-Mac/Source/SEEPlainTextEditorTopBarViewController.m b/SubEthaEdit-Mac/Source/SEEPlainTextEditorTopBarViewController.m index 87c63c150..a14720015 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; } @@ -156,16 +163,22 @@ - (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; + if (hasWorkspace) { + xPosition += 19.; + } // if there are no symbols hide the symbols popup [symbolPopUpButton setHidden:!hasSymbols]; @@ -234,6 +247,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/SEEWorkspaceController.m b/SubEthaEdit-Mac/Source/SEEWorkspaceController.m index 4b5e07610..6e5a938fd 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceController.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceController.m @@ -52,6 +52,12 @@ -(SEEWorkspace *)workspaceForURL:(NSURL *)url createIfNeeded:(BOOL)create { 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; } diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h index d8a5a9807..e142c99b2 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.h @@ -12,6 +12,9 @@ @interface SEEWorkspaceDocument : NSDocument + +- (void)selectFileWithURL:(NSURL *)url; + @end diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m index 147995dac..d24c17913 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceDocument.m @@ -37,6 +37,10 @@ - (void)windowControllerDidLoadNib:(NSWindowController *)aController { [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. diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h index 201cc2a52..3c6e32b1b 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.h @@ -17,6 +17,9 @@ - (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 index 4c462f7f8..0c29f3bc5 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m @@ -38,6 +38,11 @@ - (void)viewDidLoad { } +-(void)selectFileWithURL:(NSURL *)url { + SEEFSTreeNode *node = [tree.root nodeForPath:url.filePathURL.path]; + self.treeController.selectionIndexPath = node.indexPath; +} + - (IBAction)doubleClick:(NSOutlineView *)sender { NSUInteger clickedRow = sender.clickedRow; id item = [sender itemAtRow:clickedRow]; From d33d34b33567c2ea51f65a97567db0b864c85e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Fri, 30 Aug 2019 17:38:54 +0200 Subject: [PATCH 08/14] WIP --- SubEthaEdit-Mac/Source/PlainTextEditor.m | 3 ++- SubEthaEdit-Mac/Source/SEEWorkspace.h | 4 +--- SubEthaEdit-Mac/Source/SEEWorkspace.m | 4 +++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/SubEthaEdit-Mac/Source/PlainTextEditor.m b/SubEthaEdit-Mac/Source/PlainTextEditor.m index 6c6205ec7..14e373d4a 100644 --- a/SubEthaEdit-Mac/Source/PlainTextEditor.m +++ b/SubEthaEdit-Mac/Source/PlainTextEditor.m @@ -2602,7 +2602,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/SEEWorkspace.h b/SubEthaEdit-Mac/Source/SEEWorkspace.h index e3fd91046..2f0d08d69 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspace.h +++ b/SubEthaEdit-Mac/Source/SEEWorkspace.h @@ -20,9 +20,7 @@ @end -@interface SEEWorkspace : NSObject { - NSMutableArray *> *_documents; -} +@interface SEEWorkspace : NSObject @property (nonatomic, readonly) NSURL *baseURL; @property (nonatomic, readonly) NSArray *>*documents; diff --git a/SubEthaEdit-Mac/Source/SEEWorkspace.m b/SubEthaEdit-Mac/Source/SEEWorkspace.m index 7195c2f1f..6ffd93046 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspace.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspace.m @@ -8,7 +8,9 @@ #import "SEEWorkspace.h" -@implementation SEEWorkspace +@implementation SEEWorkspace { + NSMutableArray *> *_documents; +} - (instancetype)initWithBaseURL:(NSURL *)url { From 5e79bd488720328e512beaebc05e82930d2907af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Sun, 6 Oct 2019 16:22:46 +0200 Subject: [PATCH 09/14] wip --- SubEthaEdit-Mac/Source/SEEFSTree.m | 3 +- SubEthaEdit-Mac/Source/SEEFSTreeNode.h | 5 +- SubEthaEdit-Mac/Source/SEEFSTreeNode.m | 56 +++++++++++++------ .../SEEPlainTextEditorTopBarViewController.m | 5 ++ .../SEEWorkspaceFileTreeViewController.m | 15 ++++- .../SEEWorkspaceFileTreeViewController.xib | 27 +++++---- .../Source/SEEWorkspaceTreeDelegate.m | 33 +++++++++-- 7 files changed, 108 insertions(+), 36 deletions(-) diff --git a/SubEthaEdit-Mac/Source/SEEFSTree.m b/SubEthaEdit-Mac/Source/SEEFSTree.m index 04b6986b1..f63f5102b 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTree.m +++ b/SubEthaEdit-Mac/Source/SEEFSTree.m @@ -69,7 +69,8 @@ -(void)dealloc { } -(void)pathDidChange:(NSString *)path withFlags:(FSEventStreamEventFlags)flags { - [[self.root nodeForPath:path onlyIfCached:YES] reload]; + BOOL scanSubdirs = flags & kFSEventStreamEventFlagMustScanSubDirs; + [[self.root nodeForPath:path onlyIfCached:YES] reloadIncludingChildren:scanSubdirs]; } diff --git a/SubEthaEdit-Mac/Source/SEEFSTreeNode.h b/SubEthaEdit-Mac/Source/SEEFSTreeNode.h index 5d1cfff35..e3f7d5d2c 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTreeNode.h +++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.h @@ -13,12 +13,15 @@ @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; -- (void)reload; +- (BOOL)isRoot; +- (void)reloadIncludingChildren:(BOOL)includeChildren; - (SEEFSTreeNode * )nodeForPath:(NSString *)path; - (SEEFSTreeNode * )nodeForPath:(NSString *)path onlyIfCached:(BOOL)cached; diff --git a/SubEthaEdit-Mac/Source/SEEFSTreeNode.m b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m index 515fd238a..fd60b9cc6 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTreeNode.m +++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m @@ -18,8 +18,11 @@ @interface FSEvent : NSObject @interface SEEFSTreeNode () @property (nonatomic) BOOL isFolder; +@property (nonatomic) BOOL isHidden; + @end +static NSArray *resourceValueKeys; @implementation SEEFSTreeNode { __weak SEEFSTreeNode *parent; @@ -27,6 +30,13 @@ @implementation SEEFSTreeNode { NSArray *children; } ++(void)initialize { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + resourceValueKeys = @[NSURLIsDirectoryKey, NSURLIsHiddenKey]; + }); +} + - (instancetype)initWithURL:(NSURL *)anURL parent:(SEEFSTreeNode *)aParent { self = [super init]; @@ -34,18 +44,17 @@ - (instancetype)initWithURL:(NSURL *)anURL parent:(SEEFSTreeNode *)aParent url = anURL; parent = aParent; - NSDictionary *values = [url resourceValuesForKeys:@[NSURLIsDirectoryKey] + 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; } @@ -53,25 +62,26 @@ - (NSString *)name { - (NSArray *)children { if(!children) { NSArray *contents = [NSFileManager.defaultManager contentsOfDirectoryAtURL:url - includingPropertiesForKeys:@[NSURLIsDirectoryKey] + includingPropertiesForKeys:resourceValueKeys options:0 error:nil]; NSMutableArray * _children = [NSMutableArray arrayWithCapacity:contents.count]; for (NSURL *url in contents) { - [_children addObject:[[SEEFSTreeNode alloc] initWithURL:url parent:self]]; + SEEFSTreeNode *node = [[SEEFSTreeNode alloc] initWithURL:url parent:self]; + node.includeHidden = self.includeHidden; + [_children addObject:node]; } - children = [_children sortedArrayUsingComparator:^NSComparisonResult(SEEFSTreeNode *obj1, SEEFSTreeNode *obj2) { - NSComparisonResult result = NSOrderedSame; - if (obj1.isLeaf && !obj2.isLeaf) { - result = NSOrderedDescending; - } else if(!obj1.isLeaf && obj2.isLeaf) { - result = NSOrderedAscending; - } else { - result = [obj1.name compare:obj2.name]; + + [_children filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SEEFSTreeNode * evaluatedObject, NSDictionary * bindings) { + if(evaluatedObject.isHidden && !self.includeHidden) { + return NO; } - - return result; - }]; + return YES; + }]]; + + children = _children; + + } return children; @@ -85,6 +95,10 @@ - (BOOL)isLeaf { return !_isFolder; } +-(BOOL)isRoot { + return parent == nil; +} + -(NSURL *)url { return url; } @@ -144,7 +158,15 @@ - (NSIndexPath *)indexPath { return indexPath; } -- (void)reload { +-(void)setIncludeHidden:(BOOL)includeHidden { + [self willChangeValueForKey:@"includeHidden"]; + _includeHidden = includeHidden; + [self reloadIncludingChildren:YES]; + [self didChangeValueForKey:@"includeHidden"]; + +} + +- (void)reloadIncludingChildren:(BOOL)includeChildren { [self willChangeValueForKey:@"children"]; children = nil; [self didChangeValueForKey:@"children"]; diff --git a/SubEthaEdit-Mac/Source/SEEPlainTextEditorTopBarViewController.m b/SubEthaEdit-Mac/Source/SEEPlainTextEditorTopBarViewController.m index bdd18e265..10f10ab33 100644 --- a/SubEthaEdit-Mac/Source/SEEPlainTextEditorTopBarViewController.m +++ b/SubEthaEdit-Mac/Source/SEEPlainTextEditorTopBarViewController.m @@ -174,6 +174,11 @@ - (void)adjustLayout { } self.workspaceButton.hidden = !hasWorkspace; + + NSRect workspaceButtonFrame = self.workspaceButton.frame; + workspaceButtonFrame.origin.x = xPosition; + self.workspaceButton.frame = workspaceButtonFrame; + if (hasWorkspace) { xPosition += 19.; } diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m index 0c29f3bc5..4d74e7332 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m @@ -11,12 +11,16 @@ #import "SEEWorkspace.h" #import "SEEWorkspaceTreeDelegate.h" #import "SEEFSTree.h" +#import "PopUpButton.h" @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{ @@ -33,8 +37,13 @@ - (instancetype)initWithWorkspace:(SEEWorkspace *)workspace { - (void)viewDidLoad { [super viewDidLoad]; + tree = [[SEEFSTree alloc] initWithURL:self.workspace.baseURL]; - [self.treeController setContent:tree.root]; + [self.treeController setContent:tree.root.children]; + + self.treeController.sortDescriptors = @[ + [NSSortDescriptor sortDescriptorWithKey:@"isLeaf" ascending:YES], + [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)]]; } @@ -66,4 +75,8 @@ - (IBAction)doubleClick:(NSOutlineView *)sender { }]; } +-(IBAction)toggleShowHidden:(id)sender { + tree.root.includeHidden = !tree.root.includeHidden; +} + @end diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib index e4ac7160f..e40ecb042 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib @@ -16,18 +16,17 @@ - + - - - - - + + + + - - + + @@ -93,8 +92,8 @@ - - + + @@ -112,7 +111,13 @@ - + + + + + + + diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m b/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m index 7132c80cb..668acb4dc 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m @@ -13,12 +13,35 @@ @implementation SEEWorkspaceTreeDelegate -(NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(NSTreeNode *)node { SEEFSTreeNode *item = node.representedObject; - NSString *identifier = @"DataCell"; - NSTableCellView *view = [outlineView makeViewWithIdentifier:identifier - owner:nil]; - view.textField.stringValue = item.name; - view.imageView.image = item.icon; + NSTableCellView *view; + NSString *identifier; + + if (!item.isRoot) { + identifier = @"DataCell"; + view = [outlineView makeViewWithIdentifier:identifier + owner:nil]; + view.textField.stringValue = item.name; + view.imageView.image = item.icon; + } else { + identifier = @"HeaderCell"; + view = [outlineView makeViewWithIdentifier:identifier + owner:nil]; + view.textField.stringValue = item.name.uppercaseString; + view.imageView.image = nil; + } + 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 From 2e2a23bc9ef06fb37566eaaf6b1a6d65663741c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Wed, 23 Oct 2019 09:01:13 +0200 Subject: [PATCH 10/14] wip --- SubEthaEdit-Mac/Source/SEEFSTree.m | 2 +- SubEthaEdit-Mac/Source/SEEFSTreeNode.h | 2 +- SubEthaEdit-Mac/Source/SEEFSTreeNode.m | 87 +++++++++++++------ .../SEEWorkspaceFileTreeViewController.m | 22 ++++- .../SEEWorkspaceFileTreeViewController.xib | 34 ++++---- .../Source/SEEWorkspaceTreeDelegate.m | 18 ++-- 6 files changed, 106 insertions(+), 59 deletions(-) diff --git a/SubEthaEdit-Mac/Source/SEEFSTree.m b/SubEthaEdit-Mac/Source/SEEFSTree.m index f63f5102b..1919f063c 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTree.m +++ b/SubEthaEdit-Mac/Source/SEEFSTree.m @@ -70,7 +70,7 @@ -(void)dealloc { -(void)pathDidChange:(NSString *)path withFlags:(FSEventStreamEventFlags)flags { BOOL scanSubdirs = flags & kFSEventStreamEventFlagMustScanSubDirs; - [[self.root nodeForPath:path onlyIfCached:YES] reloadIncludingChildren:scanSubdirs]; + [[self.root nodeForPath:path onlyIfCached:YES] reloadChildrenRecursive:scanSubdirs]; } diff --git a/SubEthaEdit-Mac/Source/SEEFSTreeNode.h b/SubEthaEdit-Mac/Source/SEEFSTreeNode.h index e3f7d5d2c..d23028a9b 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTreeNode.h +++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.h @@ -21,7 +21,7 @@ - (NSURL *)url; - (BOOL)isLeaf; - (BOOL)isRoot; -- (void)reloadIncludingChildren:(BOOL)includeChildren; +- (void)reloadChildrenRecursive:(BOOL)includeChildren; - (SEEFSTreeNode * )nodeForPath:(NSString *)path; - (SEEFSTreeNode * )nodeForPath:(NSString *)path onlyIfCached:(BOOL)cached; diff --git a/SubEthaEdit-Mac/Source/SEEFSTreeNode.m b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m index fd60b9cc6..fc080588b 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTreeNode.m +++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m @@ -27,7 +27,8 @@ @interface SEEFSTreeNode () @implementation SEEFSTreeNode { __weak SEEFSTreeNode *parent; NSURL *url; - NSArray *children; + NSMutableArray *unfilteredChildren; + NSMutableArray *children; } +(void)initialize { @@ -61,27 +62,8 @@ - (NSString *)name { - (NSArray *)children { if(!children) { - NSArray *contents = [NSFileManager.defaultManager contentsOfDirectoryAtURL:url - includingPropertiesForKeys:resourceValueKeys - options:0 - error:nil]; - NSMutableArray * _children = [NSMutableArray arrayWithCapacity:contents.count]; - for (NSURL *url in contents) { - SEEFSTreeNode *node = [[SEEFSTreeNode alloc] initWithURL:url parent:self]; - node.includeHidden = self.includeHidden; - [_children addObject:node]; - } - - [_children filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SEEFSTreeNode * evaluatedObject, NSDictionary * bindings) { - if(evaluatedObject.isHidden && !self.includeHidden) { - return NO; - } - return YES; - }]]; - - children = _children; - - + children = [NSMutableArray new]; + [self reloadChildrenRecursive:NO]; } return children; @@ -103,6 +85,23 @@ -(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]; } @@ -161,14 +160,50 @@ - (NSIndexPath *)indexPath { -(void)setIncludeHidden:(BOOL)includeHidden { [self willChangeValueForKey:@"includeHidden"]; _includeHidden = includeHidden; - [self reloadIncludingChildren:YES]; + [self reloadChildrenRecursive:YES]; [self didChangeValueForKey:@"includeHidden"]; - } -- (void)reloadIncludingChildren:(BOOL)includeChildren { +- (void)reloadChildrenRecursive:(BOOL)recursive { [self willChangeValueForKey:@"children"]; - children = nil; + + + 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"]; } diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m index 4d74e7332..4377161e9 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m @@ -13,6 +13,8 @@ #import "SEEFSTree.h" #import "PopUpButton.h" +static void *SEERootChildrenChangedContext = (void *)&SEERootChildrenChangedContext; + @interface SEEWorkspaceFileTreeViewController () @property (nonatomic, strong) IBOutlet NSTreeController *treeController; @@ -38,13 +40,18 @@ - (instancetype)initWithWorkspace:(SEEWorkspace *)workspace { - (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 { @@ -79,4 +86,17 @@ -(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 index e40ecb042..f8697097e 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.xib @@ -1,8 +1,8 @@ - + - + @@ -19,22 +19,22 @@ - + - + - - - - - + + + + + - + - + @@ -46,11 +46,11 @@ - + - + @@ -64,7 +64,7 @@ - + @@ -73,7 +73,7 @@ - + @@ -92,8 +92,8 @@ - - + + diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m b/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m index 668acb4dc..6ab4ad38e 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceTreeDelegate.m @@ -16,19 +16,11 @@ -(NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableC NSTableCellView *view; NSString *identifier; - if (!item.isRoot) { - identifier = @"DataCell"; - view = [outlineView makeViewWithIdentifier:identifier - owner:nil]; - view.textField.stringValue = item.name; - view.imageView.image = item.icon; - } else { - identifier = @"HeaderCell"; - view = [outlineView makeViewWithIdentifier:identifier - owner:nil]; - view.textField.stringValue = item.name.uppercaseString; - view.imageView.image = nil; - } + identifier = @"DataCell"; + view = [outlineView makeViewWithIdentifier:identifier owner:nil]; + view.textField.stringValue = item.name; + view.imageView.image = item.icon; + return view; } From c6920b2e072e39a6f75010877e3520e940e7d54b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Tue, 12 Nov 2019 17:00:52 +0100 Subject: [PATCH 11/14] select the correct item when using the project icon --- SubEthaEdit-Mac/Source/SEEFSTreeNode.m | 13 +++++++---- .../SEEWorkspaceFileTreeViewController.m | 22 +++++++++++++++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/SubEthaEdit-Mac/Source/SEEFSTreeNode.m b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m index fc080588b..aa8fc915f 100644 --- a/SubEthaEdit-Mac/Source/SEEFSTreeNode.m +++ b/SubEthaEdit-Mac/Source/SEEFSTreeNode.m @@ -145,13 +145,18 @@ - (SEEFSTreeNode *)nodeForPath:(NSString *)path onlyIfCached:(BOOL)cached { } - (NSIndexPath *)indexPath { - NSIndexPath *indexPath = parent.indexPath; + NSIndexPath *indexPath = nil; + if (parent) { NSUInteger ownIndex = [parent.children indexOfObject:self]; - indexPath = [indexPath indexPathByAddingIndex:ownIndex]; - } else { - indexPath = [NSIndexPath indexPathWithIndex:0]; + NSIndexPath *parentIndexPath = parent.indexPath; + + if(parentIndexPath) { + indexPath = [parentIndexPath indexPathByAddingIndex:ownIndex]; + } else { + indexPath = [NSIndexPath indexPathWithIndex:ownIndex]; + } } return indexPath; diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m index 4377161e9..c34f42e47 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceFileTreeViewController.m @@ -55,8 +55,26 @@ - (void)viewDidLoad { } -(void)selectFileWithURL:(NSURL *)url { - SEEFSTreeNode *node = [tree.root nodeForPath:url.filePathURL.path]; - self.treeController.selectionIndexPath = node.indexPath; + 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 { From 5dc4ac16449dcd7d239b2309d308abd9dfec597c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Tue, 12 Nov 2019 17:37:57 +0100 Subject: [PATCH 12/14] Initial version of the project context documentation --- Documentation/ProjectContext.md | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Documentation/ProjectContext.md diff --git a/Documentation/ProjectContext.md b/Documentation/ProjectContext.md new file mode 100644 index 000000000..660ce7c38 --- /dev/null +++ b/Documentation/ProjectContext.md @@ -0,0 +1,67 @@ +# 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. + +## 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 | + | | | | + +-----------------------------+ +-----------------------------+ + ``` + + From 79b49fd224d2f5072fc509a13b2a0a98ad9a0872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Tue, 12 Nov 2019 18:33:54 +0100 Subject: [PATCH 13/14] Improve the SEEWorkspaceController --- .../Source/SEEWorkspaceController.m | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/SubEthaEdit-Mac/Source/SEEWorkspaceController.m b/SubEthaEdit-Mac/Source/SEEWorkspaceController.m index 6e5a938fd..0647580eb 100644 --- a/SubEthaEdit-Mac/Source/SEEWorkspaceController.m +++ b/SubEthaEdit-Mac/Source/SEEWorkspaceController.m @@ -9,6 +9,7 @@ #import "SEEWorkspaceController.h" #import "SEEWorkspaceDocument.h" #import "SEEWorkspace.h" +#import "PlainTextDocument.h" @interface SEEWorkspaceController () @@ -16,21 +17,24 @@ @interface SEEWorkspaceController () @implementation SEEWorkspaceController { @private - NSMutableArray *workspaces; + NSMutableSet *workspaces; } -// TODO: prune empty workspaces - - (instancetype)initWithDocumentController:(NSDocumentController *)controller; { self = [super init]; if (self) { _documentController = controller; - workspaces = [NSMutableArray new]; + workspaces = [NSMutableSet new]; } return self; } +- (void)dealloc +{ + [NSNotificationCenter.defaultCenter removeObserver:self]; +} + -(SEEWorkspace *)workspaceForDocument:(NSDocument *)document { for (SEEWorkspace *ws in workspaces) { if ([ws containsDocument:document]) { @@ -45,10 +49,19 @@ -(SEEWorkspace *)workspaceForURL:(NSURL *)url { } -(SEEWorkspace *)workspaceForURL:(NSURL *)url createIfNeeded:(BOOL)create { - SEEWorkspace *workspace = [workspaces SEE_firstObjectPassingTest:^BOOL(SEEWorkspace *ws) { + // 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]; @@ -65,17 +78,48 @@ -(SEEWorkspace *)workspaceForURL:(NSURL *)url createIfNeeded:(BOOL)create { -(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)]) { - [[self workspaceForDocument:document] removeDocument:(NSDocument *)document]; + 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; From aeebd8ee092a2679bbbedd774c22cf0afa2f731e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bartelme=C3=9F?= Date: Tue, 12 Nov 2019 18:43:18 +0100 Subject: [PATCH 14/14] Clarify impact on collaboration --- Documentation/ProjectContext.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/ProjectContext.md b/Documentation/ProjectContext.md index 660ce7c38..3fbb9450b 100644 --- a/Documentation/ProjectContext.md +++ b/Documentation/ProjectContext.md @@ -45,6 +45,10 @@ If a new file is created or opened and the path of the file is contained in a wo 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.