Update README file

This commit is contained in:
Bjorn Winckler
2008-06-22 17:08:38 +02:00
parent ab9df03d0d
commit 10cff2591d
+150 -182
View File
@@ -1,196 +1,164 @@
Compiling:
This README contains an overview of the MacVim source code and a very short
description on how to build the application.
- To build the project:
+ patch vim7 src with MacVim patch
+ make vim7 src with --enable-gui=macvim
+ build MacVim.xcodeproj
- To install:
+ copy MacVim.app to /Applications (or anywhere you want it)
+ in ~/.profile add this line:
alias gvim='/Applications/MacVim.app/Contents/MacOS/Vim -g'
- To run:
+ Double click MacVim icon
+ with the above alias you can type 'gvim' in terminal to open MacVim
(if the -g switch is left out, then Vim is started in terminal mode)
+ in terminal mode of Vim, type :gui and MacVim will start
- Technical notes:
+ to build a universal binary, the compiler AND linker needs the flags
'-isysroot /Developer/SDKs/MacOSX10.4u.sdk -arch ppc -arch i386'; also,
make needs argument --with-mac-arch=both
+ vim runtime files are copied to
'MacVim.app/Contents/Resources/vim/runtime/'
Weirdness:
- [obsolete] When the text system (Cocoa) comes across multi byte characters it
automatically chooses a font for those characters; this font may not be the
same height as the one set on the text storage and hence the program must
account for the fact that lines may have differing heights.
We get around this problem by resizing the window to fit the text storage
after the layout manager has performed layout. As a side-effect the user
sees how the window resizes when certain multi byte characters are being
displayed.
- [obsolete] Remember to set 'inputReceived = YES' in MMBackend
handlePortMessage:, otherwise Vim will not inform MMVimController of
changes it makes (e.g. in response to a key down event).
- The way delegate messages from the tab bar are handled are based on lots of
assumptions on how the code works. See comments in tabView:... delegate
messages in MMWindowController.
- [obsolete] The insertion point is automatically moved to wherever changes are
made in the text storage. To set the position manually (via
setSelectedRange:), first call endEditing on the text storage.
- Delegate messages from the tab view need to be able to differentiate whether
the message was sent due to the user clicking a tab with the mouse, or if the
vim task initiated the change. To facilitate this, flags must be set
whenever the vim task does something that results in these delegate messages
being sent. See comments in the tabView:...Item: messages in
MMWindowController.
- In Vim the first tab has index 1, in the gui the first tab has index 0. This
is compensated for in MMBackend.m.
- The PSMTabBarControl does not reorder the NSTabView when a user drags tabs
around, so we must rely on [PSMTabBarControl representedItems] to get the
correct order of tabs (the order which the user can 'see'). WARNING! This
means that the code cannot rely on calls like
[NSTabView selectTabViewItemAtIndex:] etc. since the NSTabView has the
'wrong' order.
- The MMVimController is added to the NSEventTrackingRunLoopMode, otherwise
updates from Vim would not reach the MMVimController while the user
resizes the window using the mouse.
- It seems that (oneway void) DO messages can arrive when another such message
is being processed. For this reason, no input is sent to Vim whilst in
processCommandQueue:. Instead, messages are queued and sent when
processCommandQueue: has finished. Otherwise the invariant that all Vim
messages must appear in the same order they were issued will be violated.
- Text storage dimensions are not ever directly modified, instead a message is
sent to Vim asking it to change the "shell size". Otherwise, a message
asking Vim to change the shell size might get lost and Vim and MacVim will
have inconsistent states.
- gui_mch_browse() and gui_mch_dialog() are blocking calls, but you can't put
up dialogs in Cocoa which block until the user dismisses them (it uses
callbacks). This complicates the browsing somewhat.
- When binding menus to :action note that that action will be used for all
modes. The reason for this is that MacVim needs to be able to execute such
menu items even when no windows are open (e.g. newVimWindow:) and the default
menu action relies on Vim to deal with it.
- The 'help' key is treated as a special key by Cocoa; when the user presses
this key the mouse cursor changes to a question mark and the application is
put into 'context help mode'. The key down event is never sent down the
responder chain. To get around this problem we are forced to subclass
NSApplication and look for the 'help' key in sendEvent: (see MMApplication).
The information in here is not meant to be exhaustive. A lot more information
can be found in the source code comments.
Design decisions:
Source code overview:
- Output is queued and sent to the MMVimController only when
[MMBackend flushQueue] is called in order to minimize the amount of
messages sent back and forth between the task and gui. Also, this makes sure
that commands reach MacVim in the same order they were issued by Vim.
- Drawing commands are stored in a buffer (drawData) and put on the output
queue whenever [MMBackend flush] is called. This buffer might no
longer be needed now that there is a general output queue. However, the
existing code works, so why change it?
- [obsolete] The gui listens for tasks on a named port whose name is derived
from CFBundleIdentifier (which is defined inside Info.plist of the app
bundle). In order to run two different copies of MacVim at the same time,
they need to have differing bundle identifiers; otherwise one copy will not
be able to create the named listening port and all tasks will connect to the
first copy.
- The gui creates a named NSConnection which vends the MMAppController object.
- All tabs share one text view and its associated text storage. There used to
be one text view per tab, but this only complicated the code since Vim has no
concept of different views (as in NSView).
- Vim holds the actual state. MacVim should never change Vim related states
directly, instead it must ask Vim to change the state and wait for Vim to
reply with an actual state change command.
- If MacVim wants to change the state of Vim it must go through
processInput:data:, this is an asynchronous call.
- MacVim can query state information synchronously by adding a suitable message
to MMBackendProtocol, however this must not change the state of Vim!
- If MacVim or Vim dies, the NSConnection is invalidated and connectionDidDie:
is invoked.
- Input may reach the backend whenever the run loop is updated. This can cause
problems if more input is received whilst already processing other input. At
the moment new input is dropped if the backend is already processing other
input.
MacVim.app consists of two executables: MacVim and Vim. MacVim is a Cocoa app
which does all the window management including drawing and receiving input.
Vim is the actual editor which receives input from MacVim and sends output
back when there is something to draw.
As far as the source code files goes, MacVim.[m|h] contains code shared
between MacVim and Vim, gui_macvim.m and MMBackend.[m|h] belongs to Vim,
everything else belongs to MacVim. (The source code is all Objective-C which
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
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.
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
state of a Vim process. Typically this is not a problem, but sometimes MacVim
must change state without going via Vim, and sometimes MacVim needs immediate
access to the state from Vim. The former happens e.g. when the user drags to
resize a window (MacVim changes the window dimensions immediately without
asking Vim first), the second can happen when some option variable affects the
way something is presented visually (e.g. MacVim needs immediate access to the
'mousehide' option so that it can hide the mouse cursor when input is
received). State information that may be required in this way can be "pushed"
to MacVim inside -[MMBackend queueVimStateMessage].
Keyboard stuff:
Vim:
- input ends up in one of the following methods
(1) insertText:
(2) doCommandBySelector:
(3) performKeyEquivalent:
Hooks from within Vim are implmented 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()).
The output queue is flushed when requested (in -[MMBackend flushQueue]) or
before Vim takes a nap whilst waiting for new input (in
-[MMBackend waitForInput]).
- (1) handles: printable keys (a, Space, 1, ...) and <M-key> (also <M-C-key>).
if Ctrl+Option is held [NSEvents characters] will translate the input to
control characters; note that if the translation fails, then Shift and Option
modifiers are NOT includeded in [NSEvent characters], but they are included
in [NSEvent charactersIgnoringModifiers]. e.g. given <M-C-S-1>, characters
returns 1, charactersIgnoringModifiers returns <M-S-1>.
- (2) handles: Ctrl+key, enter, backspace, escape.
same note on translation of Ctrl+key as above holds true.
come Ctrl+key combos are by default mapped to several commands, so Ctrl+keys
must be intercepted in keyDown:
- (3) handles: Cmd+key, arrow keys, function keys, help key
Cmd+letter keys never reach the app if this method isn't overridden (but
Cmd+function keys do)
Cmd+function key must not be intercepted here or input methods won't work
- <M-Space> and <Space> are two different characters (the former is 0xa0)
- Cocoa translates <C-Enter> to Ctrl-C so this must be taken care of
- <Tab> with various modifiers is very special, check MMBackend how it is
handled
Note that each Vim process has its own run loop (it is required for DO) and
since Vim is in charge of its thread it needs to "update" the run loop
manually. This can happen in -[MMBackend update], which returns immediately
if nothing is pending on the run loop, or in -[MMBackend waitForInput], which
can possibly block until input appears on the run loop. In any case, if Vim
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.)
Bugs:
MacVim:
- Using NSString initWithBytesNoCopy:::: causes crash when trying to set window
title.
- NSTabViewItem setInitialFirstResponder: seems to have no effect, so we
manually set the first responder when the tab item selection changes.
- PSMTabBarControl never removes itself as an observer, which can cause all
sort of weird problems (crashes etc.), so this is taken care of at cleanup.
- PSMTabBarControl retains its delegate, so the delegate is forcibly set to nil
at cleanup, else there will be a memory leak.
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 mens, when MacVim starts it will load this nib file and
automatically create an instance of the MMAppController singleton.
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 calling 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.
The MMVimController represents the frontend of a Vim process inside MacVim.
It coordinates all communication with the Vim process and delegates output
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 MMTextView
object.
Features (!supp indicates that a feature is not supported):
Distributed Object dangers:
- Multiple top-level windows: each window runs its own vim process (they are
completely independent)
- Tabs: uses PSMTabBarControl to show tabs, can reorder tabs by dragging them,
has overflow menu, new tab button on tabline
- Menubar: accelerators !supp, actext hint displayed as tool tip
instead of on menu, each window has its own menu, set key equivalents with
:menukeyequiv command
- Toolbar: toolbariconsize supported (tiny&small equiv to 24x24 px,
medium&large equiv to 32x32 px), toolbar supports 'icons' and 'text' options
(but not 'tooltip' which is always on), each window has its own toolbar,
custom toolbar items
- Cocoa input protocols: input managers, character palette input etc.
supported, marked text partially supported, cocoa key bindings
(DefaultKeyBinding.dict) are disabled
- Mouse: resize (vim) windows, selection, different mouse cursors,
autoscrolling whilst selecting (horizontal autoscroll !supp)
- Drag and Drop: drag files onto dock icon to open in tabs, drag text and files
onto text view
- Zoom: Command-click to zoom to fill window, otherwise only zoom height,
hold down Option to zoom all windows
- Resize: live resize (although terribly slow), :set lines will not make window
higher than can fit the screen (no such restrictions on width at the moment)
- Pasteboard: star-register works with the mac os x pasteboard
- Open/Save dialog: use with :browse
- Gui dialogs
- Fonts: bold/italic/underline traits supported, font changes with ':set gfn',
or use font panel
- File type associations: add more associations by editing Info.plist
- Start GUI from terminal, type :gui
- Scroll bars
- Wide characters: but composed characters !supp
- Printing: !supp
- Find/Replace dialog: !supp
- External editor protocol: !supp
- Services menu: some simple minded provider entries
- Encodings: !supp (enc, tenc always set to utf-8)
- Autosave window position
- Smart cascading of new windows
- Client/server support (only gui window can become server)
Distributed Object messages are handled whenever the run loop is updated.
Since the run loop can be updated at unpredictable times some care has to be
taken when implementing DO messages. Some unexpected examples of when the run
loop is updated:
1. When a synchronous DO message is sent. The run loop goes into a loop
waiting for a return to the synchronous message; During this wait another DO
message may arrive.
2. When a modal loop is entered. For example, when a user presses a Cmd-key
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
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.
Item 2 can cause similar problems but it may happen deep inside a Cocoa call
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.
Another danger is that we must take care when releasing objects that Cocoa may
be using. See -[MMVimController connectionDidDie:] how MacVim releases
MMVimControllers when the Vim process they control exits.
Source code file organisation:
Here is an incomplete list of source code files with a short explanation of
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
MMVimView.* Cocoa view object
MMWindowController.* Coordinates visual presentation
MacVim.* Code shared between MacVim and Vim
gui_macvim.m Hooks from Vim
Building:
You will need to install the Xcode tools before building the source code.
Nothing else needs to be installed in order to build MacVim.
Steps to build MacVim.app (the text before the '$' shows the folder you should
be in when executing these commands):
1. Configure Vim
src/$ configure --enable-gui=macvim
2. Build Vim executable
src/$ make
3. Build MacVim.app application bundle
src/MacVim/$ xcodebuild
The application bundle can be found inside "src/MacVim/build/Release".
Bjorn Winckler <bjorn.winckler@gmail.com>
June 22, 2008