Files
macvim-mirror/src/MacVim/gui_macvim.m
T
2017-10-18 15:19:16 +09:00

2443 lines
64 KiB
Objective-C

/* vi:set ts=8 sts=4 sw=4 ft=objc:
*
* VIM - Vi IMproved by Bram Moolenaar
* MacVim GUI port by Bjorn Winckler
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* gui_macvim.m
*
* Hooks for the Vim gui code. Mainly passes control on to MMBackend.
*/
#import "MMBackend.h"
#import "MacVim.h"
#import "vim.h"
#import <Foundation/Foundation.h>
// HACK! Used in gui.c to determine which string drawing code to use.
int use_gui_macvim_draw_string = 1;
static int use_graphical_sign = 0;
// Max number of files to add to MRU in one go (this matches the maximum that
// Cocoa displays in the MRU -- if this changes in Cocoa then update this
// number as well).
static int MMMaxMRU = 10;
// Enabled when files passed on command line should not be added to MRU.
static BOOL MMNoMRU = NO;
static NSString *MMDefaultFontName = @"Menlo Regular";
static int MMDefaultFontSize = 11;
static int MMMinFontSize = 6;
static int MMMaxFontSize = 100;
static BOOL MMShareFindPboard = YES;
static GuiFont gui_macvim_font_with_name(char_u *name);
static int specialKeyToNSKey(int key);
static int vimModMaskToEventModifierFlags(int mods);
NSArray *descriptor_for_menu(vimmenu_T *menu);
vimmenu_T *menu_for_descriptor(NSArray *desc);
// -- Initialization --------------------------------------------------------
void
macvim_early_init()
{
NSBundle *bundle = [NSBundle mainBundle];
if (bundle) {
// Set environment variables $VIM and $VIMRUNTIME
NSString *path = [[bundle resourcePath]
stringByAppendingPathComponent:@"vim"];
char_u *p = mch_getenv((char_u *)"VIM");
if (p == NULL || *p == NUL) {
vim_setenv((char_u*)"VIM", (char_u*)[path UTF8String]);
}
p = mch_getenv((char_u *)"VIMRUNTIME");
if (p == NULL || *p == NUL) {
path = [path stringByAppendingPathComponent:@"runtime"];
vim_setenv((char_u*)"VIMRUNTIME", (char_u*)[path UTF8String]);
}
}
#if 0 // NOTE: setlocale(LC_ALL, "") seems to work after a restart so this is
// not necessary. The locale used depends on what "Region" is set
// inside the "Formats" tab of the "International" System Preferences
// pane.
// Try to ensure that the locale is set to match that used by NSBundle to
// load localized resources. If there is a mismatch e.g. between the
// MacVim menu and other menus, then this code needs to change (nb. the
// MacVim menu is set up inside a nib file so the locale used for it is
// chosen by NSBundle and the other menus are set up by Vim so their locale
// matches whatever we set here).
NSLocale *loc = [NSLocale currentLocale];
if (loc) {
NSString *s = [NSString stringWithFormat:@"%@_%@.UTF-8",
[loc objectForKey:NSLocaleLanguageCode],
[loc objectForKey:NSLocaleCountryCode]];
setlocale(LC_ALL, [s UTF8String]);
fprintf(stderr, "locale=%s\n", [s UTF8String]);
fflush(stderr);
}
#endif
}
/*
* Parse the GUI related command-line arguments. Any arguments used are
* deleted from argv, and *argc is decremented accordingly. This is called
* when vim is started, whether or not the GUI has been started.
* NOTE: This function will be called twice if the Vim process forks.
*/
void
gui_mch_prepare(int *argc, char **argv)
{
// NOTE! Vim expects this method to remove args that it handles from the
// arg list but if the process then forks then these arguments will not
// reach the child process due to the way forking is handled on Mac OS X.
//
// Thus, only delete arguments that imply that no forking is done.
//
// If you add an argument that does not imply no forking, then do not
// delete it from the arg list. Such arguments must be ignored in main.c
// command_line_scan() or Vim will issue an error on startup when that
// argument is used.
int i = 0;
while (i < *argc) {
BOOL delarg = NO;
if (strncmp(argv[i], "--mmwaitforack", 14) == 0) {
// Implies -f (only called from front end)
[[MMBackend sharedInstance] setWaitForAck:YES];
delarg = YES;
}
#ifdef FEAT_NETBEANS_INTG
else if (strncmp(argv[i], "-nb", 3) == 0) {
// TODO: Can this be used without -f? If so, should not del arg.
netbeansArg = argv[i];
delarg = YES;
}
#endif
else if (strncmp(argv[i], "--nomru", 7) == 0) {
// Can be used without -f, do not delete from arg list!
MMNoMRU = YES;
}
if (delarg) {
// NOTE: See comment above about when to delete arguments!
--*argc;
if (*argc > i)
mch_memmove(&argv[i], &argv[i+1], (*argc-i) * sizeof(char*));
} else
++i;
}
}
/* Called directly after forking (even if we didn't fork). */
void
gui_macvim_after_fork_init()
{
ASLInit();
ASLogDebug(@"");
// Restore autosaved rows & columns
CFIndex rows, cols;
Boolean rowsValid, colsValid;
rows = CFPreferencesGetAppIntegerValue((CFStringRef)MMAutosaveRowsKey,
kCFPreferencesCurrentApplication,
&rowsValid);
cols = CFPreferencesGetAppIntegerValue((CFStringRef)MMAutosaveColumnsKey,
kCFPreferencesCurrentApplication,
&colsValid);
if (rowsValid && colsValid
&& (rows > 4 && rows < 1000 && cols > 29 && cols < 4000)) {
gui.num_rows = rows;
gui.num_cols = cols;
} else {
// Use the defaults (typically 80x24), if there are no autosaved rows &
// columns.
gui.num_rows = Rows;
gui.num_cols = Columns;
}
// Check which code path to take for string drawing.
CFIndex val;
Boolean keyValid;
val = CFPreferencesGetAppIntegerValue((CFStringRef)MMRendererKey,
kCFPreferencesCurrentApplication,
&keyValid);
if (!keyValid) {
// If MMRendererKey is not valid in the defaults, it means MacVim uses
// the Core Text Renderer.
keyValid = YES;
val = MMRendererCoreText;
}
if (val != MMRendererDefault && val != MMRendererCoreText) {
// Migrate from the old value to the Core Text Renderer.
val = MMRendererCoreText;
CFPreferencesSetAppValue((CFStringRef)MMRendererKey,
(CFPropertyListRef)[NSNumber numberWithInt:val],
kCFPreferencesCurrentApplication);
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
}
if (keyValid) {
ASLogInfo(@"Use renderer=%ld", val);
use_gui_macvim_draw_string = (val != MMRendererCoreText);
// For now only the Core Text renderer knows how to render graphical
// signs.
use_graphical_sign = (val == MMRendererCoreText);
}
// Check to use the Find Pasteboard.
MMShareFindPboard = CFPreferencesGetAppBooleanValue((CFStringRef)MMShareFindPboardKey,
kCFPreferencesCurrentApplication,
&keyValid);
if (!keyValid) {
// Share text via the Find Pasteboard by default.
MMShareFindPboard = YES;
}
}
/*
* Check if the GUI can be started. Called before gvimrc is sourced.
* Return OK or FAIL.
*/
int
gui_mch_init_check(void)
{
return OK;
}
/*
* Initialise the GUI. Create all the windows, set up all the call-backs etc.
* Returns OK for success, FAIL when the GUI can't be started.
*/
int
gui_mch_init(void)
{
ASLogDebug(@"");
if (![[MMBackend sharedInstance] checkin]) {
// TODO: Kill the process if there is no terminal to fall back on,
// otherwise the process will run outputting to the console.
return FAIL;
}
// Force 'termencoding' to utf-8 (changes to 'tenc' are disallowed in
// 'option.c', so that ':set termencoding=...' is impossible).
set_option_value((char_u *)"termencoding", 0L, (char_u *)"utf-8", 0);
// Set values so that pixels and characters are in one-to-one
// correspondence (assuming all characters have the same dimensions).
gui.scrollbar_width = gui.scrollbar_height = 0;
gui.char_height = 1;
gui.char_width = 1;
gui.char_ascent = 0;
gui_mch_def_colors();
[[MMBackend sharedInstance]
setDefaultColorsBackground:gui.back_pixel foreground:gui.norm_pixel];
[[MMBackend sharedInstance] setBackgroundColor:gui.back_pixel];
[[MMBackend sharedInstance] setForegroundColor:gui.norm_pixel];
// NOTE: If this call is left out the cursor is opaque.
highlight_gui_started();
// Ensure 'linespace' option is passed along to MacVim in case it was set
// in [g]vimrc.
gui_mch_adjust_charheight();
gui_mch_adjust_charwidth();
if (!MMNoMRU && GARGCOUNT > 0) {
// Add files passed on command line to MRU.
NSMutableArray *filenames = [NSMutableArray array];
int i, count = GARGCOUNT > MMMaxMRU ? MMMaxMRU : GARGCOUNT;
for (i = 0; i < count; ++i) {
char_u *fname = GARGLIST[i].ae_fname;
if (!fname) continue;
// Expand to a full file name (including the full path).
char_u *ffname = fix_fname(fname);
if (!ffname) continue;
[filenames addObject:[NSString stringWithVimString:ffname]];
vim_free(ffname);
}
[[MMBackend sharedInstance] addToMRU:filenames];
}
return OK;
}
void
gui_mch_exit(int rc)
{
ASLogDebug(@"rc=%d", rc);
[[MMBackend sharedInstance] exit];
}
/*
* Open the GUI window which was created by a call to gui_mch_init().
*/
int
gui_mch_open(void)
{
return [[MMBackend sharedInstance] openGUIWindow];
}
// -- Updating --------------------------------------------------------------
/*
* Catch up with any queued X events. This may put keyboard input into the
* input buffer, call resize call-backs, trigger timers etc. If there is
* nothing in the X event queue (& no timers pending), then we return
* immediately.
*/
void
gui_mch_update(void)
{
// This function is called extremely often. It is tempting to do nothing
// here to avoid reduced frame-rates but then it would not be possible to
// interrupt Vim by presssing Ctrl-C during lengthy operations (e.g. after
// entering "10gs" it would not be possible to bring Vim out of the 10 s
// sleep prematurely). Furthermore, Vim sometimes goes into a loop waiting
// for keyboard input (e.g. during a "more prompt") where not checking for
// input could cause Vim to lock up indefinitely.
//
// As a compromise we check for new input only every now and then. Note
// that Cmd-. sends SIGINT so it has higher success rate at interrupting
// Vim than Ctrl-C.
static CFAbsoluteTime lastTime = 0;
CFAbsoluteTime nowTime = CFAbsoluteTimeGetCurrent();
if (nowTime - lastTime > 1.0 / 30) {
[[MMBackend sharedInstance] update];
lastTime = nowTime;
}
}
/* Flush any output to the screen */
void
gui_mch_flush(void)
{
// This function is called way too often to be useful as a hint for
// flushing. If we were to flush every time it was called the screen would
// flicker.
}
void
gui_macvim_flush(void)
{
// This function counts how many times it is called and only flushes the
// draw queue if called sufficiently often. The first few times it is
// called it will flush often, but the more it is called the less likely is
// it that anything will be flushed. (The counter resets itself if the
// function isn't called for a second.)
//
// NOTE: Should only be used in loops where it is impossible to know how
// often Vim needs to flush. It was written to handle output from external
// commands (see mch_call_shell() in os_unix.c).
static CFAbsoluteTime lastTime = 0;
static int delay = 1;
static int counter = 0;
static int scrolls = 0;
CFAbsoluteTime nowTime = CFAbsoluteTimeGetCurrent();
CFAbsoluteTime delta = nowTime - lastTime;
if (delta > 1.0)
delay = 1;
// We assume that each call corresponds roughly to one line of output.
// When one page has scrolled by we increase the delay before the next
// flush.
if (++scrolls > gui.num_rows) {
delay <<= 1;
if (delay > 2048)
delay = 2048;
scrolls = 0;
}
if (++counter > delay) {
gui_macvim_force_flush();
counter = 0;
}
lastTime = nowTime;
}
/* Force flush output to MacVim. Do not call this method unless absolutely
* necessary. */
void
gui_macvim_force_flush(void)
{
[[MMBackend sharedInstance] flushQueue:YES];
}
/*
* GUI input routine called by gui_wait_for_chars(). Waits for a character
* from the keyboard.
* wtime == -1 Wait forever.
* wtime == 0 This should never happen.
* wtime > 0 Wait wtime milliseconds for a character.
* Returns OK if a character was found to be available within the given time,
* or FAIL otherwise.
*/
int
gui_mch_wait_for_chars(int wtime)
{
// NOTE! In all likelihood Vim will take a nap when waitForInput: is
// called, so force a flush of the command queue here.
[[MMBackend sharedInstance] flushQueue:YES];
#ifdef MESSAGE_QUEUE
parse_queued_messages();
#endif
return [[MMBackend sharedInstance] waitForInput:wtime];
}
// -- Drawing ---------------------------------------------------------------
/*
* Clear the whole text window.
*/
void
gui_mch_clear_all(void)
{
[[MMBackend sharedInstance] clearAll];
}
/*
* Clear a rectangular region of the screen from text pos (row1, col1) to
* (row2, col2) inclusive.
*/
void
gui_mch_clear_block(int row1, int col1, int row2, int col2)
{
[[MMBackend sharedInstance] clearBlockFromRow:row1 column:col1
toRow:row2 column:col2];
}
/*
* Delete the given number of lines from the given row, scrolling up any
* text further down within the scroll region.
*/
void
gui_mch_delete_lines(int row, int num_lines)
{
[[MMBackend sharedInstance] deleteLinesFromRow:row count:num_lines
scrollBottom:gui.scroll_region_bot
left:gui.scroll_region_left
right:gui.scroll_region_right];
}
void
gui_mch_draw_string(int row, int col, char_u *s, int len, int cells, int flags)
{
#ifdef FEAT_MBYTE
char_u *conv_str = NULL;
if (output_conv.vc_type != CONV_NONE) {
conv_str = string_convert(&output_conv, s, &len);
if (conv_str)
s = conv_str;
}
#endif
[[MMBackend sharedInstance] drawString:s
length:len
row:row
column:col
cells:cells
flags:flags];
#ifdef FEAT_MBYTE
if (conv_str)
vim_free(conv_str);
#endif
}
int
gui_macvim_draw_string(int row, int col, char_u *s, int len, int flags)
{
int c, cn, cl, i;
int start = 0;
int endcol = col;
int startcol = col;
BOOL wide = NO;
MMBackend *backend = [MMBackend sharedInstance];
#ifdef FEAT_MBYTE
char_u *conv_str = NULL;
if (output_conv.vc_type != CONV_NONE) {
conv_str = string_convert(&output_conv, s, &len);
if (conv_str)
s = conv_str;
}
#endif
// Loop over each character and output text when it changes from normal to
// wide and vice versa.
for (i = 0; i < len; i += cl) {
c = utf_ptr2char(s + i);
cn = utf_char2cells(c);
cl = utf_ptr2len(s + i);
if (0 == cl)
len = i; // len must be wrong (shouldn't happen)
if (!utf_iscomposing(c)) {
if ((cn > 1 && !wide) || (cn <= 1 && wide)) {
// Changed from normal to wide or vice versa.
[backend drawString:(s+start) length:i-start
row:row column:startcol
cells:endcol-startcol
flags:(wide ? flags|DRAW_WIDE : flags)];
start = i;
startcol = endcol;
}
wide = cn > 1;
endcol += cn;
}
}
// Output remaining characters.
[backend drawString:(s+start) length:len-start
row:row column:startcol cells:endcol-startcol
flags:(wide ? flags|DRAW_WIDE : flags)];
#ifdef FEAT_MBYTE
if (conv_str)
vim_free(conv_str);
#endif
return endcol - col;
}
/*
* Insert the given number of lines before the given row, scrolling down any
* following text within the scroll region.
*/
void
gui_mch_insert_lines(int row, int num_lines)
{
[[MMBackend sharedInstance] insertLinesFromRow:row count:num_lines
scrollBottom:gui.scroll_region_bot
left:gui.scroll_region_left
right:gui.scroll_region_right];
}
/*
* Set the current text foreground color.
*/
void
gui_mch_set_fg_color(guicolor_T color)
{
[[MMBackend sharedInstance] setForegroundColor:color];
}
/*
* Set the current text background color.
*/
void
gui_mch_set_bg_color(guicolor_T color)
{
[[MMBackend sharedInstance] setBackgroundColor:color];
}
/*
* Set the current text special color (used for underlines).
*/
void
gui_mch_set_sp_color(guicolor_T color)
{
[[MMBackend sharedInstance] setSpecialColor:color];
}
/*
* Set default colors.
*/
void
gui_mch_def_colors()
{
MMBackend *backend = [MMBackend sharedInstance];
// The default colors are taken from system values
gui.def_norm_pixel = gui.norm_pixel =
[backend lookupColorWithKey:@"MacTextColor"];
gui.def_back_pixel = gui.back_pixel =
[backend lookupColorWithKey:@"MacTextBackgroundColor"];
}
/*
* Called when the foreground or background color has been changed.
*/
void
gui_mch_new_colors(void)
{
gui.def_back_pixel = gui.back_pixel;
gui.def_norm_pixel = gui.norm_pixel;
ASLogDebug(@"back=%ld norm=%ld", gui.def_back_pixel, gui.def_norm_pixel);
[[MMBackend sharedInstance]
setDefaultColorsBackground:gui.def_back_pixel
foreground:gui.def_norm_pixel];
}
/*
* Invert a rectangle from row r, column c, for nr rows and nc columns.
*/
void
gui_mch_invert_rectangle(int r, int c, int nr, int nc, int invert)
{
[[MMBackend sharedInstance] drawInvertedRectAtRow:r column:c numRows:nr
numColumns:nc invert:invert];
}
// -- Tabline ---------------------------------------------------------------
/*
* Set the current tab to "nr". First tab is 1.
*/
void
gui_mch_set_curtab(int nr)
{
[[MMBackend sharedInstance] selectTab:nr];
}
/*
* Return TRUE when tabline is displayed.
*/
int
gui_mch_showing_tabline(void)
{
return [[MMBackend sharedInstance] tabBarVisible];
}
/*
* Update the labels of the tabline.
*/
void
gui_mch_update_tabline(void)
{
[[MMBackend sharedInstance] updateTabBar];
}
/*
* Show or hide the tabline.
*/
void
gui_mch_show_tabline(int showit)
{
[[MMBackend sharedInstance] showTabBar:showit];
}
// -- Menu ------------------------------------------------------------------
/*
* A menu descriptor represents the "address" of a menu as an array of strings.
* E.g. the menu "File->Close" has descriptor { "File", "Close" }.
*/
NSArray *
descriptor_for_menu(vimmenu_T *menu)
{
if (!menu) return nil;
NSMutableArray *desc = [NSMutableArray array];
while (menu) {
NSString *name = [NSString stringWithVimString:menu->dname];
[desc insertObject:name atIndex:0];
menu = menu->parent;
}
return desc;
}
vimmenu_T *
menu_for_descriptor(NSArray *desc)
{
if (!(desc && [desc count] > 0)) return NULL;
vimmenu_T *menu = root_menu;
int i, count = [desc count];
for (i = 0; i < count; ++i) {
NSString *component = [desc objectAtIndex:i];
while (menu) {
NSString *name = [NSString stringWithVimString:menu->dname];
if ([component isEqual:name]) {
if (i+1 == count)
return menu; // Matched all components, so return menu
menu = menu->children;
break;
}
menu = menu->next;
}
}
return NULL;
}
/*
* Add a submenu to the menu bar, toolbar, or a popup menu.
*/
void
gui_mch_add_menu(vimmenu_T *menu, int idx)
{
NSArray *desc = descriptor_for_menu(menu);
[[MMBackend sharedInstance] queueMessage:AddMenuMsgID properties:
[NSDictionary dictionaryWithObjectsAndKeys:
desc, @"descriptor",
[NSNumber numberWithInt:idx], @"index",
nil]];
}
// Taken from gui_gtk.c (slightly modified)
static int
lookup_menu_iconfile(char_u *iconfile, char_u *dest)
{
expand_env(iconfile, dest, MAXPATHL);
if (mch_isFullName(dest))
return vim_fexists(dest);
static const char suffixes[][4] = {"png", "bmp"};
char_u buf[MAXPATHL];
unsigned int i;
for (i = 0; i < sizeof(suffixes)/sizeof(suffixes[0]); ++i)
if (gui_find_bitmap(dest, buf, (char *)suffixes[i]) == OK) {
STRCPY(dest, buf);
return TRUE;
}
return FALSE;
}
/*
* Add a menu item to a menu
*/
void
gui_mch_add_menu_item(vimmenu_T *menu, int idx)
{
char_u *tip = menu->strings[MENU_INDEX_TIP]
? menu->strings[MENU_INDEX_TIP] : menu->actext;
NSArray *desc = descriptor_for_menu(menu);
NSString *keyEquivalent = menu->mac_key
? [NSString stringWithFormat:@"%C",
(unsigned short)specialKeyToNSKey(menu->mac_key)]
: [NSString string];
int modifierMask = vimModMaskToEventModifierFlags(menu->mac_mods);
char_u *icon = NULL;
if (menu_is_toolbar(menu->parent->name)) {
char_u fname[MAXPATHL];
// Try to use the icon=.. argument
if (menu->iconfile && lookup_menu_iconfile(menu->iconfile, fname))
icon = fname;
// If not found and not builtin specified try using the menu name
if (!icon && !menu->icon_builtin
&& lookup_menu_iconfile(menu->name, fname))
icon = fname;
// Still no icon found, try using a builtin icon. (If this also fails,
// then a warning icon will be displayed).
if (!icon)
icon = lookup_toolbar_item(menu->iconidx);
}
[[MMBackend sharedInstance] queueMessage:AddMenuItemMsgID properties:
[NSDictionary dictionaryWithObjectsAndKeys:
desc, @"descriptor",
[NSNumber numberWithInt:idx], @"index",
[NSString stringWithVimString:tip], @"tip",
[NSString stringWithVimString:icon], @"icon",
keyEquivalent, @"keyEquivalent",
[NSNumber numberWithInt:modifierMask], @"modifierMask",
[NSString stringWithVimString:menu->mac_action], @"action",
[NSNumber numberWithBool:menu->mac_alternate], @"isAlternate",
nil]];
}
/*
* Destroy the machine specific menu widget.
*/
void
gui_mch_destroy_menu(vimmenu_T *menu)
{
NSArray *desc = descriptor_for_menu(menu);
[[MMBackend sharedInstance] queueMessage:RemoveMenuItemMsgID properties:
[NSDictionary dictionaryWithObject:desc forKey:@"descriptor"]];
}
/*
* Make a menu either grey or not grey.
*/
void
gui_mch_menu_grey(vimmenu_T *menu, int grey)
{
/* Only update menu if the 'grey' state has changed to avoid having to pass
* lots of unnecessary data to MacVim. (Skipping this test makes MacVim
* pause noticably on mode changes. */
NSArray *desc = descriptor_for_menu(menu);
if (menu->was_grey == grey)
return;
menu->was_grey = grey;
[[MMBackend sharedInstance] queueMessage:EnableMenuItemMsgID properties:
[NSDictionary dictionaryWithObjectsAndKeys:
desc, @"descriptor",
[NSNumber numberWithInt:!grey], @"enable",
nil]];
}
/*
* Make menu item hidden or not hidden
*/
void
gui_mch_menu_hidden(vimmenu_T *menu, int hidden)
{
// HACK! There is no (obvious) way to hide a menu item, so simply
// enable/disable it instead.
gui_mch_menu_grey(menu, hidden);
}
/*
* This is called when user right clicks.
*/
void
gui_mch_show_popupmenu(vimmenu_T *menu)
{
NSArray *desc = descriptor_for_menu(menu);
[[MMBackend sharedInstance] queueMessage:ShowPopupMenuMsgID properties:
[NSDictionary dictionaryWithObject:desc forKey:@"descriptor"]];
}
/*
* This is called when a :popup command is executed.
*/
void
gui_make_popup(char_u *path_name, int mouse_pos)
{
vimmenu_T *menu = gui_find_menu(path_name);
if (!(menu && menu->children)) return;
NSArray *desc = descriptor_for_menu(menu);
NSDictionary *p = (mouse_pos || NULL == curwin)
? [NSDictionary dictionaryWithObject:desc forKey:@"descriptor"]
: [NSDictionary dictionaryWithObjectsAndKeys:
desc, @"descriptor",
[NSNumber numberWithInt:curwin->w_wrow], @"row",
[NSNumber numberWithInt:curwin->w_wcol], @"column",
nil];
[[MMBackend sharedInstance] queueMessage:ShowPopupMenuMsgID properties:p];
}
/*
* This is called after setting all the menus to grey/hidden or not.
*/
void
gui_mch_draw_menubar(void)
{
// The (main) menu draws itself in Mac OS X.
}
void
gui_mch_enable_menu(int flag)
{
// The (main) menu is always enabled in Mac OS X.
}
#if 0
void
gui_mch_set_menu_pos(int x, int y, int w, int h)
{
// The (main) menu cannot be moved in Mac OS X.
}
#endif
void
gui_mch_show_toolbar(int showit)
{
int flags = 0;
if (toolbar_flags & TOOLBAR_TEXT) flags |= ToolbarLabelFlag;
if (toolbar_flags & TOOLBAR_ICONS) flags |= ToolbarIconFlag;
if (tbis_flags & (TBIS_MEDIUM|TBIS_LARGE)) flags |= ToolbarSizeRegularFlag;
[[MMBackend sharedInstance] showToolbar:showit flags:flags];
}
// -- Fonts -----------------------------------------------------------------
/*
* If a font is not going to be used, free its structure.
*/
void
gui_mch_free_font(font)
GuiFont font;
{
if (font != NOFONT) {
ASLogDebug(@"font=%p", font);
[(id)font release];
}
}
GuiFont
gui_mch_retain_font(GuiFont font)
{
return (GuiFont)[(id)font retain];
}
/*
* Get a font structure for highlighting.
*/
GuiFont
gui_mch_get_font(char_u *name, int giveErrorIfMissing)
{
ASLogDebug(@"name='%s' giveErrorIfMissing=%d", name, giveErrorIfMissing);
GuiFont font = gui_macvim_font_with_name(name);
if (font != NOFONT)
return font;
if (giveErrorIfMissing)
EMSG2(_(e_font), name);
return NOFONT;
}
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* Return the name of font "font" in allocated memory.
*/
char_u *
gui_mch_get_fontname(GuiFont font, char_u *name)
{
return font ? [(NSString *)font vimStringSave]
: (name ? vim_strsave(name) : NULL);
}
#endif
/*
* Initialise vim to use the font with the given name. Return FAIL if the font
* could not be loaded, OK otherwise.
*/
int
gui_mch_init_font(char_u *font_name, int fontset)
{
ASLogDebug(@"font_name='%s' fontset=%d", font_name, fontset);
if (font_name && STRCMP(font_name, "*") == 0) {
// :set gfn=* shows the font panel.
do_cmdline_cmd((char_u*)":macaction orderFrontFontPanel:");
return FAIL;
}
GuiFont font = gui_macvim_font_with_name(font_name);
if (font == NOFONT)
return FAIL;
gui_mch_free_font(gui.norm_font);
gui.norm_font = font;
// NOTE: MacVim keeps separate track of the normal and wide fonts.
// Unless the user changes 'guifontwide' manually, they are based on
// the same (normal) font. Also note that each time the normal font is
// set, the advancement may change so the wide font needs to be updated
// as well (so that it is always twice the width of the normal font).
[[MMBackend sharedInstance] setFont:font wide:NO];
[[MMBackend sharedInstance] setFont:(NOFONT != gui.wide_font ? gui.wide_font
: font)
wide:YES];
return OK;
}
/*
* Set the current text font.
*/
void
gui_mch_set_font(GuiFont font)
{
// Font selection is done inside MacVim...nothing here to do.
}
/*
* Return GuiFont in allocated memory. The caller must free it using
* gui_mch_free_font().
*/
GuiFont
gui_macvim_font_with_name(char_u *name)
{
if (!name)
return (GuiFont)[[NSString alloc] initWithFormat:@"%@:h%d",
MMDefaultFontName, MMDefaultFontSize];
NSString *fontName = [NSString stringWithVimString:name];
int size = MMDefaultFontSize;
BOOL parseFailed = NO;
NSArray *components = [fontName componentsSeparatedByString:@":"];
if ([components count] == 2) {
NSString *sizeString = [components lastObject];
if ([sizeString length] > 0
&& [sizeString characterAtIndex:0] == 'h') {
sizeString = [sizeString substringFromIndex:1];
if ([sizeString length] > 0) {
size = (int)round([sizeString floatValue]);
fontName = [components objectAtIndex:0];
}
} else {
parseFailed = YES;
}
} else if ([components count] > 2) {
parseFailed = YES;
}
if (!parseFailed) {
// Replace underscores with spaces.
fontName = [[fontName componentsSeparatedByString:@"_"]
componentsJoinedByString:@" "];
}
if (!parseFailed && [fontName length] > 0) {
if (size < MMMinFontSize) size = MMMinFontSize;
if (size > MMMaxFontSize) size = MMMaxFontSize;
// If the default font is requested we don't need to check if NSFont
// can load it. Otherwise we ask NSFont if it can load it.
if ([fontName isEqualToString:MMDefaultFontName]
|| [NSFont fontWithName:fontName size:size])
return [[NSString alloc] initWithFormat:@"%@:h%d", fontName, size];
}
return NOFONT;
}
// -- Scrollbars ------------------------------------------------------------
// NOTE: Even though scrollbar identifiers are 'long' we tacitly assume that
// they only use 32 bits (in particular when compiling for 64 bit). This is
// justified since identifiers are generated from a 32 bit counter in
// gui_create_scrollbar(). However if that code changes we may be in trouble
// (if ever that many scrollbars are allocated...). The reason behind this is
// that we pass scrollbar identifers over process boundaries so the width of
// the variable needs to be fixed (and why fix at 64 bit when only 32 are
// really used?).
void
gui_mch_create_scrollbar(
scrollbar_T *sb,
int orient) /* SBAR_VERT or SBAR_HORIZ */
{
[[MMBackend sharedInstance]
createScrollbarWithIdentifier:(int32_t)sb->ident type:sb->type];
}
void
gui_mch_destroy_scrollbar(scrollbar_T *sb)
{
[[MMBackend sharedInstance]
destroyScrollbarWithIdentifier:(int32_t)sb->ident];
}
void
gui_mch_enable_scrollbar(
scrollbar_T *sb,
int flag)
{
[[MMBackend sharedInstance]
showScrollbarWithIdentifier:(int32_t)sb->ident state:flag];
}
void
gui_mch_set_scrollbar_pos(
scrollbar_T *sb,
int x,
int y,
int w,
int h)
{
int pos = y;
int len = h;
if (SBAR_BOTTOM == sb->type) {
pos = x;
len = w;
}
[[MMBackend sharedInstance]
setScrollbarPosition:pos length:len identifier:(int32_t)sb->ident];
}
void
gui_mch_set_scrollbar_thumb(
scrollbar_T *sb,
long val,
long size,
long max)
{
[[MMBackend sharedInstance]
setScrollbarThumbValue:val
size:size
max:max
identifier:(int32_t)sb->ident];
}
// -- Cursor ----------------------------------------------------------------
/*
* Draw a cursor without focus.
*/
void
gui_mch_draw_hollow_cursor(guicolor_T color)
{
return [[MMBackend sharedInstance]
drawCursorAtRow:gui.row column:gui.col shape:MMInsertionPointHollow
fraction:100 color:color];
}
/*
* Draw part of a cursor, only w pixels wide, and h pixels high.
*/
void
gui_mch_draw_part_cursor(int w, int h, guicolor_T color)
{
// HACK! 'w' and 'h' are always 1 since we do not tell Vim about the exact
// font dimensions. Thus these parameters are useless. Instead we look at
// the shape_table to determine the shape and size of the cursor (just like
// gui_update_cursor() does).
int idx = get_shape_idx(FALSE);
int shape = MMInsertionPointBlock;
switch (shape_table[idx].shape) {
case SHAPE_HOR:
shape = MMInsertionPointHorizontal;
break;
case SHAPE_VER:
shape =
#ifdef FEAT_RIGHTLEFT
// If 'rl' is set the insert mode cursor may be drawn on
// the right-hand side of a text cell.
CURSOR_BAR_RIGHT ? MMInsertionPointVerticalRight :
#endif
MMInsertionPointVertical;
break;
}
return [[MMBackend sharedInstance]
drawCursorAtRow:gui.row column:gui.col shape:shape
fraction:shape_table[idx].percentage color:color];
}
int
gui_mch_is_blinking(void)
{
return FALSE;
}
int
gui_mch_is_blink_off(void)
{
return FALSE;
}
/*
* Cursor blink functions.
*
* This is a simple state machine:
* BLINK_NONE not blinking at all
* BLINK_OFF blinking, cursor is not shown
* BLINK_ON blinking, cursor is shown
*/
void
gui_mch_set_blinking(long wait, long on, long off)
{
[[MMBackend sharedInstance] setBlinkWait:wait on:on off:off];
}
/*
* Start the cursor blinking. If it was already blinking, this restarts the
* waiting time and shows the cursor.
*/
void
gui_mch_start_blink(void)
{
[[MMBackend sharedInstance] startBlink];
}
/*
* Stop the cursor blinking. Show the cursor if it wasn't shown.
*/
void
gui_mch_stop_blink(void)
{
[[MMBackend sharedInstance] stopBlink];
}
// -- Mouse -----------------------------------------------------------------
/*
* Get current mouse coordinates in text window.
*/
void
gui_mch_getmouse(int *x, int *y)
{
ASLogInfo(@"Not implemented!");
}
void
gui_mch_setmouse(int x, int y)
{
ASLogInfo(@"Not implemented!");
}
void
mch_set_mouse_shape(int shape)
{
[[MMBackend sharedInstance] setMouseShape:shape];
}
// -- Input Method ----------------------------------------------------------
#if defined(USE_IM_CONTROL)
void
im_set_position(int row, int col)
{
// The pre-edit area is a popup window which is displayed by MMTextView.
[[MMBackend sharedInstance] setPreEditRow:row column:col];
}
void
im_set_control(int enable)
{
// Tell frontend whether it should notify us when the input method changes
// or not (called when 'imd' is toggled).
int msgid = enable ? EnableImControlMsgID : DisableImControlMsgID;
[[MMBackend sharedInstance] queueMessage:msgid properties:nil];
}
void
im_set_active(int active)
{
// Tell frontend to enable/disable IM (called e.g. when the mode changes).
if (!p_imdisable) {
int msgid = active ? ActivateKeyScriptMsgID : DeactivateKeyScriptMsgID;
[[MMBackend sharedInstance] setImState:active];
[[MMBackend sharedInstance] queueMessage:msgid properties:nil];
}
}
int
im_get_status(void)
{
return [[MMBackend sharedInstance] imState];
}
#endif // defined(USE_IM_CONTROL)
// -- Find & Replace dialog -------------------------------------------------
#ifdef FIND_REPLACE_DIALOG
static void
macvim_find_and_replace(char_u *arg, BOOL replace)
{
// TODO: Specialized dialog for find without replace?
int wholeWord = FALSE;
int matchCase = !p_ic;
char_u *text = get_find_dialog_text(arg, &wholeWord, &matchCase);
int flags = 0;
if (wholeWord) flags |= FRD_WHOLE_WORD;
if (matchCase) flags |= FRD_MATCH_CASE;
NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithVimString:text], @"text",
[NSNumber numberWithInt:flags], @"flags",
nil];
[[MMBackend sharedInstance] queueMessage:ShowFindReplaceDialogMsgID
properties:args];
}
void
gui_mch_find_dialog(exarg_T *eap)
{
macvim_find_and_replace(eap->arg, NO);
}
void
gui_mch_replace_dialog(exarg_T *eap)
{
macvim_find_and_replace(eap->arg, YES);
}
#endif // FIND_REPLACE_DIALOG
// -- Unsorted --------------------------------------------------------------
void
ex_macaction(eap)
exarg_T *eap;
{
if (!gui.in_use) {
EMSG(_("E???: Command only available in GUI mode"));
return;
}
char_u *arg = eap->arg;
#ifdef FEAT_MBYTE
arg = CONVERT_TO_UTF8(arg);
#endif
NSDictionary *actionDict = [[MMBackend sharedInstance] actionDict];
NSString *name = [NSString stringWithUTF8String:(char*)arg];
if (actionDict && [actionDict objectForKey:name] != nil) {
[[MMBackend sharedInstance] executeActionWithName:name];
} else {
EMSG2(_("E???: Invalid action: %s"), eap->arg);
}
#ifdef FEAT_MBYTE
arg = CONVERT_TO_UTF8(arg);
#endif
}
/*
* Adjust gui.char_height (after 'linespace' was changed).
*/
int
gui_mch_adjust_charheight(void)
{
[[MMBackend sharedInstance] adjustLinespace:p_linespace];
return OK;
}
/*
* Adjust gui.char_width (after 'columnspace' was changed).
*/
int
gui_mch_adjust_charwidth(void)
{
[[MMBackend sharedInstance] adjustColumnspace:p_columnspace];
return OK;
}
void
gui_mch_beep(void)
{
NSBeep();
}
#ifdef FEAT_BROWSE
/*
* Pop open a file browser and return the file selected, in allocated memory,
* or NULL if Cancel is hit.
* saving - TRUE if the file will be saved to, FALSE if it will be opened.
* title - Title message for the file browser dialog.
* dflt - Default name of file.
* ext - Default extension to be added to files without extensions.
* initdir - directory in which to open the browser (NULL = current dir)
* filter - Filter for matched files to choose from.
* Has a format like this:
* "C Files (*.c)\0*.c\0"
* "All Files\0*.*\0\0"
* If these two strings were concatenated, then a choice of two file
* filters will be selectable to the user. Then only matching files will
* be shown in the browser. If NULL, the default allows all files.
*
* *NOTE* - the filter string must be terminated with TWO nulls.
*/
char_u *
gui_mch_browse(
int saving,
char_u *title,
char_u *dflt,
char_u *ext,
char_u *initdir,
char_u *filter)
{
ASLogDebug(@"saving=%d title='%s' dflt='%s' ext='%s' initdir='%s' "
"filter='%s'", saving, title, dflt, ext, initdir, filter);
// Ensure no data is on the output queue before presenting the dialog.
gui_macvim_force_flush();
NSMutableDictionary *attr = [NSMutableDictionary
dictionaryWithObject:[NSNumber numberWithBool:saving]
forKey:@"saving"];
if (initdir)
[attr setObject:[NSString stringWithVimString:initdir] forKey:@"dir"];
char_u *s = (char_u*)[[MMBackend sharedInstance]
browseForFileWithAttributes:attr];
return s;
}
/*
* Put up a directory selector
* Returns the selected name in allocated memory, or NULL for Cancel.
* title title for the window (UNUSED)
* initdir initial directory, NULL for current dir
*/
char_u *
gui_mch_browsedir(
char_u *title,
char_u *initdir)
{
ASLogDebug(@"title='%s' initdir='%s'", title, initdir);
// Ensure no data is on the output queue before presenting the dialog.
gui_macvim_force_flush();
NSMutableDictionary *attr = [NSMutableDictionary
dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:@"browsedir"];
if (initdir)
[attr setObject:[NSString stringWithVimString:initdir] forKey:@"dir"];
char_u *s = (char_u*)[[MMBackend sharedInstance]
browseForFileWithAttributes:attr];
return s;
}
#endif /* FEAT_BROWSE */
int
gui_mch_dialog(
int type,
char_u *title,
char_u *message,
char_u *buttons,
int dfltbutton,
char_u *textfield,
int ex_cmd) // UNUSED
{
ASLogDebug(@"type=%d title='%s' message='%s' buttons='%s' dfltbutton=%d "
"textfield='%s'", type, title, message, buttons, dfltbutton,
textfield);
// Ensure no data is on the output queue before presenting the dialog.
gui_macvim_force_flush();
int style = NSAlertStyleInformational;
if (VIM_WARNING == type) style = NSAlertStyleWarning;
else if (VIM_ERROR == type) style = NSAlertStyleCritical;
NSMutableDictionary *attr = [NSMutableDictionary
dictionaryWithObject:[NSNumber numberWithInt:style]
forKey:@"alertStyle"];
if (buttons) {
// 'buttons' is a string of '\n'-separated button titles
NSString *string = [NSString stringWithVimString:buttons];
NSArray *array = [string componentsSeparatedByString:@"\n"];
[attr setObject:array forKey:@"buttonTitles"];
}
NSString *messageText = nil;
if (title)
messageText = [NSString stringWithVimString:title];
if (message) {
NSString *informativeText = [NSString stringWithVimString:message];
if (!messageText) {
// HACK! If there is a '\n\n' or '\n' sequence in the message, then
// make the part up to there into the title. We only do this
// because Vim has lots of dialogs without a title and they look
// ugly that way.
// TODO: Fix the actual dialog texts.
NSRange eolRange = [informativeText rangeOfString:@"\n\n"];
if (NSNotFound == eolRange.location)
eolRange = [informativeText rangeOfString:@"\n"];
if (NSNotFound != eolRange.location) {
messageText = [informativeText substringToIndex:
eolRange.location];
informativeText = [informativeText substringFromIndex:
NSMaxRange(eolRange)];
}
}
[attr setObject:informativeText forKey:@"informativeText"];
}
if (messageText)
[attr setObject:messageText forKey:@"messageText"];
if (textfield) {
NSString *string = [NSString stringWithVimString:textfield];
[attr setObject:string forKey:@"textFieldString"];
}
return [[MMBackend sharedInstance] showDialogWithAttributes:attr
textField:(char*)textfield];
}
void
gui_mch_flash(int msec)
{
}
/*
* Return the Pixel value (color) for the given color name. This routine was
* pretty much taken from example code in the Silicon Graphics OSF/Motif
* Programmer's Guide.
* Return INVALCOLOR when failed.
*/
guicolor_T
gui_mch_get_color(char_u *name)
{
if (![MMBackend sharedInstance])
return INVALCOLOR;
#ifdef FEAT_MBYTE
name = CONVERT_TO_UTF8(name);
#endif
NSString *key = [NSString stringWithUTF8String:(char*)name];
guicolor_T col = [[MMBackend sharedInstance] lookupColorWithKey:key];
#ifdef FEAT_MBYTE
CONVERT_TO_UTF8_FREE(name);
#endif
return col;
}
/*
* Return the RGB value of a pixel as long.
*/
guicolor_T
gui_mch_get_rgb(guicolor_T pixel)
{
// This is only implemented so that vim can guess the correct value for
// 'background' (which otherwise defaults to 'dark'); it is not used for
// anything else (as far as I know).
// The implementation is simple since colors are stored in an int as
// "rrggbb".
return pixel;
}
guicolor_T
gui_mch_get_rgb_color(int r, int g, int b)
{
return gui_get_rgb_color_cmn(r, g, b);
}
/*
* Get the screen dimensions.
* Allow 10 pixels for horizontal borders, 40 for vertical borders.
* Is there no way to find out how wide the borders really are?
* TODO: Add live udate of those value on suspend/resume.
*/
void
gui_mch_get_screen_dimensions(int *screen_w, int *screen_h)
{
ASLogDebug(@"Columns=%ld Rows=%ld", Columns, Rows);
*screen_w = Columns;
*screen_h = Rows;
}
/*
* Return OK if the key with the termcap name "name" is supported.
*/
int
gui_mch_haskey(char_u *name)
{
return [[MMBackend sharedInstance] hasSpecialKeyWithValue:name];
}
/*
* Iconify the GUI window.
*/
void
gui_mch_iconify(void)
{
}
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* Bring the Vim window to the foreground.
*/
void
gui_mch_set_foreground(void)
{
[[MMBackend sharedInstance] activate];
}
#endif
void
gui_mch_set_shellsize(
int width,
int height,
int min_width,
int min_height,
int base_width,
int base_height,
int direction)
{
ASLogDebug(@"width=%d height=%d min_width=%d min_height=%d base_width=%d "
"base_height=%d direction=%d", width, height, min_width,
min_height, base_width, base_height, direction);
[[MMBackend sharedInstance] setRows:height columns:width];
}
/*
* Set the position of the top left corner of the window to the given
* coordinates.
*/
void
gui_mch_set_winpos(int x, int y)
{
[[MMBackend sharedInstance] setWindowPositionX:x Y:y];
}
/*
* Get the position of the top left corner of the window.
*/
int
gui_mch_get_winpos(int *x, int *y)
{
[[MMBackend sharedInstance] getWindowPositionX:x Y:y];
return OK;
}
void
gui_mch_set_text_area_pos(int x, int y, int w, int h)
{
}
#ifdef FEAT_TITLE
/*
* Set the window title and icon.
* (The icon is not taken care of).
*/
void
gui_mch_settitle(char_u *title, char_u *icon)
{
ASLogDebug(@"title='%s' icon='%s'", title, icon);
#ifdef FEAT_MBYTE
title = CONVERT_TO_UTF8(title);
#endif
MMBackend *backend = [MMBackend sharedInstance];
[backend setWindowTitle:(char*)title];
// TODO: Convert filename to UTF-8?
if (curbuf)
[backend setDocumentFilename:(char*)curbuf->b_ffname];
#ifdef FEAT_MBYTE
CONVERT_TO_UTF8_FREE(title);
#endif
}
#endif
void
gui_mch_toggle_tearoffs(int enable)
{
}
void
gui_mch_enter_fullscreen(int fuoptions_flags, guicolor_T bg)
{
[[MMBackend sharedInstance] enterFullScreen:fuoptions_flags background:bg];
}
void
gui_mch_leave_fullscreen()
{
[[MMBackend sharedInstance] leaveFullScreen];
}
void
gui_mch_fuopt_update()
{
if (!gui.in_use)
return;
guicolor_T fg, bg;
if (fuoptions_flags & FUOPT_BGCOLOR_HLGROUP) {
syn_id2colors(fuoptions_bgcolor, &fg, &bg);
} else {
bg = fuoptions_bgcolor;
}
[[MMBackend sharedInstance] setFullScreenBackgroundColor:bg];
}
void
gui_macvim_update_modified_flag()
{
[[MMBackend sharedInstance] updateModifiedFlag];
}
/*
* Add search pattern 'pat' to the OS X find pasteboard. This allows other
* apps access the last pattern searched for (hitting <D-g> in another app will
* initiate a search for the same pattern).
*/
void
gui_macvim_add_to_find_pboard(char_u *pat)
{
if (!pat) return;
#ifdef FEAT_MBYTE
pat = CONVERT_TO_UTF8(pat);
#endif
NSString *s = [NSString stringWithUTF8String:(char*)pat];
#ifdef FEAT_MBYTE
CONVERT_TO_UTF8_FREE(pat);
#endif
if (!s) return;
NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSFindPboard];
NSArray *supportedTypes = [NSArray arrayWithObjects:VimFindPboardType,
NSStringPboardType, nil];
[pb declareTypes:supportedTypes owner:nil];
// Put two entries on the Find pasteboard:
// * the pattern Vim uses
// * same as above but with some backslash escaped characters removed
// The second entry will be used by other applications when taking entries
// off the Find pasteboard, whereas MacVim will use the first if present.
[pb setString:s forType:VimFindPboardType];
if (MMShareFindPboard)
[pb setString:[s stringByRemovingFindPatterns] forType:NSStringPboardType];
}
void
gui_macvim_set_antialias(int antialias)
{
[[MMBackend sharedInstance] setAntialias:antialias];
}
void
gui_macvim_set_ligatures(int ligatures)
{
[[MMBackend sharedInstance] setLigatures:ligatures];
}
void
gui_macvim_set_thinstrokes(int thinStrokes)
{
[[MMBackend sharedInstance] setThinStrokes:thinStrokes];
}
void
gui_macvim_wait_for_startup()
{
MMBackend *backend = [MMBackend sharedInstance];
if ([backend waitForAck])
[backend waitForConnectionAcknowledgement];
}
void gui_macvim_get_window_layout(int *count, int *layout)
{
if (!(count && layout)) return;
// NOTE: Only set 'layout' if the backend has requested a != 0 layout, else
// any command line arguments (-p/-o) would be ignored.
int window_layout = [[MMBackend sharedInstance] initialWindowLayout];
if (window_layout > 0 && window_layout < 4) {
// The window_layout numbers must match the WIN_* defines in main.c.
*count = 0;
*layout = window_layout;
}
}
void *gui_macvim_new_autoreleasepool()
{
return (void *)[[NSAutoreleasePool alloc] init];
}
void gui_macvim_release_autoreleasepool(void *pool)
{
[(id)pool release];
}
// -- Client/Server ---------------------------------------------------------
#ifdef MAC_CLIENTSERVER
//
// NOTE: Client/Server is only fully supported with a GUI. Theoretically it
// would be possible to make the server code work with terminal Vim, but it
// would require that a run-loop is set up and checked. This should not be
// difficult to implement, simply call gui_mch_update() at opportune moments
// and it will take care of the run-loop. Another (bigger) problem with
// supporting servers in terminal mode is that the server listing code talks to
// MacVim (the GUI) to figure out which servers are running.
//
/*
* Register connection with 'name'. The actual connection is named something
* like 'org.vim.MacVim.VIM3', whereas the server is called 'VIM3'.
*/
void
serverRegisterName(char_u *name)
{
#ifdef FEAT_MBYTE
name = CONVERT_TO_UTF8(name);
#endif
NSString *svrName = [NSString stringWithUTF8String:(char*)name];
[[MMBackend sharedInstance] registerServerWithName:svrName];
#ifdef FEAT_MBYTE
CONVERT_TO_UTF8_FREE(name);
#endif
}
/*
* Send to an instance of Vim.
* Returns 0 for OK, negative for an error.
*/
int
serverSendToVim(char_u *name, char_u *cmd, char_u **result,
int *port, int asExpr, int timeout, int silent)
{
#ifdef FEAT_MBYTE
name = CONVERT_TO_UTF8(name);
cmd = CONVERT_TO_UTF8(cmd);
#endif
BOOL ok = [[MMBackend sharedInstance]
sendToServer:[NSString stringWithUTF8String:(char*)name]
string:[NSString stringWithUTF8String:(char*)cmd]
reply:result
port:port
expression:asExpr
silent:silent];
#ifdef FEAT_MBYTE
CONVERT_TO_UTF8_FREE(name);
CONVERT_TO_UTF8_FREE(cmd);
#endif
return ok ? 0 : -1;
}
/*
* Ask MacVim for the names of all Vim servers.
*/
char_u *
serverGetVimNames(void)
{
char_u *names = NULL;
NSArray *list = [[MMBackend sharedInstance] serverList];
if (list) {
NSString *string = [list componentsJoinedByString:@"\n"];
names = [string vimStringSave];
}
return names;
}
/*
* 'str' is a hex int representing the send port of the connection.
*/
int
serverStrToPort(char_u *str)
{
int port = 0;
sscanf((char *)str, "0x%x", &port);
if (!port)
EMSG2(_("E573: Invalid server id used: %s"), str);
return port;
}
/*
* Check for replies from server with send port 'port'.
* Return TRUE and a non-malloc'ed string if there is. Else return FALSE.
*/
int
serverPeekReply(int port, char_u **str)
{
NSString *reply = [[MMBackend sharedInstance] peekForReplyOnPort:port];
int len = [reply lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
if (str && len > 0) {
*str = (char_u*)[reply UTF8String];
#ifdef FEAT_MBYTE
if (input_conv.vc_type != CONV_NONE) {
char_u *s = string_convert(&input_conv, *str, &len);
if (len > 0) {
// HACK! Since 's' needs to be freed we cannot simply set
// '*str = s' or memory will leak. Instead, create a dummy
// NSData and return its 'bytes' pointer, then autorelease the
// NSData.
NSData *data = [NSData dataWithBytes:s length:len+1];
*str = (char_u*)[data bytes];
}
vim_free(s);
}
#endif
}
return reply != nil;
}
/*
* Wait for replies from server with send port 'port'.
* Return 0 and the malloc'ed string when a reply is available.
* Return -1 on error.
*/
int
serverReadReply(int port, char_u **str)
{
NSString *reply = [[MMBackend sharedInstance] waitForReplyOnPort:port];
if (reply && str) {
*str = [reply vimStringSave];
return 0;
}
return -1;
}
/*
* Send a reply string (notification) to client with port given by "serverid".
* Return -1 if the window is invalid.
*/
int
serverSendReply(char_u *serverid, char_u *reply)
{
int retval = -1;
int port = serverStrToPort(serverid);
if (port > 0 && reply) {
#ifdef FEAT_MBYTE
reply = CONVERT_TO_UTF8(reply);
#endif
BOOL ok = [[MMBackend sharedInstance]
sendReply:[NSString stringWithUTF8String:(char*)reply]
toPort:port];
retval = ok ? 0 : -1;
#ifdef FEAT_MBYTE
CONVERT_TO_UTF8_FREE(reply);
#endif
}
return retval;
}
#endif // MAC_CLIENTSERVER
// -- ODB Editor Support ----------------------------------------------------
#ifdef FEAT_ODB_EDITOR
/*
* The ODB Editor protocol works like this:
* - An external program (the server) asks MacVim to open a file and associates
* three things with this file: (1) a server id (a four character code that
* identifies the server), (2) a path that can be used as window title for
* the file (optional), (3) an arbitrary token (optional)
* - When a file is saved or closed, MacVim should tell the server about which
* file was modified and also pass back the token
*
* All communication between MacVim and the server goes via Apple Events.
*/
static int16_t
odb_event(buf_T *buf, const AEEventID action)
{
if (!(buf->b_odb_server_id && buf->b_ffname))
return noErr;
NSAppleEventDescriptor *targetDesc = [NSAppleEventDescriptor
descriptorWithDescriptorType:typeApplSignature
bytes:&buf->b_odb_server_id
length:sizeof(uint32_t)];
// TODO: Convert b_ffname to UTF-8?
NSString *path = [NSString stringWithUTF8String:(char*)buf->b_ffname];
NSData *pathData = [[[NSURL fileURLWithPath:path] absoluteString]
dataUsingEncoding:NSUTF8StringEncoding];
NSAppleEventDescriptor *pathDesc = [NSAppleEventDescriptor
descriptorWithDescriptorType:typeFileURL data:pathData];
NSAppleEventDescriptor *event = [NSAppleEventDescriptor
appleEventWithEventClass:kODBEditorSuite
eventID:action
targetDescriptor:targetDesc
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];
[event setParamDescriptor:pathDesc forKeyword:keyDirectObject];
if (buf->b_odb_token)
[event setParamDescriptor:buf->b_odb_token forKeyword:keySenderToken];
return AESendMessage([event aeDesc], NULL, kAENoReply | kAENeverInteract,
kAEDefaultTimeout);
}
int16_t
odb_buffer_close(buf_T *buf)
{
int16_t err = noErr;
if (buf) {
err = odb_event(buf, kAEClosedFile);
buf->b_odb_server_id = 0;
if (buf->b_odb_token) {
[(NSAppleEventDescriptor *)(buf->b_odb_token) release];
buf->b_odb_token = NULL;
}
if (buf->b_odb_fname) {
vim_free(buf->b_odb_fname);
buf->b_odb_fname = NULL;
}
}
return err;
}
int16_t
odb_post_buffer_write(buf_T *buf)
{
return buf ? odb_event(buf, kAEModifiedFile) : noErr;
}
void
odb_end(void)
{
buf_T *buf;
for (buf = firstbuf; buf != NULL; buf = buf->b_next)
odb_buffer_close(buf);
}
#endif // FEAT_ODB_EDITOR
char_u *
get_macaction_name(expand_T *xp, int idx)
{
static char_u *str = NULL;
NSDictionary *actionDict = [[MMBackend sharedInstance] actionDict];
if (nil == actionDict || idx < 0 || idx >= [actionDict count])
return NULL;
NSString *string = [[actionDict allKeys] objectAtIndex:idx];
if (!string)
return NULL;
char_u *plainStr = (char_u*)[string UTF8String];
#ifdef FEAT_MBYTE
if (str) {
vim_free(str);
str = NULL;
}
if (input_conv.vc_type != CONV_NONE) {
int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
str = string_convert(&input_conv, plainStr, &len);
plainStr = str;
}
#endif
return plainStr;
}
int
is_valid_macaction(char_u *action)
{
int isValid = NO;
NSDictionary *actionDict = [[MMBackend sharedInstance] actionDict];
if (actionDict) {
#ifdef FEAT_MBYTE
action = CONVERT_TO_UTF8(action);
#endif
NSString *string = [NSString stringWithUTF8String:(char*)action];
isValid = (nil != [actionDict objectForKey:string]);
#ifdef FEAT_MBYTE
CONVERT_TO_UTF8_FREE(action);
#endif
}
return isValid;
}
static int specialKeyToNSKey(int key)
{
if (!IS_SPECIAL(key))
return key;
static struct {
int special;
int nskey;
} sp2ns[] = {
{ K_UP, NSUpArrowFunctionKey },
{ K_DOWN, NSDownArrowFunctionKey },
{ K_LEFT, NSLeftArrowFunctionKey },
{ K_RIGHT, NSRightArrowFunctionKey },
{ K_F1, NSF1FunctionKey },
{ K_F2, NSF2FunctionKey },
{ K_F3, NSF3FunctionKey },
{ K_F4, NSF4FunctionKey },
{ K_F5, NSF5FunctionKey },
{ K_F6, NSF6FunctionKey },
{ K_F7, NSF7FunctionKey },
{ K_F8, NSF8FunctionKey },
{ K_F9, NSF9FunctionKey },
{ K_F10, NSF10FunctionKey },
{ K_F11, NSF11FunctionKey },
{ K_F12, NSF12FunctionKey },
{ K_F13, NSF13FunctionKey },
{ K_F14, NSF14FunctionKey },
{ K_F15, NSF15FunctionKey },
{ K_F16, NSF16FunctionKey },
{ K_F17, NSF17FunctionKey },
{ K_F18, NSF18FunctionKey },
{ K_F19, NSF19FunctionKey },
{ K_F20, NSF20FunctionKey },
{ K_F21, NSF21FunctionKey },
{ K_F22, NSF22FunctionKey },
{ K_F23, NSF23FunctionKey },
{ K_F24, NSF24FunctionKey },
{ K_F25, NSF25FunctionKey },
{ K_F26, NSF26FunctionKey },
{ K_F27, NSF27FunctionKey },
{ K_F28, NSF28FunctionKey },
{ K_F29, NSF29FunctionKey },
{ K_F30, NSF30FunctionKey },
{ K_F31, NSF31FunctionKey },
{ K_F32, NSF32FunctionKey },
{ K_F33, NSF33FunctionKey },
{ K_F34, NSF34FunctionKey },
{ K_F35, NSF35FunctionKey },
{ K_DEL, NSBackspaceCharacter },
{ K_BS, NSDeleteCharacter },
{ K_HOME, NSHomeFunctionKey },
{ K_END, NSEndFunctionKey },
{ K_PAGEUP, NSPageUpFunctionKey },
{ K_PAGEDOWN, NSPageDownFunctionKey }
};
int i;
for (i = 0; i < sizeof(sp2ns)/sizeof(sp2ns[0]); ++i) {
if (sp2ns[i].special == key)
return sp2ns[i].nskey;
}
return 0;
}
static int vimModMaskToEventModifierFlags(int mods)
{
int flags = 0;
if (mods & MOD_MASK_SHIFT)
flags |= NSEventModifierFlagShift;
if (mods & MOD_MASK_CTRL)
flags |= NSEventModifierFlagControl;
if (mods & MOD_MASK_ALT)
flags |= NSEventModifierFlagOption;
if (mods & MOD_MASK_CMD)
flags |= NSEventModifierFlagCommand;
return flags;
}
// -- Channel Support ------------------------------------------------------
void *
gui_macvim_add_channel(channel_T *channel, ch_part_T part)
{
dispatch_source_t s =
dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
channel->ch_part[part].ch_fd,
0,
dispatch_get_main_queue());
dispatch_source_set_event_handler(s, ^{
channel_may_read(channel, part, "gui_macvim_add_channel");
});
dispatch_resume(s);
return s;
}
void
gui_macvim_remove_channel(void *cookie)
{
dispatch_source_t s = (dispatch_source_t)cookie;
dispatch_source_cancel(s);
dispatch_release(s);
}
// -- Graphical Sign Support ------------------------------------------------
#if defined(FEAT_SIGN_ICONS)
void
gui_mch_drawsign(int row, int col, int typenr)
{
if (!gui.in_use)
return;
NSString *imgName = (NSString *)sign_get_image(typenr);
if (!imgName)
return;
char_u *txt = sign_get_text(typenr);
int txtSize = txt ? strlen((char*)txt) : 2;
[[MMBackend sharedInstance] drawSign:imgName
atRow:row
column:col
width:txtSize
height:1];
}
void *
gui_mch_register_sign(char_u *signfile)
{
if (!use_graphical_sign)
return NULL;
NSString *imgName = [NSString stringWithVimString:signfile];
NSImage *img = [[NSImage alloc] initWithContentsOfFile:imgName];
if (!img) {
EMSG(_(e_signdata));
return NULL;
}
[img release];
return (void*)[imgName retain];
}
void
gui_mch_destroy_sign(void *sign)
{
NSString *imgName = (NSString *)sign;
if (!imgName)
return;
[[MMBackend sharedInstance]
queueMessage:DeleteSignMsgID
properties:[NSDictionary dictionaryWithObjectsAndKeys:
imgName, @"imgName", nil]];
[imgName release];
}
#endif // FEAT_SIGN_ICONS
// -- Balloon Eval Support ---------------------------------------------------
#ifdef FEAT_BEVAL
BalloonEval *
gui_mch_create_beval_area(target, mesg, mesgCB, clientData)
void *target;
char_u *mesg;
void (*mesgCB)(BalloonEval *, int);
void *clientData;
{
BalloonEval *beval;
beval = (BalloonEval *)calloc(1, sizeof(BalloonEval));
if (NULL == beval)
return NULL;
beval->msg = mesg;
beval->msgCB = mesgCB;
beval->clientData = clientData;
return beval;
}
void
gui_mch_enable_beval_area(beval)
BalloonEval *beval;
{
// Set the balloon delay when enabling balloon eval.
float delay = p_bdlay/1000.0f - MMBalloonEvalInternalDelay;
if (delay < 0) delay = 0;
[[MMBackend sharedInstance] queueMessage:SetTooltipDelayMsgID properties:
[NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:delay]
forKey:@"delay"]];
}
void
gui_mch_disable_beval_area(beval)
BalloonEval *beval;
{
// NOTE: An empty tool tip indicates that the tool tip window should hide.
[[MMBackend sharedInstance] queueMessage:SetTooltipMsgID properties:
[NSDictionary dictionaryWithObject:@"" forKey:@"toolTip"]];
}
/*
* Show a balloon with "mesg".
*/
void
gui_mch_post_balloon(beval, mesg)
BalloonEval *beval;
char_u *mesg;
{
NSString *toolTip = [NSString stringWithVimString:mesg];
[[MMBackend sharedInstance] setLastToolTip:toolTip];
}
#endif // FEAT_BEVAL
void
gui_macvim_set_blur(int radius)
{
[[MMBackend sharedInstance] setBlurRadius:radius];
}