Compare commits

...

23 Commits

Author SHA1 Message Date
Bjorn Winckler 204e14b994 Snapshot 45 2009-04-13 19:28:18 +02:00
Bjorn Winckler 2e8bea32db Fix compilation problems on Tiger
The NSRunLoopCommonModes constant is not defined in the Tiger API so
explicitly enumerate the necessary modes instead.
2009-04-13 16:04:49 +02:00
Bjorn Winckler e70fb38670 Add "show hidden files" checkbox to save dialog 2009-04-13 13:48:54 +02:00
Bjorn Winckler 3d56969f47 Add function to print message queue 2009-04-10 19:05:49 +02:00
Bjorn Winckler 294d03baf6 Beware exceptions when processing input
Added comment on the dangers of exceptions being raised when processing
input in the frontend.  Shuffled the exception handling around in the
vim controller.
2009-04-10 18:33:02 +02:00
Bjorn Winckler ac96dc67ba Fix Quickstart regression
Output from a Vim process may reach the frontend even if the process is
cached.  Thus when processing input queues in the frontend, also check
if the input came from a cached controller else it could be silently
ignored.
2009-04-10 18:11:18 +02:00
Bjorn Winckler 0413dcf3f1 Make dialog sheet messages unsafe 2009-04-08 20:41:32 +02:00
Bjorn Winckler 40263195cb Avoid enumerating vim controllers
Don't enumerate vim controllers when processing input since it may
potentially be a huge operation.  If the vim controller array were to be
modified during input processing (should never happen) MacVim would crash.
2009-04-08 20:38:28 +02:00
Bjorn Winckler dd76f85f0b Deprecate performSelectorOnMainThread calls
Use performSelector:withObject:after:delay instead since it
automatically only triggers in default mode.
2009-04-08 19:36:00 +02:00
Bjorn Winckler e93e9c4201 Do not modify frontend state in DO calls
In particular, delay all state changes that used to be made inside
connectBackend:pid such add adding vim controllers to the vimControllers
array.
2009-04-05 21:43:00 +02:00
Bjorn Winckler a4a14b39ac Cleanup 2009-04-05 20:21:36 +02:00
Bjorn Winckler 49eeb133ac Add comment on DO timeouts 2009-04-05 20:00:02 +02:00
Bjorn Winckler 28de969ae3 Deprecate inProcessCommandQueue related code
We are guarding against re-entrant calls in the app controller now so
the inProcessCommandQueue related code is now obsolete.
2009-04-05 19:56:21 +02:00
Bjorn Winckler 6961a51e9a Cleanup 2009-04-05 19:21:16 +02:00
Bjorn Winckler f679af784b Update README 2009-04-05 19:20:42 +02:00
Bjorn Winckler fd8e8e0b4f Clear delayed vim controller calls on cleanup 2009-04-05 19:02:08 +02:00
Bjorn Winckler cd5ca1918d Add support for dialogs 2009-04-05 18:57:56 +02:00
Bjorn Winckler 27d4ee55ae Add support for live resizing 2009-04-05 18:22:59 +02:00
Bjorn Winckler 23e38c4298 Guard against re-entrant calls to process input 2009-04-05 18:12:23 +02:00
Bjorn Winckler 0486075221 Vim talks only to app controller
- dialog support not implemented yet
- live resizing not supported
2009-04-05 17:32:37 +02:00
Kazuki Sakamoto 7a010c4a46 Avoid forever bouncing dock icon
This problem was caused by calling the Carbon function KeyScript() from
the Vim process.
2009-04-03 21:54:15 +09:00
Bjorn Winckler 9aeade7147 Use "guisp" color for underline in ATSUI 2009-04-01 20:12:26 +02:00
Bjorn Winckler 5663536b1f Do not hide toolbar unless requested
Commit 2d497eabe995ed7f667d67166b396dff6389d12f introduced a regression
where the toolbar automatically hid upon startup regardless whether 'go'
included the "T" flag or not.
2009-03-30 19:41:50 +02:00
18 changed files with 582 additions and 467 deletions
+1 -1
View File
@@ -1153,7 +1153,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>44</string>
<string>45</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
+2
View File
@@ -29,6 +29,8 @@
int preloadPid;
BOOL shouldActivateWhenNextWindowOpens;
int numChildProcesses;
NSMutableDictionary *inputQueues;
int processingFlag;
#if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
FSEventStreamRef fsEventStream;
+175 -67
View File
@@ -125,6 +125,8 @@ typedef struct
- (void)loadDefaultFont;
- (int)executeInLoginShell:(NSString *)path arguments:(NSArray *)args;
- (void)reapChildProcesses:(id)sender;
- (void)processInputQueues:(id)sender;
- (void)addVimController:(MMVimController *)vc;
#ifdef MM_ENABLE_PLUGINS
- (void)removePlugInMenu;
@@ -208,6 +210,7 @@ fsEventCallback(ConstFSEventStreamRef streamRef,
cachedVimControllers = [NSMutableArray new];
preloadPid = -1;
pidArguments = [NSMutableDictionary new];
inputQueues = [NSMutableDictionary new];
#ifdef MM_ENABLE_PLUGINS
NSString *plugInTitle = NSLocalizedString(@"Plug-In",
@@ -249,6 +252,7 @@ fsEventCallback(ConstFSEventStreamRef streamRef,
//NSLog(@"MMAppController dealloc");
[connection release]; connection = nil;
[inputQueues release]; inputQueues = nil;
[pidArguments release]; pidArguments = nil;
[vimControllers release]; vimControllers = nil;
[cachedVimControllers release]; cachedVimControllers = nil;
@@ -994,7 +998,7 @@ fsEventCallback(ConstFSEventStreamRef streamRef,
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:YES];
[panel setAccessoryView:openPanelAccessoryView()];
[panel setAccessoryView:showHiddenFilesView()];
int result = [panel runModalForDirectory:dir file:nil types:nil];
if (NSOKButton == result)
@@ -1093,64 +1097,79 @@ fsEventCallback(ConstFSEventStreamRef streamRef,
}
}
- (byref id <MMFrontendProtocol>)
connectBackend:(byref in id <MMBackendProtocol>)backend
pid:(int)pid
- (MMVimController *)keyVimController
{
//NSLog(@"Connect backend (pid=%d)", pid);
NSNumber *pidKey = [NSNumber numberWithInt:pid];
MMVimController *vc = nil;
@try {
[(NSDistantObject*)backend
setProtocolForProxy:@protocol(MMBackendProtocol)];
vc = [[[MMVimController alloc] initWithBackend:backend pid:pid]
autorelease];
if (preloadPid == pid) {
// This backend was preloaded, so add it to the cache and schedule
// another vim process to be preloaded.
preloadPid = -1;
[vc setIsPreloading:YES];
[cachedVimControllers addObject:vc];
[self scheduleVimControllerPreloadAfterDelay:1];
return vc;
NSWindow *keyWindow = [NSApp keyWindow];
if (keyWindow) {
unsigned i, count = [vimControllers count];
for (i = 0; i < count; ++i) {
MMVimController *vc = [vimControllers objectAtIndex:i];
if ([[[vc windowController] window] isEqual:keyWindow])
return vc;
}
[vimControllers addObject:vc];
id args = [pidArguments objectForKey:pidKey];
if (args && [NSNull null] != args)
[vc passArguments:args];
// HACK! MacVim does not get activated if it is launched from the
// terminal, so we forcibly activate here unless it is an untitled
// window opening. Untitled windows are treated differently, else
// MacVim would steal the focus if another app was activated while the
// untitled window was loading.
if (!args || args != [NSNull null])
[self activateWhenNextWindowOpens];
if (args)
[pidArguments removeObjectForKey:pidKey];
return vc;
}
@catch (NSException *e) {
NSLog(@"Exception caught in %s: \"%@\"", _cmd, e);
if (vc)
[vimControllers removeObject:vc];
[pidArguments removeObjectForKey:pidKey];
}
return nil;
}
- (unsigned)connectBackend:(byref in id <MMBackendProtocol>)proxy pid:(int)pid
{
//NSLog(@"[%s] pid=%d", _cmd, pid);
[(NSDistantObject*)proxy setProtocolForProxy:@protocol(MMBackendProtocol)];
// NOTE: Allocate the vim controller now but don't add it to the list of
// controllers since this is a distributed object call and as such can
// arrive at unpredictable times (e.g. while iterating the list of vim
// controllers).
// (What if input arrives before the vim controller is added to the list of
// controllers? This should not be a problem since the input isn't
// processed immediately (see processInput:forIdentifier:).)
MMVimController *vc = [[MMVimController alloc] initWithBackend:proxy
pid:pid];
[self performSelector:@selector(addVimController:)
withObject:vc
afterDelay:0];
[vc release];
return [vc identifier];
}
- (oneway void)processInput:(in bycopy NSArray *)queue
forIdentifier:(unsigned)identifier
{
// NOTE: Input is not handled immediately since this is a distribued object
// call and as such can arrive at unpredictable times. Instead, queue the
// input and process it when the run loop is updated.
if (!(queue && identifier)) {
NSLog(@"[%s] Bad input for identifier=%d", _cmd, identifier);
return;
}
//NSLog(@"[%s] QUEUE for identifier=%d: <<< %@>>>", _cmd, identifier,
// debugStringForMessageQueue(queue));
NSNumber *key = [NSNumber numberWithUnsignedInt:identifier];
NSArray *q = [inputQueues objectForKey:key];
if (q) {
q = [q arrayByAddingObjectsFromArray:queue];
[inputQueues setObject:q forKey:key];
} else {
[inputQueues setObject:queue forKey:key];
}
// NOTE: We must use "event tracking mode" as well as "default mode",
// otherwise the input queue will not be processed e.g. during live
// resizing.
[self performSelector:@selector(processInputQueues:)
withObject:nil
afterDelay:0
inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,
NSEventTrackingRunLoopMode, nil]];
}
- (NSArray *)serverList
{
NSMutableArray *array = [NSMutableArray array];
@@ -1165,21 +1184,6 @@ fsEventCallback(ConstFSEventStreamRef streamRef,
return array;
}
- (MMVimController *)keyVimController
{
NSWindow *keyWindow = [NSApp keyWindow];
if (keyWindow) {
unsigned i, count = [vimControllers count];
for (i = 0; i < count; ++i) {
MMVimController *vc = [vimControllers objectAtIndex:i];
if ([[[vc windowController] window] isEqual:keyWindow])
return vc;
}
}
return nil;
}
@end // MMAppController
@@ -2115,4 +2119,108 @@ fsEventCallback(ConstFSEventStreamRef streamRef,
}
}
- (void)processInputQueues:(id)sender
{
// NOTE: Because we use distributed objects it is quite possible for this
// function to be re-entered. This can cause all sorts of unexpected
// problems so we guard against it here so that the rest of the code does
// not need to worry about it.
// The processing flag is > 0 if this function is already on the call
// stack; < 0 if this function was also re-entered.
if (processingFlag != 0) {
NSLog(@"[%s] BUSY!", _cmd);
processingFlag = -1;
return;
}
// NOTE: Be _very_ careful that no exceptions can be raised between here
// and the point at which 'processingFlag' is reset. Otherwise the above
// test could end up always failing and no input queues would ever be
// processed!
processingFlag = 1;
// NOTE: New input may arrive while we're busy processing; we deal with
// this by putting the current queue aside and creating a new input queue
// for future input.
NSDictionary *queues = inputQueues;
inputQueues = [NSMutableDictionary new];
// Pass each input queue on to the vim controller with matching
// identifier (and note that it could be cached).
NSEnumerator *e = [queues keyEnumerator];
NSNumber *key;
while ((key = [e nextObject])) {
unsigned ukey = [key unsignedIntValue];
int i = 0, count = [vimControllers count];
for (i = 0; i < count; ++i) {
MMVimController *vc = [vimControllers objectAtIndex:i];
if (ukey == [vc identifier]) {
[vc processInputQueue:[queues objectForKey:key]]; // !exceptions
break;
}
}
if (i < count) continue;
count = [cachedVimControllers count];
for (i = 0; i < count; ++i) {
MMVimController *vc = [cachedVimControllers objectAtIndex:i];
if (ukey == [vc identifier]) {
[vc processInputQueue:[queues objectForKey:key]]; // !exceptions
break;
}
}
if (i == count)
NSLog(@"[%s] WARNING: No Vim controller for identifier=%d",
_cmd, ukey);
}
[queues release];
// If new input arrived while we were processing it would have been
// blocked so we have to schedule it to be processed again.
if (processingFlag < 0)
[self performSelector:@selector(processInputQueues:)
withObject:nil
afterDelay:0
inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,
NSEventTrackingRunLoopMode, nil]];
processingFlag = 0;
}
- (void)addVimController:(MMVimController *)vc
{
int pid = [vc pid];
NSNumber *pidKey = [NSNumber numberWithInt:pid];
if (preloadPid == pid) {
// This controller was preloaded, so add it to the cache and
// schedule another vim process to be preloaded.
preloadPid = -1;
[vc setIsPreloading:YES];
[cachedVimControllers addObject:vc];
[self scheduleVimControllerPreloadAfterDelay:1];
} else {
[vimControllers addObject:vc];
id args = [pidArguments objectForKey:pidKey];
if (args && [NSNull null] != args)
[vc passArguments:args];
// HACK! MacVim does not get activated if it is launched from the
// terminal, so we forcibly activate here unless it is an untitled
// window opening. Untitled windows are treated differently, else
// MacVim would steal the focus if another app was activated while the
// untitled window was loading.
if (!args || args != [NSNull null])
[self activateWhenNextWindowOpens];
if (args)
[pidArguments removeObjectForKey:pidKey];
}
}
@end // MMAppController (Private)
+1 -1
View File
@@ -1139,7 +1139,7 @@ defaultLineHeightForFont(NSFont *font)
if (flags & DRAW_UNDERL)
{
[fg set];
[sp set];
NSRectFill(NSMakeRect(rect.origin.x,
(row + 1) * cellSize.height + kUnderlineOffset,
rect.size.width, kUnderlineHeight));
+2 -1
View File
@@ -21,7 +21,8 @@
NSMutableArray *inputQueue;
NSMutableData *drawData;
NSConnection *connection;
id frontendProxy;
id appProxy;
unsigned identifier;
NSDictionary *colorDict;
NSDictionary *sysColorDict;
NSDictionary *actionDict;
+27 -35
View File
@@ -21,7 +21,7 @@
*
* It is very important to realize that all state is held by the backend, the
* frontend must either ask for state [MMBackend evaluateExpression:] or wait
* for the backend to update [MMVimController processCommandQueue:].
* for the backend to update [MMAppController processInput:forIdentifier:].
*
* The client/server functionality of Vim is handled by the backend. It sets
* up a named NSConnection to which other Vim processes can connect.
@@ -178,7 +178,6 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
[inputQueue release]; inputQueue = nil;
[outputQueue release]; outputQueue = nil;
[drawData release]; drawData = nil;
[frontendProxy release]; frontendProxy = nil;
[connection release]; connection = nil;
[actionDict release]; actionDict = nil;
[sysColorDict release]; sysColorDict = nil;
@@ -328,29 +327,29 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
}
}
BOOL ok = NO;
@try {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(connectionDidDie:)
name:NSConnectionDidDieNotification object:connection];
id proxy = [connection rootProxy];
[proxy setProtocolForProxy:@protocol(MMAppProtocol)];
appProxy = [[connection rootProxy] retain];
[appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
// NOTE: We do not set any new timeout values for the connection to the
// frontend. This means that if the frontend is "stuck" (e.g. in a
// modal loop) then any calls to the frontend will block indefinitely
// (the default timeouts are huge).
int pid = [[NSProcessInfo processInfo] processIdentifier];
frontendProxy = [proxy connectBackend:self pid:pid];
if (frontendProxy) {
[frontendProxy retain];
[frontendProxy setProtocolForProxy:@protocol(MMFrontendProtocol)];
ok = YES;
}
identifier = [appProxy connectBackend:self pid:pid];
return YES;
}
@catch (NSException *e) {
NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
}
return ok;
return NO;
}
- (BOOL)openGUIWindow
@@ -506,10 +505,11 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
[self insertVimStateMessage];
@try {
[frontendProxy processCommandQueue:outputQueue];
//NSLog(@"[%s] Flushing (count=%d)", _cmd, [outputQueue count]);
[appProxy processInput:outputQueue forIdentifier:identifier];
}
@catch (NSException *e) {
NSLog(@"Exception caught when processing command queue: \"%@\"", e);
NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
outputQueue);
if (![connection isValid]) {
@@ -575,7 +575,7 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
// Flush the entire queue in case a VimLeave autocommand added
// something to the queue.
[self queueMessage:CloseWindowMsgID data:nil];
[frontendProxy processCommandQueue:outputQueue];
[appProxy processInput:outputQueue forIdentifier:identifier];
}
@catch (NSException *e) {
NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
@@ -695,9 +695,10 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
{
char_u *s = NULL;
@try {
[frontendProxy showSavePanelWithAttributes:attr];
[self queueMessage:BrowseForFileMsgID properties:attr];
[self flushQueue:YES];
@try {
[self waitForDialogReturn];
if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
@@ -706,7 +707,7 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
[dialogReturn release]; dialogReturn = nil;
}
@catch (NSException *e) {
NSLog(@"Exception caught when showing save panel: \"%@\"", e);
NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
}
return (char *)s;
@@ -734,9 +735,10 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
{
int retval = 0;
@try {
[frontendProxy presentDialogWithAttributes:attr];
[self queueMessage:ShowDialogMsgID properties:attr];
[self flushQueue:YES];
@try {
[self waitForDialogReturn];
if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
@@ -758,7 +760,7 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
[dialogReturn release]; dialogReturn = nil;
}
@catch (NSException *e) {
NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
}
return retval;
@@ -1107,16 +1109,6 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
[inputQueue addObject:(data ? (id)data : [NSNull null])];
}
- (oneway void)processInputAndData:(in bycopy NSArray *)messages
{
// This is just a convenience method that allows the frontend to delay
// sending messages.
int i, count = [messages count];
for (i = 1; i < count; i+=2)
[self processInput:[[messages objectAtIndex:i-1] intValue]
data:[messages objectAtIndex:i]];
}
- (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
errorString:(out bycopy NSString **)errstr
{
@@ -1453,7 +1445,7 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
if (waitForAck) {
// Never received a connection acknowledgement, so die.
[[NSNotificationCenter defaultCenter] removeObserver:self];
[frontendProxy release]; frontendProxy = nil;
[appProxy release]; appProxy = nil;
// NOTE: We intentionally do not call mch_exit() since this in turn
// will lead to -[MMBackend exit] getting called which we want to
@@ -2488,9 +2480,9 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
if ([args objectForKey:@"remoteID"]) {
// NOTE: We have to delay processing any ODB related arguments since
// the file(s) may not be opened until the input buffer is processed.
[self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
withObject:args
waitUntilDone:NO];
[self performSelector:@selector(startOdbEditWithArguments:)
withObject:args
afterDelay:0];
}
NSString *lineString = [args objectForKey:@"cursorLine"];
+6 -6
View File
@@ -20,14 +20,11 @@
@interface MMVimController : NSObject <MMFrontendProtocol>
{
@interface MMVimController : NSObject {
unsigned identifier;
BOOL isInitialized;
MMWindowController *windowController;
id backendProxy;
BOOL inProcessCommandQueue;
NSMutableArray *sendQueue;
NSMutableArray *receiveQueue;
NSMenu *mainMenu;
NSMutableArray *popupMenuItems;
NSToolbar *toolbar;
@@ -43,6 +40,7 @@
}
- (id)initWithBackend:(id)backend pid:(int)processIdentifier;
- (unsigned)identifier;
- (id)backendProxy;
- (int)pid;
- (void)setServerName:(NSString *)name;
@@ -65,7 +63,9 @@
timeout:(NSTimeInterval)timeout;
- (void)addVimInput:(NSString *)string;
- (NSString *)evaluateVimExpression:(NSString *)expr;
- (id)evaluateVimExpressionCocoa:(NSString *)expr errorString:(NSString **)errstr;
- (id)evaluateVimExpressionCocoa:(NSString *)expr
errorString:(NSString **)errstr;
- (void)processInputQueue:(NSArray *)queue;
#ifdef MM_ENABLE_PLUGINS
- (MMPlugInInstanceMediator *)instanceMediator;
#endif
+236 -286
View File
@@ -10,17 +10,20 @@
/*
* MMVimController
*
* Coordinates input/output to/from backend. Each MMBackend communicates
* directly with a MMVimController.
* Coordinates input/output to/from backend. A MMVimController sends input
* directly to a MMBackend, but communication from MMBackend to MMVimController
* goes via MMAppController so that it can coordinate all incoming distributed
* object messages.
*
* MMVimController does not deal with visual presentation. Essentially it
* should be able to run with no window present.
*
* Output from the backend is received in processCommandQueue:. Input is sent
* to the backend via sendMessage:data: or addVimInput:. The latter allows
* execution of arbitrary strings in the Vim process, much like the Vim script
* function remote_send() does. The messages that may be passed between
* frontend and backend are defined in an enum in MacVim.h.
* Output from the backend is received in processInputQueue: (this message is
* called from MMAppController so it is not a DO call). Input is sent to the
* backend via sendMessage:data: or addVimInput:. The latter allows execution
* of arbitrary strings in the Vim process, much like the Vim script function
* remote_send() does. The messages that may be passed between frontend and
* backend are defined in an enum in MacVim.h.
*/
#import "MMAppController.h"
@@ -49,9 +52,7 @@ static NSTimeInterval MMBackendProxyRequestTimeout = 0;
// Timeout used for setDialogReturn:.
static NSTimeInterval MMSetDialogReturnTimeout = 1.0;
// Maximum number of items in the receiveQueue. (It is hard to predict what
// consequences changing this number will have.)
static int MMReceiveQueueCap = 100;
static unsigned identifierCounter = 1;
static BOOL isUnsafeMessage(int msgid);
@@ -65,7 +66,7 @@ static BOOL isUnsafeMessage(int msgid);
@interface MMVimController (Private)
- (void)doProcessCommandQueue:(NSArray *)queue;
- (void)doProcessInputQueue:(NSArray *)queue;
- (void)handleMessage:(int)msgid data:(NSData *)data;
- (void)savePanelDidEnd:(NSSavePanel *)panel code:(int)code
context:(void *)context;
@@ -95,6 +96,8 @@ static BOOL isUnsafeMessage(int msgid);
- (void)popupMenuWithAttributes:(NSDictionary *)attrs;
- (void)connectionDidDie:(NSNotification *)notification;
- (void)scheduleClose;
- (void)handleBrowseForFile:(NSDictionary *)attr;
- (void)handleShowDialog:(NSDictionary *)attr;
@end
@@ -107,11 +110,12 @@ static BOOL isUnsafeMessage(int msgid);
if (!(self = [super init]))
return nil;
// TODO: Come up with a better way of creating an identifier.
identifier = identifierCounter++;
windowController =
[[MMWindowController alloc] initWithVimController:self];
backendProxy = [backend retain];
sendQueue = [NSMutableArray new];
receiveQueue = [NSMutableArray new];
popupMenuItems = [[NSMutableArray alloc] init];
toolbarItemDict = [[NSMutableDictionary alloc] init];
pid = processIdentifier;
@@ -168,8 +172,6 @@ static BOOL isUnsafeMessage(int msgid);
[serverName release]; serverName = nil;
[backendProxy release]; backendProxy = nil;
[sendQueue release]; sendQueue = nil;
[receiveQueue release]; receiveQueue = nil;
[toolbarItemDict release]; toolbarItemDict = nil;
[toolbar release]; toolbar = nil;
@@ -183,6 +185,11 @@ static BOOL isUnsafeMessage(int msgid);
[super dealloc];
}
- (unsigned)identifier
{
return identifier;
}
- (MMWindowController *)windowController
{
return windowController;
@@ -321,21 +328,11 @@ static BOOL isUnsafeMessage(int msgid);
- (void)sendMessage:(int)msgid data:(NSData *)data
{
//NSLog(@"sendMessage:%s (isInitialized=%d inProcessCommandQueue=%d)",
// MessageStrings[msgid], isInitialized, inProcessCommandQueue);
//NSLog(@"sendMessage:%s (isInitialized=%d)",
// MessageStrings[msgid], isInitialized);
if (!isInitialized) return;
if (inProcessCommandQueue) {
//NSLog(@"In process command queue; delaying message send.");
[sendQueue addObject:[NSNumber numberWithInt:msgid]];
if (data)
[sendQueue addObject:data];
else
[sendQueue addObject:[NSNull null]];
return;
}
@try {
[backendProxy processInput:msgid data:data];
}
@@ -353,7 +350,7 @@ static BOOL isUnsafeMessage(int msgid);
// ball forever. In almost all circumstances sendMessage:data: should be
// used instead.
if (!isInitialized || inProcessCommandQueue)
if (!isInitialized)
return NO;
if (timeout < 0) timeout = 0;
@@ -400,7 +397,8 @@ static BOOL isUnsafeMessage(int msgid);
return eval;
}
- (id)evaluateVimExpressionCocoa:(NSString *)expr errorString:(NSString **)errstr
- (id)evaluateVimExpressionCocoa:(NSString *)expr
errorString:(NSString **)errstr
{
id eval = nil;
@@ -423,6 +421,9 @@ static BOOL isUnsafeMessage(int msgid);
{
if (!isInitialized) return;
// Remove any delayed calls made on this object.
[NSObject cancelPreviousPerformRequestsWithTarget:self];
isInitialized = NO;
[toolbar setDelegate:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];
@@ -431,204 +432,19 @@ static BOOL isUnsafeMessage(int msgid);
[windowController cleanup];
}
- (oneway void)showSavePanelWithAttributes:(in bycopy NSDictionary *)attr
- (void)processInputQueue:(NSArray *)queue
{
if (!isInitialized) return;
BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
isEqual:NSDefaultRunLoopMode];
if (!inDefaultMode) {
// Delay call until run loop is in default mode.
[self performSelectorOnMainThread:
@selector(showSavePanelWithAttributes:)
withObject:attr
waitUntilDone:NO
modes:[NSArray arrayWithObject:
NSDefaultRunLoopMode]];
return;
// NOTE: This method must not raise any exceptions (see comment in the
// calling method).
@try {
[self doProcessInputQueue:queue];
[windowController processInputQueueDidFinish];
}
NSString *dir = [attr objectForKey:@"dir"];
BOOL saving = [[attr objectForKey:@"saving"] boolValue];
if (!dir) {
// 'dir == nil' means: set dir to the pwd of the Vim process, or let
// open dialog decide (depending on the below user default).
BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
boolForKey:MMDialogsTrackPwdKey];
if (trackPwd)
dir = [vimState objectForKey:@"pwd"];
@catch (NSException *ex) {
NSLog(@"[%s] Caught exception (pid=%d): %@", _cmd, pid, ex);
}
if (saving) {
[[NSSavePanel savePanel] beginSheetForDirectory:dir file:nil
modalForWindow:[windowController window]
modalDelegate:self
didEndSelector:@selector(savePanelDidEnd:code:context:)
contextInfo:NULL];
} else {
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
[panel setAccessoryView:openPanelAccessoryView()];
[panel beginSheetForDirectory:dir file:nil types:nil
modalForWindow:[windowController window]
modalDelegate:self
didEndSelector:@selector(savePanelDidEnd:code:context:)
contextInfo:NULL];
}
}
- (oneway void)presentDialogWithAttributes:(in bycopy NSDictionary *)attr
{
if (!isInitialized) return;
BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
isEqual:NSDefaultRunLoopMode];
if (!inDefaultMode) {
// Delay call until run loop is in default mode.
[self performSelectorOnMainThread:
@selector(presentDialogWithAttributes:)
withObject:attr
waitUntilDone:NO
modes:[NSArray arrayWithObject:
NSDefaultRunLoopMode]];
return;
}
NSArray *buttonTitles = [attr objectForKey:@"buttonTitles"];
if (!(buttonTitles && [buttonTitles count])) return;
int style = [[attr objectForKey:@"alertStyle"] intValue];
NSString *message = [attr objectForKey:@"messageText"];
NSString *text = [attr objectForKey:@"informativeText"];
NSString *textFieldString = [attr objectForKey:@"textFieldString"];
MMAlert *alert = [[MMAlert alloc] init];
// NOTE! This has to be done before setting the informative text.
if (textFieldString)
[alert setTextFieldString:textFieldString];
[alert setAlertStyle:style];
if (message) {
[alert setMessageText:message];
} else {
// If no message text is specified 'Alert' is used, which we don't
// want, so set an empty string as message text.
[alert setMessageText:@""];
}
if (text) {
[alert setInformativeText:text];
} else if (textFieldString) {
// Make sure there is always room for the input text field.
[alert setInformativeText:@""];
}
unsigned i, count = [buttonTitles count];
for (i = 0; i < count; ++i) {
NSString *title = [buttonTitles objectAtIndex:i];
// NOTE: The title of the button may contain the character '&' to
// indicate that the following letter should be the key equivalent
// associated with the button. Extract this letter and lowercase it.
NSString *keyEquivalent = nil;
NSRange hotkeyRange = [title rangeOfString:@"&"];
if (NSNotFound != hotkeyRange.location) {
if ([title length] > NSMaxRange(hotkeyRange)) {
NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
keyEquivalent = [[title substringWithRange:keyEquivRange]
lowercaseString];
}
NSMutableString *string = [NSMutableString stringWithString:title];
[string deleteCharactersInRange:hotkeyRange];
title = string;
}
[alert addButtonWithTitle:title];
// Set key equivalent for the button, but only if NSAlert hasn't
// already done so. (Check the documentation for
// - [NSAlert addButtonWithTitle:] to see what key equivalents are
// automatically assigned.)
NSButton *btn = [[alert buttons] lastObject];
if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
[btn setKeyEquivalent:keyEquivalent];
}
}
[alert beginSheetModalForWindow:[windowController window]
modalDelegate:self
didEndSelector:@selector(alertDidEnd:code:context:)
contextInfo:NULL];
[alert release];
}
- (oneway void)processCommandQueue:(in bycopy NSArray *)queue
{
if (!isInitialized) return;
if (inProcessCommandQueue) {
// NOTE! If a synchronous DO call is made during
// doProcessCommandQueue: below it may happen that this method is
// called a second time while the synchronous message is waiting for a
// reply (could also happen if doProcessCommandQueue: enters a modal
// loop, see comment below). Since this method cannot be considered
// reentrant, we queue the input and return immediately.
//
// If doProcessCommandQueue: enters a modal loop (happens e.g. on
// ShowPopupMenuMsgID) then the receiveQueue could grow to become
// arbitrarily large because DO calls still get processed. To avoid
// this we set a cap on the size of the queue and simply clear it if it
// becomes too large. (That is messages will be dropped and hence Vim
// and MacVim will at least temporarily be out of sync.)
if ([receiveQueue count] >= MMReceiveQueueCap)
[receiveQueue removeAllObjects];
[receiveQueue addObject:queue];
return;
}
inProcessCommandQueue = YES;
[self doProcessCommandQueue:queue];
int i;
for (i = 0; i < [receiveQueue count]; ++i) {
// Note that doProcessCommandQueue: may cause the receiveQueue to grow
// or get cleared (due to cap being hit). Make sure to retain the item
// to process or it may get released from under us.
NSArray *q = [[receiveQueue objectAtIndex:i] retain];
[self doProcessCommandQueue:q];
[q release];
}
// We assume that the remaining calls make no synchronous DO calls. If
// that did happen anyway, the command queue could get processed out of
// order.
// See comment below why this is called here and not later.
[windowController processCommandQueueDidFinish];
// NOTE: Ensure that no calls are made after this "if" clause that may call
// sendMessage::. If this happens anyway, such messages will be put on the
// send queue and then the queue will not be flushed until the next time
// this method is called.
if ([sendQueue count] > 0) {
@try {
[backendProxy processInputAndData:sendQueue];
}
@catch (NSException *e) {
// Connection timed out, just ignore this.
//NSLog(@"WARNING! Connection timed out in %s", _cmd);
}
[sendQueue removeAllObjects];
}
[receiveQueue removeAllObjects];
inProcessCommandQueue = NO;
}
- (NSToolbarItem *)toolbar:(NSToolbar *)theToolbar
@@ -659,67 +475,60 @@ static BOOL isUnsafeMessage(int msgid);
@implementation MMVimController (Private)
- (void)doProcessCommandQueue:(NSArray *)queue
- (void)doProcessInputQueue:(NSArray *)queue
{
NSMutableArray *delayQueue = nil;
@try {
unsigned i, count = [queue count];
if (count % 2) {
NSLog(@"WARNING: Uneven number of components (%d) in command "
"queue. Skipping...", count);
return;
}
//NSLog(@"======== %s BEGIN ========", _cmd);
for (i = 0; i < count; i += 2) {
NSData *value = [queue objectAtIndex:i];
NSData *data = [queue objectAtIndex:i+1];
int msgid = *((int*)[value bytes]);
//NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
isEqual:NSDefaultRunLoopMode];
if (!inDefaultMode && isUnsafeMessage(msgid)) {
// NOTE: Because we may be listening to DO messages in "event
// tracking mode" we have to take extra care when doing things
// like releasing view items (and other Cocoa objects).
// Messages that may be potentially "unsafe" are delayed until
// the run loop is back to default mode at which time they are
// safe to call again.
// A problem with this approach is that it is hard to
// classify which messages are unsafe. As a rule of thumb, if
// a message may release an object used by the Cocoa framework
// (e.g. views) then the message should be considered unsafe.
// Delaying messages may have undesired side-effects since it
// means that messages may not be processed in the order Vim
// sent them, so beware.
if (!delayQueue)
delayQueue = [NSMutableArray array];
//NSLog(@"Adding unsafe message '%s' to delay queue (mode=%@)",
// MessageStrings[msgid],
// [[NSRunLoop currentRunLoop] currentMode]);
[delayQueue addObject:value];
[delayQueue addObject:data];
} else {
[self handleMessage:msgid data:data];
}
}
//NSLog(@"======== %s END ========", _cmd);
unsigned i, count = [queue count];
if (count % 2) {
NSLog(@"WARNING: Uneven number of components (%d) in command "
"queue. Skipping...", count);
return;
}
@catch (NSException *e) {
NSLog(@"Exception caught whilst processing command queue: %@", e);
//NSLog(@"======== %s BEGIN ========", _cmd);
for (i = 0; i < count; i += 2) {
NSData *value = [queue objectAtIndex:i];
NSData *data = [queue objectAtIndex:i+1];
int msgid = *((int*)[value bytes]);
//NSLog(@"%s%s", _cmd, MessageStrings[msgid]);
BOOL inDefaultMode = [[[NSRunLoop currentRunLoop] currentMode]
isEqual:NSDefaultRunLoopMode];
if (!inDefaultMode && isUnsafeMessage(msgid)) {
// NOTE: Because we may be listening to DO messages in "event
// tracking mode" we have to take extra care when doing things
// like releasing view items (and other Cocoa objects).
// Messages that may be potentially "unsafe" are delayed until
// the run loop is back to default mode at which time they are
// safe to call again.
// A problem with this approach is that it is hard to
// classify which messages are unsafe. As a rule of thumb, if
// a message may release an object used by the Cocoa framework
// (e.g. views) then the message should be considered unsafe.
// Delaying messages may have undesired side-effects since it
// means that messages may not be processed in the order Vim
// sent them, so beware.
if (!delayQueue)
delayQueue = [NSMutableArray array];
//NSLog(@"Adding unsafe message '%s' to delay queue (mode=%@)",
// MessageStrings[msgid],
// [[NSRunLoop currentRunLoop] currentMode]);
[delayQueue addObject:value];
[delayQueue addObject:data];
} else {
[self handleMessage:msgid data:data];
}
}
//NSLog(@"======== %s END ========", _cmd);
if (delayQueue) {
//NSLog(@" Flushing delay queue (%d items)", [delayQueue count]/2);
[self performSelectorOnMainThread:@selector(processCommandQueue:)
withObject:delayQueue
waitUntilDone:NO
modes:[NSArray arrayWithObject:
NSDefaultRunLoopMode]];
[self performSelector:@selector(processInputQueue:)
withObject:delayQueue
afterDelay:0];
}
}
@@ -915,12 +724,10 @@ static BOOL isUnsafeMessage(int msgid);
NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
// The popup menu enters a modal loop so delay this call so that we
// don't block inside processCommandQueue:.
[self performSelectorOnMainThread:@selector(popupMenuWithAttributes:)
withObject:attrs
waitUntilDone:NO
modes:[NSArray arrayWithObject:
NSDefaultRunLoopMode]];
// don't block inside processInputQueue:.
[self performSelector:@selector(popupMenuWithAttributes:)
withObject:attrs
afterDelay:0];
} else if (SetMouseShapeMsgID == msgid) {
const void *bytes = [data bytes];
int shape = *((int*)bytes); bytes += sizeof(int);
@@ -981,6 +788,18 @@ static BOOL isUnsafeMessage(int msgid);
showWithText:[dict objectForKey:@"text"]
flags:[[dict objectForKey:@"flags"] intValue]];
}
} else if (ActivateKeyScriptID == msgid) {
KeyScript(smKeySysScript);
} else if (DeactivateKeyScriptID == msgid) {
KeyScript(smKeyRoman);
} else if (BrowseForFileMsgID == msgid) {
NSDictionary *dict = [NSDictionary dictionaryWithData:data];
if (dict)
[self handleBrowseForFile:dict];
} else if (ShowDialogMsgID == msgid) {
NSDictionary *dict = [NSDictionary dictionaryWithData:data];
if (dict)
[self handleShowDialog:dict];
// IMPORTANT: When adding a new message, make sure to update
// isUnsafeMessage() if necessary!
} else {
@@ -1428,13 +1247,142 @@ static BOOL isUnsafeMessage(int msgid);
// following call ensures that the vim controller is not released until the
// run loop is back in the 'default' mode.
[[MMAppController sharedInstance]
performSelectorOnMainThread:@selector(removeVimController:)
withObject:self
waitUntilDone:NO
modes:[NSArray arrayWithObject:
NSDefaultRunLoopMode]];
performSelector:@selector(removeVimController:)
withObject:self
afterDelay:0];
}
// NSSavePanel delegate
- (void)panel:(id)sender willExpand:(BOOL)expanding
{
// Show or hide the "show hidden files" button
if (expanding) {
[sender setAccessoryView:showHiddenFilesView()];
} else {
[sender setShowsHiddenFiles:NO];
[sender setAccessoryView:nil];
}
}
- (void)handleBrowseForFile:(NSDictionary *)attr
{
if (!isInitialized) return;
NSString *dir = [attr objectForKey:@"dir"];
BOOL saving = [[attr objectForKey:@"saving"] boolValue];
if (!dir) {
// 'dir == nil' means: set dir to the pwd of the Vim process, or let
// open dialog decide (depending on the below user default).
BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
boolForKey:MMDialogsTrackPwdKey];
if (trackPwd)
dir = [vimState objectForKey:@"pwd"];
}
if (saving) {
NSSavePanel *panel = [NSSavePanel savePanel];
// The delegate will be notified when the panel is expanded at which
// time we may hide/show the "show hidden files" button (this button is
// always visible for the open panel since it is always expanded).
[panel setDelegate:self];
if ([panel isExpanded])
[panel setAccessoryView:showHiddenFilesView()];
[panel beginSheetForDirectory:dir file:nil
modalForWindow:[windowController window]
modalDelegate:self
didEndSelector:@selector(savePanelDidEnd:code:context:)
contextInfo:NULL];
} else {
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
[panel setAccessoryView:showHiddenFilesView()];
[panel beginSheetForDirectory:dir file:nil types:nil
modalForWindow:[windowController window]
modalDelegate:self
didEndSelector:@selector(savePanelDidEnd:code:context:)
contextInfo:NULL];
}
}
- (void)handleShowDialog:(NSDictionary *)attr
{
if (!isInitialized) return;
NSArray *buttonTitles = [attr objectForKey:@"buttonTitles"];
if (!(buttonTitles && [buttonTitles count])) return;
int style = [[attr objectForKey:@"alertStyle"] intValue];
NSString *message = [attr objectForKey:@"messageText"];
NSString *text = [attr objectForKey:@"informativeText"];
NSString *textFieldString = [attr objectForKey:@"textFieldString"];
MMAlert *alert = [[MMAlert alloc] init];
// NOTE! This has to be done before setting the informative text.
if (textFieldString)
[alert setTextFieldString:textFieldString];
[alert setAlertStyle:style];
if (message) {
[alert setMessageText:message];
} else {
// If no message text is specified 'Alert' is used, which we don't
// want, so set an empty string as message text.
[alert setMessageText:@""];
}
if (text) {
[alert setInformativeText:text];
} else if (textFieldString) {
// Make sure there is always room for the input text field.
[alert setInformativeText:@""];
}
unsigned i, count = [buttonTitles count];
for (i = 0; i < count; ++i) {
NSString *title = [buttonTitles objectAtIndex:i];
// NOTE: The title of the button may contain the character '&' to
// indicate that the following letter should be the key equivalent
// associated with the button. Extract this letter and lowercase it.
NSString *keyEquivalent = nil;
NSRange hotkeyRange = [title rangeOfString:@"&"];
if (NSNotFound != hotkeyRange.location) {
if ([title length] > NSMaxRange(hotkeyRange)) {
NSRange keyEquivRange = NSMakeRange(hotkeyRange.location+1, 1);
keyEquivalent = [[title substringWithRange:keyEquivRange]
lowercaseString];
}
NSMutableString *string = [NSMutableString stringWithString:title];
[string deleteCharactersInRange:hotkeyRange];
title = string;
}
[alert addButtonWithTitle:title];
// Set key equivalent for the button, but only if NSAlert hasn't
// already done so. (Check the documentation for
// - [NSAlert addButtonWithTitle:] to see what key equivalents are
// automatically assigned.)
NSButton *btn = [[alert buttons] lastObject];
if ([[btn keyEquivalent] length] == 0 && keyEquivalent) {
[btn setKeyEquivalent:keyEquivalent];
}
}
[alert beginSheetModalForWindow:[windowController window]
modalDelegate:self
didEndSelector:@selector(alertDidEnd:code:context:)
contextInfo:NULL];
[alert release];
}
@end // MMVimController (Private)
@@ -1525,6 +1473,8 @@ isUnsafeMessage(int msgid)
EnterFullscreenMsgID, // Modifies delegate of window controller
LeaveFullscreenMsgID, // Modifies delegate of window controller
CloseWindowMsgID, // See note below
BrowseForFileMsgID, // Enters modal loop
ShowDialogMsgID, // Enters modal loop
};
// NOTE about CloseWindowMsgID: If this arrives at the same time as say
+2 -2
View File
@@ -22,7 +22,7 @@
MMVimView *vimView;
BOOL setupDone;
BOOL shouldResizeVimView;
int shouldUpdateToolbar;
int updateToolbarFlag;
BOOL keepOnScreen;
BOOL fullscreenEnabled;
NSString *windowAutosaveKey;
@@ -55,7 +55,7 @@
- (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore;
- (void)setFont:(NSFont *)font;
- (void)setWideFont:(NSFont *)font;
- (void)processCommandQueueDidFinish;
- (void)processInputQueueDidFinish;
- (void)showTabBar:(BOOL)on;
- (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode;
- (void)setMouseShape:(int)shape;
+14 -16
View File
@@ -308,7 +308,7 @@
// NOTE: The only place where the (rows,columns) of the vim view are
// modified is here and when entering/leaving full-screen. Setting these
// values have no immediate effect, the actual resizing of the view is done
// in processCommandQueueDidFinish.
// in processInputQueueDidFinish.
//
// The 'live' flag indicates that this resize originated from a live
// resize; it may very well happen that the view is no longer in live
@@ -419,11 +419,8 @@
[[vimView textView] setWideFont:font];
}
- (void)processCommandQueueDidFinish
- (void)processInputQueueDidFinish
{
// IMPORTANT! No synchronous DO calls are allowed in this method. They
// may cause the command queue to get processed out of order.
// NOTE: Resizing is delayed until after all commands have been processed
// since it often happens that more than one command will cause a resize.
// If we were to immediately resize then the vim view size would jitter
@@ -453,10 +450,8 @@
keepOnScreen = NO;
}
if (shouldUpdateToolbar != 0) {
if (updateToolbarFlag != 0)
[self updateToolbar];
shouldUpdateToolbar = 0;
}
}
- (void)showTabBar:(BOOL)on
@@ -491,16 +486,16 @@
[toolbar setSizeMode:size];
[toolbar setDisplayMode:mode];
// Positive flag shows toolbar, negative hides it.
updateToolbarFlag = on ? 1 : -1;
// NOTE: If the window is not visible we must toggle the toolbar
// immediately, otherwise "set go-=T" in .gvimrc will lead to the toolbar
// showing its hide animation every time a new window is opened. (See
// processCommandQueueDidFinish for the reason why we need to delay
// toggling the toolbar when the window is visible.)
if ([decoratedWindow isVisible]) {
shouldUpdateToolbar = on ? 1 : -1;
} else {
// processInputQueueDidFinish for the reason why we need to delay toggling
// the toolbar when the window is visible.)
if (![decoratedWindow isVisible])
[self updateToolbar];
}
}
- (void)setMouseShape:(int)shape
@@ -1061,9 +1056,10 @@
- (void)updateToolbar
{
NSToolbar *toolbar = [decoratedWindow toolbar];
if (!toolbar) return;
if (nil == toolbar || 0 == updateToolbarFlag) return;
BOOL on = shouldUpdateToolbar > 0 ? YES : NO;
// Positive flag shows toolbar, negative hides it.
BOOL on = updateToolbarFlag > 0 ? YES : NO;
[toolbar setVisible:on];
if (([decoratedWindow styleMask] & NSTexturedBackgroundWindowMask) == 0) {
@@ -1079,6 +1075,8 @@
// is visible (because it brings its own separator).
[self hideTablineSeparator:![[vimView tabBarControl] isHidden]];
}
updateToolbarFlag = 0;
}
@end // MMWindowController (Private)
+24 -20
View File
@@ -33,7 +33,6 @@
//
@protocol MMBackendProtocol
- (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data;
- (oneway void)processInputAndData:(in bycopy NSArray *)messages;
- (oneway void)setDialogReturn:(in bycopy id)obj;
- (NSString *)evaluateExpression:(in bycopy NSString *)expr;
- (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
@@ -42,30 +41,23 @@
- (oneway void)acknowledgeConnection;
@end
//
// This is the protocol MMVimController implements.
//
// Be very careful if you want to add methods to this protocol. Since DO
// messages may arrive while Cocoa is in the middle of processing some other
// message be sure to consider reentrancy issues. Look at processCommandQueue:
// to see an example of how to deal with this.
//
@protocol MMFrontendProtocol
- (oneway void)processCommandQueue:(in bycopy NSArray *)queue;
- (oneway void)showSavePanelWithAttributes:(in bycopy NSDictionary *)attr;
- (oneway void)presentDialogWithAttributes:(in bycopy NSDictionary *)attr;
@end
//
// This is the protocol MMAppController implements.
//
// It handles connections between MacVim and Vim.
// It handles connections between MacVim and Vim and communication from Vim to
// MacVim.
//
// Do not add methods to this interface without a _very_ good reason (if
// possible, instead add a new message to the *MsgID enum below and pass it via
// processInput:forIdentifier). Methods should not modify the state directly
// but should instead delay any potential modifications (see
// connectBackend:pid: and processInput:forIdentifier:).
//
@protocol MMAppProtocol
- (byref id <MMFrontendProtocol>)
connectBackend:(byref in id <MMBackendProtocol>)backend
pid:(int)pid;
- (unsigned)connectBackend:(byref in id <MMBackendProtocol>)proxy pid:(int)pid;
- (oneway void)processInput:(in bycopy NSArray *)queue
forIdentifier:(unsigned)identifier;
- (NSArray *)serverList;
@end
@@ -109,7 +101,7 @@
extern char *MessageStrings[];
enum {
OpenWindowMsgID = 1,
OpenWindowMsgID = 1, // NOTE: FIRST IN ENUM MUST BE 1
InsertTextMsgID,
KeyDownMsgID,
CmdKeyMsgID,
@@ -177,6 +169,11 @@ enum {
SetFullscreenColorMsgID,
ShowFindReplaceDialogMsgID,
FindReplaceMsgID,
ActivateKeyScriptID,
DeactivateKeyScriptID,
BrowseForFileMsgID,
ShowDialogMsgID,
LastMsgID // NOTE: MUST BE LAST MESSAGE IN ENUM!
};
@@ -215,6 +212,13 @@ enum {
MMTabInfoCount
};
// Create a string holding the labels of all messages in message queue for
// debugging purposes (condense some messages since there may typically be LOTS
// of them on a queue).
NSString *debugStringForMessageQueue(NSArray *queue);
// Argument used to stop MacVim from opening an empty window on startup
// (techincally this is a user default but should not be used as such).
extern NSString *MMNoWindowKey;
+33
View File
@@ -84,6 +84,11 @@ char *MessageStrings[] =
"SetFullscreenColorMsgID",
"ShowFindReplaceDialogMsgID",
"FindReplaceMsgID",
"ActivateKeyScriptID",
"DeactivateKeyScriptID",
"BrowseForFileMsgID",
"ShowDialogMsgID",
"END OF MESSAGE IDs" // NOTE: Must be last!
};
@@ -98,6 +103,34 @@ NSString *VimPBoardType = @"VimPBoardType";
// Create a string holding the labels of all messages in message queue for
// debugging purposes (condense some messages since there may typically be LOTS
// of them on a queue).
NSString *
debugStringForMessageQueue(NSArray *queue)
{
NSMutableString *s = [NSMutableString new];
unsigned i, count = [queue count];
int item = 0, menu = 0, enable = 0;
for (i = 0; i < count; i += 2) {
NSData *value = [queue objectAtIndex:i];
int msgid = *((int*)[value bytes]);
if (msgid < 1 || msgid >= LastMsgID)
continue;
if (msgid == AddMenuItemMsgID) ++item;
else if (msgid == AddMenuMsgID) ++menu;
else if (msgid == EnableMenuItemMsgID) ++enable;
else [s appendFormat:@"%s ", MessageStrings[msgid]];
}
if (item > 0) [s appendFormat:@"AddMenuItemMsgID(%d) ", item];
if (menu > 0) [s appendFormat:@"AddMenuMsgID(%d) ", menu];
if (enable > 0) [s appendFormat:@"EnableMenuItemMsgID(%d) ", enable];
return [s autorelease];
}
@implementation NSString (MMExtras)
+3 -3
View File
@@ -737,7 +737,7 @@
i386,
);
COPY_PHASE_STRIP = YES;
CURRENT_PROJECT_VERSION = 44;
CURRENT_PROJECT_VERSION = 45;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(FRAMEWORK_SEARCH_PATHS_QUOTED_1)",
@@ -778,7 +778,7 @@
buildSettings = {
ARCHS = "$(NATIVE_ARCH)";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 44;
CURRENT_PROJECT_VERSION = 45;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(FRAMEWORK_SEARCH_PATHS_QUOTED_1)",
@@ -810,7 +810,7 @@
buildSettings = {
ARCHS = "$(NATIVE_ARCH)";
COPY_PHASE_STRIP = YES;
CURRENT_PROJECT_VERSION = 44;
CURRENT_PROJECT_VERSION = 45;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(FRAMEWORK_SEARCH_PATHS_QUOTED_1)",
+5 -4
View File
@@ -95,7 +95,7 @@ enum {
@end
@interface NSOpenPanel (MMExtras)
@interface NSSavePanel (MMExtras)
- (void)hiddenFilesButtonToggled:(id)sender;
- (void)setShowsHiddenFiles:(BOOL)show;
@end
@@ -130,7 +130,8 @@ enum {
// Create a view to be used as accessory for open panel. This function assumes
// ownership of the view so do not release it.
NSView *openPanelAccessoryView();
// Create a view with a "show hidden files" button to be used as accessory for
// open/save panels. This function assumes ownership of the view so do not
// release it.
NSView *showHiddenFilesView();
+5 -5
View File
@@ -93,7 +93,7 @@ NSString *MMLoadDefaultFontKey = @"MMLoadDefaultFont";
@implementation NSOpenPanel (MMExtras)
@implementation NSSavePanel (MMExtras)
- (void)hiddenFilesButtonToggled:(id)sender
{
@@ -122,7 +122,7 @@ NSString *MMLoadDefaultFontKey = @"MMLoadDefaultFont";
[invocation invoke];
}
@end // NSOpenPanel (MMExtras)
@end // NSSavePanel (MMExtras)
@@ -263,7 +263,7 @@ NSString *MMLoadDefaultFontKey = @"MMLoadDefaultFont";
NSView *
openPanelAccessoryView()
showHiddenFilesView()
{
// Return a new button object for each NSOpenPanel -- several of them
// could be displayed at once.
@@ -272,13 +272,13 @@ openPanelAccessoryView()
NSButton *button = [[[NSButton alloc]
initWithFrame:NSMakeRect(0, 0, 140, 18)] autorelease];
[button setTitle:
NSLocalizedString(@"Show Hidden Files", @"Open File Dialog")];
NSLocalizedString(@"Show Hidden Files", @"Show Hidden Files Checkbox")];
[button setButtonType:NSSwitchButton];
[button setTarget:nil];
[button setAction:@selector(hiddenFilesButtonToggled:)];
// use the regular control size (checkbox is a bit smaller without this)
// Use the regular control size (checkbox is a bit smaller without this)
NSControlSize buttonSize = NSRegularControlSize;
float fontSize = [NSFont systemFontSizeForControlSize:buttonSize];
NSCell *theCell = [button cell];
+17 -18
View File
@@ -20,16 +20,16 @@ is very easy to pick up if you know C and some object oriented programming.)
Each editor window in MacVim runs its own Vim process (but there is always
only one MacVim process). Communication between MacVim and a Vim process is
done using Distributed Objects (DO). Each Vim process is represented by a
backend object (MMBackend) and it communicates with a frontend object in the
Vim process (MMVimController). The interface between the backend and frontend
backend object (MMBackend) and it communicates with the frontend object in the
Vim process (MMAppController). The interface between the backend and frontend
is defined in MacVim.h.
The frontend sends input to the backend by calling
-[MMBackend processInput:data:]. The backend queues output on a command queue
and sends it to the frontend at opportune times by calling
-[MMVimController processCommandQueue:]. These are both asynchronous calls so
MacVim can keep drawing and receiving input while Vim is working away, thus
always keeping the user interface responsive.
-[MMAppController processInput:forIdentifier:]. These are both asynchronous
calls so MacVim can keep drawing and receiving input while Vim is working away,
thus always keeping the user interface responsive.
The state of each editor window is kept entirely in the Vim process. MacVim
should remain "ignorant" in the sense that it knows nothing of the actual
@@ -46,7 +46,7 @@ to MacVim inside -[MMBackend queueVimStateMessage].
Vim:
Hooks from within Vim are implmented in gui_macvim.m, the name of such
Hooks from within Vim are implemented in gui_macvim.m, the name of such
functions usually start with "gui_mch_" and they should simply put a message
on the output queue, by calling queueMessage:properties: on the singleton
MMBackend object [MMBackend sharedInstance] (see e.g. gui_mch_destroy_menu()).
@@ -63,7 +63,7 @@ for some reason fails to update the run loop then incoming DO calls will not
be processed and for this reason it is best to avoid making synchronous DO
calls from MacVim. (If synchronous calls must be made then it is important to
set proper timeouts so that MacVim doesn't "hang", see
-[MMVimConroller sendMessageNow:::] to see how this can be done.)
-[MMVimController sendMessageNow:::] to see how this can be done.)
MacVim:
@@ -71,16 +71,15 @@ MacVim:
The main nib of MacVim.app is MainMenu.nib which contains the default menu and
an instance of MMAppController, which is connected as the delegate of
NSApplication. That means, when MacVim starts it will load this nib file and
automatically create an instance of the MMAppController singleton.
automatically create an instance of the MMAppController singleton. All
incoming distributed object calls go via MMAppController.
A new editor window is opened by calling
-[MMAppController launchVimProcessWithArguments:]. This functions starts a
new Vim process (by executing the Vim binary). The Vim process lets MacVim
know when it has launched by calling -[MMAppController connectBackend:pid:]
and MacVim responds to this message by creating a new frontend object
(MMVimController) and returns a proxy to this object back to the Vim process.
From this point onward the Vim process communicates directly with the
MMVimController.
and MacVim responds to this message by creating a new vim controller and
returns an identifier for this object back to the Vim process.
The MMVimController represents the frontend of a Vim process inside MacVim.
It coordinates all communication with the Vim process and delegates output
@@ -88,7 +87,7 @@ that affects visual presentation to a MMWindowController object. Read the
Cocoa documentation on the responsibilities of a window controller.
Input (keyboard & mouse) handling and drawing is handled by a helper object
(MMTextViewHelper) to the current text view (MMTextView, MMAtsuiTextView).
(MMTextViewHelper) to the current text view (MMTextView, MMAtsuiTextView, ...).
Distributed Object dangers:
@@ -106,7 +105,7 @@ message may arrive.
the menu flashes briefly. During this "flash" a modal loop is entered.
Item 1 can cause a problem if MacVim sends a synchronous message and before a
reply reacheds MacVim another message is received. From the source code it
reply reaches MacVim another message is received. From the source code it
looks like the synchronous message blocks but in fact the other message is
executed during this "block". If the other message changes state radically
something may go wrong after the synchronous DO message returns.
@@ -117,8 +116,8 @@ which may be even more puzzling.
One way to alleviate these problems is to ensure a DO message isn't entered
twice by setting a boolean at the beginning of the message and clearing it
afterwards. If the boolean is already set when entering the call must somehow
be delayed. See -[MMVimController processCommandQueue:] for a concrete
example.
be delayed. See processInput:forIdentifier: and processInputQueues: inside
MMAppController for a concrete example.
Another danger is that we must take care when releasing objects that Cocoa may
be using. See -[MMVimController connectionDidDie:] how MacVim releases
@@ -133,7 +132,7 @@ what they contain:
MMAppController.* Everything related to running the application
MMBackend.* Object representing a Vim process in backend
MMTextView.* Handles input and drawing
MMVimController.* Object representing a Vim Process in frontend
MMVimController.* Object representing a Vim process in frontend
MMVimView.* Cocoa view object
MMWindowController.* Coordinates visual presentation
MacVim.* Code shared between MacVim and Vim
@@ -161,4 +160,4 @@ The application bundle can be found inside "src/MacVim/build/Release".
Bjorn Winckler <bjorn.winckler@gmail.com>
November 22, 2008
April 5, 2009
+4 -2
View File
@@ -1265,8 +1265,10 @@ im_set_active(int active)
// respectively.
SInt32 systemScript = GetScriptManagerVariable(smSysScript);
if (!p_imdisable && smRoman != systemScript)
KeyScript(active ? smKeySysScript : smKeyRoman);
if (!p_imdisable && smRoman != systemScript) {
int msgid = active ? ActivateKeyScriptID : DeactivateKeyScriptID;
[[MMBackend sharedInstance] queueMessage:msgid properties:nil];
}
}
+25
View File
@@ -40,6 +40,31 @@
Sparkle supports updates in zip, tar, tbz, tgz, or dmg format.
-->
<item>
<title>Snapshot 45 released</title>
<description><![CDATA[
<h1>MacVim snapshot 45 released</h1>
<p> Changes since snapshot 44:
<ul>
<li> The toolbar is not hidden by default again (if you prefer having the toolbar hidden, then add the line "set go-=T" to your ~/.gvimrc file) </li>
<li> The ATSUI renderer honors the 'guisp' highlighting color </li>
<li> Fix the forever bouncing Dock icon bug (Kazuki Sakamoto) </li>
<li> Add the "Show Hidden Files" checkbox button to the Save dialog whenever the file browser is expanded </li>
<li> Frontend refactoring </li>
</ul>
</p>
]]></description>
<pubDate>Mon, 13 Apr 2009 19:19 CET</pubDate>
<enclosure type="application/octet-stream"
url="http://newmacvim.muskokamug.org/mirror/files/MacVim-snapshot-45.tbz"
length="8135831"
sparkle:version="45"
sparkle:shortVersionString="7.2"
/>
</item>
<item>
<title>Snapshot 44 released</title>
<description><![CDATA[