Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions Documentation/ProjectContext.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Project Context

This document outlines the desired behaviour of the "Project Context" feature.

## Rationale

Currently SubEthaEdit operates on a per file basis and thus does not provide any features on a project level like:

- powerful project wide search and replace
- constrain autocomplete for files in the same context
- simple access and recognition of build and linting scripts for the project opened
- project wide navigation (e.g. opening up of imported files, etc)
- SCM integration
- ...

Therefore a concept and an implementation of a Project Context is needed.

## Definition

- Workspace: The workspace is the root entity for all project related information like the project base path. Documents can belong to at most one workspace.

## Behaviour

### Workspaces

Workspaces are created if:

- The see tool is used to open a folder `see ./myfolder`
- If a folder is opened via one of the system provided functionalities (dragged on SEE icon, opened via Fild > Open, ...)

If a folder within an already open workspace is opened, no new workspace will be created. Instead the workspace window of the existing workspace will be displayed and the folder will be highlighted

Workspaces are dismissed if:
- All documents of the workspace are closed AND
- All Workspace windows are closed


### Documents

Every document can belong to at least one workspace. If it does so, in the upper right corner of the document window, a little "Project" icon is displayed. Clicking on this icon will reveal the Workspace Window and select the corresponding file.

If a new file is created or opened and the path of the file is contained in a workspace, it will be assigned to that workspace. If there a multiple workspaces open, the topmost one will be used. For example if the path of the file is `/foo/bar/foo.txt` and a workspace exists with the base path `/foo` as well as one with the base path `/foo/bar` it will be assigned to the latter one.

### Workspace Window

The workspace displays the tree-like file structure of a workspace. Double-Clicking a file will open the corresponding document. There can only be one workspace window per workspace.

### Collaboration

At the moment, workspaces have no effect on how collaboration works.

## Implementation
Workspaces are represented by an `SEEWorkspace` object and managed by the `SEEWorkspaceController`. The diagram shows the relations between the Document & Workspace related objects.

```
+-----------------------------+ +-----------------------------+
| | | |
| SEEWorkspaceController |<----1--+ SEEDocumentController |
| | | |
+-------------+---------------+ +--------------+--------------+
* *
| |
v v
+-----------------------------+ +-----------------------------+
| | | |
| SEEWorkspace +-*----->| NSDocument |
| | | |
+-----------------------------+ +-----------------------------+
```


7 changes: 5 additions & 2 deletions SubEthaEdit-Mac/Base.lproj/MainMenu.xib
Original file line number Diff line number Diff line change
Expand Up @@ -1013,9 +1013,9 @@ GQ
<action selector="showDocumentListWindow:" target="-1" id="KbU-OD-k6L"/>
</connections>
</menuItem>
<menuItem title="Document Hub" alternate="YES" keyEquivalent="K" id="640">
<menuItem title="Workspace" keyEquivalent="K" id="b0T-3k-3gD">
<connections>
<action selector="showDocumentListWindow:" target="-1" id="lmF-HU-bMB"/>
<action selector="showWorkspace:" target="-1" id="cj0-yL-zgO"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="k0I-fL-5QT">
Expand Down Expand Up @@ -1088,5 +1088,8 @@ GQ
<customObject id="354" userLabel="Font Manager" customClass="NSFontManager"/>
<customObject id="463" userLabel="FindReplaceController" customClass="FindReplaceController"/>
<customObject id="484" userLabel="AboutPanelController" customClass="AboutPanelController"/>
<menuItem title="Item" id="hoY-cY-6Kj">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</objects>
</document>
2 changes: 1 addition & 1 deletion SubEthaEdit-Mac/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<key>LSTypeIsPackage</key>
<false/>
<key>NSDocumentClass</key>
<string>PlainTextDocument</string>
<string>SEEWorkspaceDocument</string>
</dict>
<dict>
<key>CFBundleTypeName</key>
Expand Down
13 changes: 13 additions & 0 deletions SubEthaEdit-Mac/SEEPlainTextEditorTopBarViewController.xib
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<outlet property="symbolPopUpButton" destination="57S-Gg-lfW" id="Edc-kB-OMb"/>
<outlet property="view" destination="ROy-l3-d1g" id="hu4-z4-zY2"/>
<outlet property="waitPipeIconImageView" destination="YRN-nX-AOm" id="f39-fg-mTK"/>
<outlet property="workspaceButton" destination="LA7-TJ-3JV" id="lK4-cD-QSN"/>
<outlet property="writtenByTextField" destination="W31-AX-oex" id="X2i-0o-Wk0"/>
</connections>
</customObject>
Expand Down Expand Up @@ -81,10 +82,22 @@
<action selector="toggleDocumentInfoLabel:" target="-2" id="wmk-us-0AU"/>
</connections>
</textField>
<button id="LA7-TJ-3JV">
<rect key="frame" x="0.0" y="-1" width="19" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSFolder" imagePosition="only" alignment="center" borderStyle="border" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XJc-wJ-CbM">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="workspaceButtonAction:" target="-2" id="0bN-uG-lEc"/>
</connections>
</button>
</subviews>
</customView>
</objects>
<resources>
<image name="NSFolder" width="32" height="32"/>
<image name="ToolbarSplitTwoPanes" width="13" height="13"/>
<image name="WaitPipe" width="13" height="12"/>
</resources>
Expand Down
5 changes: 3 additions & 2 deletions SubEthaEdit-Mac/Source/PlainTextDocument.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import "FoldableTextStorage.h"
#import "FullTextStorage.h"
#import "SEEDocumentController.h"
#import "SEEWorkspace.h"
#import "SEEAlertRecipe.h"

enum {
Expand All @@ -38,6 +39,7 @@ extern NSString * const PlainTextDocumentUserDidChangeSelectionNotification;
extern NSString * const PlainTextDocumentDefaultParagraphStyleDidChangeNotification;
extern NSString * const PlainTextDocumentDidChangeDisplayNameNotification;
extern NSString * const PlainTextDocumentDidChangeDocumentModeNotification;
extern NSString * const PlainTextDocumentWorkspaceDidChangeNotification;

extern NSString * const WrittenByUserIDAttributeName;
extern NSString * const ChangedByUserIDAttributeName;
Expand All @@ -46,7 +48,7 @@ extern NSString * const PlainTextDocumentDidSaveNotification;
extern NSString * const PlainTextDocumentDidSaveShouldReloadWebPreviewNotification;


@interface PlainTextDocument : NSDocument <SEEDocument, NSTextViewDelegate, NSTextStorageDelegate, NSOpenSavePanelDelegate, NSSharingServicePickerDelegate, NSSharingServiceDelegate>
@interface PlainTextDocument : NSDocument <SEEDocument, SEEWorkspaceDocument, NSTextViewDelegate, NSTextStorageDelegate, NSOpenSavePanelDelegate, NSSharingServicePickerDelegate, NSSharingServiceDelegate>
{
TCMMMSession *I_session;
struct {
Expand Down Expand Up @@ -151,7 +153,6 @@ extern NSString * const PlainTextDocumentDidSaveShouldReloadWebPreviewNotificati
@property (readwrite, strong) IBOutlet NSWindow *O_exportSheet;
@property (readwrite, strong) IBOutlet NSObjectController *O_exportSheetController;
@property (nonatomic, strong) NSMutableArray *persistentDocumentScopedBookmarkURLs;

@property (nonatomic, strong) SEEDocumentCreationFlags *attachedCreationFlags;

/*!
Expand Down
19 changes: 19 additions & 0 deletions SubEthaEdit-Mac/Source/PlainTextDocument.m
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,17 @@
@"PlainTextDocumentDidChangeTextStorageNotification";
NSString * const PlainTextDocumentDefaultParagraphStyleDidChangeNotification =
@"PlainTextDocumentDefaultParagraphStyleDidChangeNotification";
NSString * const PlainTextDocumentWorkspaceDidChangeNotification =
@"PlainTextDocumentWorkspaceDidChangeNotification";
NSString * const PlainTextDocumentDidSaveNotification =
@"PlainTextDocumentDidSaveNotification";
NSString * const PlainTextDocumentDidSaveShouldReloadWebPreviewNotification =
@"PlainTextDocumentDidSaveShouldReloadWebPreviewNotification";
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
Expand Down Expand Up @@ -155,6 +159,8 @@ - (void)TCM_validateLineEndings;


@implementation PlainTextDocument
@synthesize workspace;


+ (void)initialize {
if (self == [PlainTextDocument class]) {
Expand Down Expand Up @@ -316,6 +322,8 @@ - (void)TCM_initHelper {
I_undoManager = [(UndoManager *)[UndoManager alloc] initWithDocument:self];
[[[self session] loggingState] setInitialTextStorageDictionaryRepresentation:[self textStorageDictionaryRepresentation]];
[[[self session] loggingState] handleOperation:[SelectionOperation selectionOperationWithRange:NSMakeRange(0,0) userID:[TCMMMUserManager myUserID]]];

[self addObserver:self forKeyPath:@"workspace" options:0 context:PlainTextDocumentWorkspaceObservationContext];

}

Expand Down Expand Up @@ -778,6 +786,9 @@ - (void)updateTempDisplayName:(NSNotification *)aNotification {

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];

[self removeObserver:self forKeyPath:@"workspace" context:PlainTextDocumentWorkspaceObservationContext];

if (I_flags.isAnnounced) {
[[TCMMMPresenceManager sharedInstance] concealSession:I_session];
}
Expand All @@ -793,6 +804,14 @@ - (void)dealloc {
[[TCMMMPresenceManager sharedInstance] unregisterSession:I_session];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)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 {
Expand Down
3 changes: 2 additions & 1 deletion SubEthaEdit-Mac/Source/PlainTextEditor.m
Original file line number Diff line number Diff line change
Expand Up @@ -2598,7 +2598,8 @@ - (NSArray *)textView:(NSTextView *)textView completions:(NSArray *)words forPar
PlainTextDocument *document = nil;

while ((document = [documents nextObject])){
if (document == myDocument) continue;
if (document == myDocument ||
![document isKindOfClass:[PlainTextDocument class]]) continue;

NSEnumerator *matches = [document matchEnumeratorForAutocompleteString:partialWord];
OGRegularExpressionMatch *match = nil;
Expand Down
18 changes: 16 additions & 2 deletions SubEthaEdit-Mac/Source/PlainTextWindowController.m
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -289,8 +290,9 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
} else {
return NO;
}
}

} else if (selector == @selector(showWorkspace:)) {
return self.plainTextDocument.workspace != nil;
}
return YES;
}

Expand Down Expand Up @@ -379,6 +381,18 @@ - (IBAction)copyDocumentURL:(id)aSender {
[documentURL writeToPasteboard:pboard];
}

- (IBAction)showWorkspace:(id)sender {
SEEDocumentController * documentController = (SEEDocumentController *)[NSDocumentController sharedDocumentController];

[documentController openWorkspace:self.plainTextDocument.workspace
display:YES
withCompletionHandler:^(NSDocument *document, BOOL documentWasAlreadyOpen, NSError *error) {
if ([document isKindOfClass:[SEEWorkspaceDocument class]]) {
[(SEEWorkspaceDocument *)document selectFileWithURL:self.plainTextDocument.fileURL];
}
}];
}


#pragma mark -

Expand Down
5 changes: 5 additions & 0 deletions SubEthaEdit-Mac/Source/SEEDocumentController.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
@class PlainTextWindowController;
@class MAAttachedWindow;
@class PlainTextDocument;
@class SEEWorkspaceController;
@class SEEWorkspace;


extern NSString *const RecentDocumentsDidChangeNotification;
Expand All @@ -34,6 +36,7 @@ extern NSString * const kSEETypeSEEMode;
@property (nonatomic, weak) IBOutlet NSMenu *recentDocumentMenu;
@property (nonatomic, readonly) NSStringEncoding encodingFromLastRunOpenPanel;
@property (nonatomic, readonly, copy) NSString *modeIdentifierFromLastRunOpenPanel;
@property (nonatomic, readonly) SEEWorkspaceController *workspaceController;

+ (SEEDocumentController *)sharedInstance;

Expand All @@ -51,6 +54,8 @@ extern NSString * const kSEETypeSEEMode;
- (IBAction)openNormalDocument:(id)aSender;
- (IBAction)openAlternateDocument:(id)aSender;

-(void)openWorkspace:(SEEWorkspace *)workspace display:(BOOL)displayDocument withCompletionHandler:(void (^)(NSDocument *, BOOL, NSError *))completionHandler;

- (void)addProxyDocumentWithSession:(TCMMMSession *)aSession;

- (NSArray *)documentsInMode:(DocumentMode *)aDocumentMode;
Expand Down
Loading