mirror of
https://github.com/macvim-dev/macvim.git
synced 2026-02-14 11:53:26 +01:00
Upstream Vim has added strict-prototypes and missing-prototypes warnings to CI, which is useful but need to clean up MacVim code to avoid build failures. There should be no more magic extern function declarations from now on as they are quite fragile. Just put them in a prototype header. Also fix GitHub Action badge on README. For some reason the format has changed and the badge was always reporting we have passed even if CI is failing.
2680 lines
71 KiB
Objective-C
2680 lines
71 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>
|
|
|
|
|
|
static int use_graphical_sign = 0;
|
|
|
|
static BOOL is_macos_high_sierra_or_later = NO;
|
|
|
|
// 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(void)
|
|
{
|
|
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(void)
|
|
{
|
|
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);
|
|
|
|
// 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];
|
|
}
|
|
|
|
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10
|
|
if ([[NSProcessInfo processInfo]
|
|
respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)])
|
|
{
|
|
NSOperatingSystemVersion version = {10, 13, 0};
|
|
|
|
is_macos_high_sierra_or_later = [[NSProcessInfo processInfo]
|
|
isOperatingSystemAtLeastVersion:version];
|
|
}
|
|
#endif
|
|
|
|
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) {
|
|
gui_macvim_update();
|
|
lastTime = nowTime;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
gui_macvim_update(void)
|
|
{
|
|
[[MMBackend sharedInstance] update];
|
|
}
|
|
|
|
|
|
/* 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
|
|
# ifdef FEAT_TIMERS
|
|
did_add_timer = FALSE;
|
|
# endif
|
|
|
|
parse_queued_messages();
|
|
|
|
# ifdef FEAT_TIMERS
|
|
if (did_add_timer)
|
|
wtime = 0;
|
|
# endif
|
|
#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];
|
|
}
|
|
|
|
|
|
int
|
|
gui_macvim_draw_string(int row, int col, char_u *s, int len, int flags)
|
|
{
|
|
MMBackend *backend = [MMBackend sharedInstance];
|
|
int c, cw, cl, ccl;
|
|
int start = 0;
|
|
int endcol = col;
|
|
int startcol = col;
|
|
BOOL wide = NO;
|
|
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;
|
|
}
|
|
|
|
// Loop over each character and output text when it changes from normal to
|
|
// wide and vice versa.
|
|
for (int i = 0; i < len; i += cl) {
|
|
c = utf_ptr2char(s + i);
|
|
cw = utf_char2cells(c);
|
|
cl = utf_ptr2len(s + i);
|
|
ccl = utfc_ptr2len(s + i);
|
|
if (cl == 0)
|
|
len = i; // len must be wrong (shouldn't happen)
|
|
|
|
if (i > start && (cl < ccl || (cw > 1 && !wide) || (cw <= 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:flags|(wide ? DRAW_WIDE : 0)];
|
|
|
|
start = i;
|
|
startcol = endcol;
|
|
}
|
|
|
|
wide = cw > 1;
|
|
endcol += cw;
|
|
|
|
if (cl < ccl) {
|
|
// Changed from normal to wide or vice versa.
|
|
[backend drawString:(s+start) length:ccl
|
|
row:row column:startcol
|
|
cells:endcol-startcol
|
|
flags:flags|DRAW_COMP|(wide ? DRAW_WIDE : 0)];
|
|
|
|
start = i + ccl;
|
|
startcol = endcol;
|
|
cl = ccl;
|
|
}
|
|
}
|
|
|
|
if (len > start) {
|
|
// Output remaining characters.
|
|
[backend drawString:(s+start) length:len-start
|
|
row:row column:startcol
|
|
cells:endcol-startcol
|
|
flags:flags|(wide ? DRAW_WIDE : 0)];
|
|
}
|
|
|
|
if (conv_str)
|
|
vim_free(conv_str);
|
|
|
|
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(void)
|
|
{
|
|
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]];
|
|
}
|
|
|
|
|
|
// Look up the icon file. If it's a full path, return that. Otherwise, look for
|
|
// it under a 'bitmaps' folder under runtimepath, using common file extensions.
|
|
// 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);
|
|
|
|
// Just find the popular image formats that macOS supports.
|
|
static const char suffixes[][5] = {
|
|
"png", "bmp", "ico", "icns", "jpeg", "jpg", "heic", "webp"};
|
|
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);
|
|
NSString *icon = nil;
|
|
|
|
vimmenu_T *rootMenu = menu;
|
|
while (rootMenu->parent) {
|
|
rootMenu = rootMenu->parent;
|
|
}
|
|
if (menu_is_toolbar(rootMenu->name)) {
|
|
// Find out what file to load for the toolbar icon.
|
|
char_u fname[MAXPATHL];
|
|
|
|
// Try to use the file path from the icon=.. argument
|
|
if (menu->iconfile && lookup_menu_iconfile(menu->iconfile, fname))
|
|
icon = [NSString stringWithVimString:fname];
|
|
|
|
// If not found and not builtin specified try using the menu name
|
|
if (icon == nil && !menu->icon_builtin
|
|
&& lookup_menu_iconfile(menu->name, fname))
|
|
icon = [NSString stringWithVimString:fname];
|
|
|
|
// Still no icon found, try using a builtin icon. (If this also fails,
|
|
// then a warning icon will be displayed).
|
|
if (icon == nil) {
|
|
char_u* toolbar_item = lookup_toolbar_item(menu->iconidx);
|
|
if (toolbar_item) {
|
|
icon = [NSString stringWithVimString:toolbar_item];
|
|
|
|
// All the default icons that MacVim ships with are templates
|
|
// to make them work better in light/dark modes.
|
|
icon = [icon stringByAppendingString:@":template"];
|
|
}
|
|
}
|
|
|
|
// Last step is to simply pass the icon argument up the chain as there
|
|
// are more complicated logic to determine what this is (e.g. SF Symbol
|
|
// or raw image).
|
|
if (icon == nil) {
|
|
if (menu->iconfile && *menu->iconfile != '\0') {
|
|
icon = [NSString stringWithVimString:menu->iconfile];
|
|
}
|
|
}
|
|
} else {
|
|
// For regular menus, we support icons as well, but only if it's
|
|
// specified by the icon=... argument. This is a MacVim-extension.
|
|
char_u fname[MAXPATHL];
|
|
|
|
if (menu->iconfile && *menu->iconfile != '\0') {
|
|
if (lookup_menu_iconfile(menu->iconfile, fname)) {
|
|
icon = [NSString stringWithVimString:fname];
|
|
} else {
|
|
icon = [NSString stringWithVimString:menu->iconfile];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (icon == nil) {
|
|
// Need non-nil items for dictionaryWithObjectsAndKeys: below.
|
|
icon = @"";
|
|
}
|
|
|
|
[[MMBackend sharedInstance] queueMessage:AddMenuItemMsgID properties:
|
|
[NSDictionary dictionaryWithObjectsAndKeys:
|
|
desc, @"descriptor",
|
|
[NSNumber numberWithInt:idx], @"index",
|
|
[NSString stringWithVimString:tip], @"tip",
|
|
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"]];
|
|
}
|
|
|
|
|
|
/*
|
|
* Update a menu's tooltip.
|
|
*/
|
|
void
|
|
gui_mch_menu_set_tip(vimmenu_T *menu)
|
|
{
|
|
char_u *tip = menu->strings[MENU_INDEX_TIP];
|
|
NSArray *desc = descriptor_for_menu(menu);
|
|
[[MMBackend sharedInstance] queueMessage:UpdateMenuItemTooltipMsgID properties:
|
|
[NSDictionary dictionaryWithObjectsAndKeys:
|
|
desc, @"descriptor",
|
|
[NSString stringWithVimString:tip], @"tip",
|
|
nil]];
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 UNUSED)
|
|
{
|
|
// 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(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)
|
|
semsg(_(e_unknown_font_str), 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 UNUSED)
|
|
{
|
|
// 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 UNUSED) /* 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];
|
|
}
|
|
|
|
|
|
int
|
|
gui_mch_get_scrollbar_xpadding(void)
|
|
{
|
|
// TODO: Calculate the padding for adjust scrollbar position when the
|
|
// Window is maximized.
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
gui_mch_get_scrollbar_ypadding(void)
|
|
{
|
|
// TODO: Calculate the padding for adjust scrollbar position when the
|
|
// Window is maximized.
|
|
return 0;
|
|
}
|
|
|
|
|
|
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 UNUSED, int h UNUSED, 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(int may_call_gui_update_cursor)
|
|
{
|
|
[[MMBackend sharedInstance] stopBlink:may_call_gui_update_cursor];
|
|
}
|
|
|
|
|
|
// -- Mouse -----------------------------------------------------------------
|
|
|
|
|
|
/*
|
|
* Get current mouse coordinates in text window.
|
|
*/
|
|
void
|
|
gui_mch_getmouse(int *x UNUSED, int *y UNUSED)
|
|
{
|
|
ASLogInfo(@"Not implemented!");
|
|
}
|
|
|
|
|
|
void
|
|
gui_mch_setmouse(int x UNUSED, int y UNUSED)
|
|
{
|
|
ASLogInfo(@"Not implemented!");
|
|
}
|
|
|
|
void
|
|
gui_mch_mousehide(int hide UNUSED)
|
|
{
|
|
// We don't implement this. `insertVimStateMessage` already sends this to
|
|
// MacVim, and we handle this in the parent process using NSCursor's
|
|
// `setHiddenUntilMouseMoves` instead of letting Vim have manual control
|
|
// over this.
|
|
}
|
|
|
|
|
|
void
|
|
mch_set_mouse_shape(int shape)
|
|
{
|
|
[[MMBackend sharedInstance] setMouseShape:shape];
|
|
}
|
|
|
|
|
|
|
|
|
|
// -- Input Method ----------------------------------------------------------
|
|
|
|
#if defined(FEAT_EVAL)
|
|
# ifdef FEAT_GUI
|
|
# define USE_IMACTIVATEFUNC (!gui.in_use && *p_imaf != NUL)
|
|
# define USE_IMSTATUSFUNC (!gui.in_use && *p_imsf != NUL)
|
|
# else
|
|
# define USE_IMACTIVATEFUNC (*p_imaf != NUL)
|
|
# define USE_IMSTATUSFUNC (*p_imsf != NUL)
|
|
# endif
|
|
#endif
|
|
|
|
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)
|
|
{
|
|
#if defined(FEAT_EVAL)
|
|
if (USE_IMACTIVATEFUNC)
|
|
{
|
|
if (active != im_get_status())
|
|
call_imactivatefunc(active);
|
|
return;
|
|
}
|
|
#endif
|
|
// 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)
|
|
{
|
|
#if defined(FEAT_EVAL)
|
|
if (USE_IMSTATUSFUNC)
|
|
return call_imstatusfunc();
|
|
#endif
|
|
return [[MMBackend sharedInstance] imState];
|
|
}
|
|
|
|
|
|
|
|
|
|
// -- Find & Replace dialog -------------------------------------------------
|
|
|
|
#ifdef FIND_REPLACE_DIALOG
|
|
|
|
static void
|
|
macvim_find_and_replace(char_u *arg, BOOL replace UNUSED)
|
|
{
|
|
// 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(exarg_T *eap)
|
|
{
|
|
if (!gui.in_use) {
|
|
emsg(_("E9000-M: Command only available in GUI mode"));
|
|
return;
|
|
}
|
|
|
|
char_u *arg = eap->arg;
|
|
arg = CONVERT_TO_UTF8(arg);
|
|
|
|
NSDictionary *actionDict = [[MMBackend sharedInstance] actionDict];
|
|
NSString *name = [NSString stringWithUTF8String:(char*)arg];
|
|
if (actionDict && [actionDict objectForKey:name] != nil) {
|
|
[[MMBackend sharedInstance] executeActionWithName:name];
|
|
} else {
|
|
semsg(_("E9001-M: Invalid action: %s"), eap->arg);
|
|
}
|
|
|
|
arg = CONVERT_TO_UTF8(arg);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 UNUSED)
|
|
{
|
|
// NOP
|
|
}
|
|
|
|
|
|
/*
|
|
* 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;
|
|
|
|
char_u *u8name = CONVERT_TO_UTF8(name);
|
|
|
|
NSString *key = [NSString stringWithUTF8String:(char*)u8name];
|
|
guicolor_T color = [[MMBackend sharedInstance] lookupColorWithKey:key];
|
|
|
|
CONVERT_TO_UTF8_FREE(u8name);
|
|
|
|
if (color != INVALCOLOR)
|
|
return color;
|
|
|
|
return gui_get_color_cmn(name);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
// NOP
|
|
}
|
|
|
|
|
|
#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];
|
|
}
|
|
|
|
|
|
/*
|
|
* Re-calculates size of the Vim view to fit within the window without having
|
|
* to resize the window. Usually happens after UI elements have changed (e.g.
|
|
* adding / removing a toolbar) when guioptions 'k' is set.
|
|
*/
|
|
void
|
|
gui_mch_resize_view(void)
|
|
{
|
|
[[MMBackend sharedInstance] resizeView];
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 UNUSED, int y UNUSED, int w UNUSED, int h UNUSED)
|
|
{
|
|
// NOP
|
|
}
|
|
|
|
|
|
/*
|
|
* 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);
|
|
|
|
title = CONVERT_TO_UTF8(title);
|
|
|
|
MMBackend *backend = [MMBackend sharedInstance];
|
|
[backend setWindowTitle:(char*)title];
|
|
|
|
// TODO: Convert filename to UTF-8?
|
|
if (curbuf)
|
|
[backend setDocumentFilename:(char*)curbuf->b_ffname];
|
|
|
|
CONVERT_TO_UTF8_FREE(title);
|
|
}
|
|
|
|
|
|
void
|
|
gui_mch_toggle_tearoffs(int enable UNUSED)
|
|
{
|
|
// NOP
|
|
}
|
|
|
|
|
|
|
|
void
|
|
gui_mch_enter_fullscreen(guicolor_T bg)
|
|
{
|
|
[[MMBackend sharedInstance] enterFullScreen:fuoptions_flags background:bg];
|
|
}
|
|
|
|
|
|
void
|
|
gui_mch_leave_fullscreen(void)
|
|
{
|
|
[[MMBackend sharedInstance] leaveFullScreen];
|
|
}
|
|
|
|
|
|
void
|
|
gui_mch_fuopt_update(void)
|
|
{
|
|
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(void)
|
|
{
|
|
[[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;
|
|
|
|
if ([MMBackend sharedInstance].addToFindPboardOverride) {
|
|
// MMBackend would set addToFindPboardOverride to YES when we are
|
|
// using the useSelectionFodFind: macaction, accessible via Cmd-E,
|
|
// which indicates we always want to share to the find pasteboard
|
|
// unlike normal search. Since this function is called after the action
|
|
// is handled we need to manually clear the override so it doesn't
|
|
// persist in later searches.
|
|
[[MMBackend sharedInstance] clearAddToFindPboardOverride];
|
|
} else {
|
|
if (!MMShareFindPboard) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
pat = CONVERT_TO_UTF8(pat);
|
|
NSString *s = [NSString stringWithUTF8String:(char*)pat];
|
|
CONVERT_TO_UTF8_FREE(pat);
|
|
|
|
if (!s) return;
|
|
|
|
NSPasteboard *pb = [NSPasteboard pasteboardWithName:NSPasteboardNameFind];
|
|
NSArray *supportedTypes = [NSArray arrayWithObjects:VimFindPboardType,
|
|
NSPasteboardTypeString, 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];
|
|
[pb setString:[s stringByRemovingFindPatterns] forType:NSPasteboardTypeString];
|
|
}
|
|
|
|
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(void)
|
|
{
|
|
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(void)
|
|
{
|
|
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)
|
|
{
|
|
name = CONVERT_TO_UTF8(name);
|
|
|
|
NSString *svrName = [NSString stringWithUTF8String:(char*)name];
|
|
[[MMBackend sharedInstance] registerServerWithName:svrName];
|
|
|
|
CONVERT_TO_UTF8_FREE(name);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 UNUSED,
|
|
int silent)
|
|
{
|
|
name = CONVERT_TO_UTF8(name);
|
|
cmd = CONVERT_TO_UTF8(cmd);
|
|
|
|
BOOL ok = [[MMBackend sharedInstance]
|
|
sendToServer:[NSString stringWithUTF8String:(char*)name]
|
|
string:[NSString stringWithUTF8String:(char*)cmd]
|
|
reply:result
|
|
port:port
|
|
expression:asExpr
|
|
silent:silent];
|
|
|
|
CONVERT_TO_UTF8_FREE(name);
|
|
CONVERT_TO_UTF8_FREE(cmd);
|
|
|
|
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)
|
|
semsg(_("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];
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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, int timeout)
|
|
{
|
|
NSString *reply = [[MMBackend sharedInstance] waitForReplyOnPort:port
|
|
timeout:timeout];
|
|
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) {
|
|
reply = CONVERT_TO_UTF8(reply);
|
|
BOOL ok = [[MMBackend sharedInstance]
|
|
sendReply:[NSString stringWithUTF8String:(char*)reply]
|
|
toPort:port];
|
|
retval = ok ? 0 : -1;
|
|
CONVERT_TO_UTF8_FREE(reply);
|
|
}
|
|
|
|
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 UNUSED, int idx)
|
|
{
|
|
static char_u *str = NULL;
|
|
NSDictionary *actionDict = [[MMBackend sharedInstance] actionDict];
|
|
|
|
if (nil == actionDict || idx < 0 || (size_t)idx >= [actionDict count])
|
|
return NULL;
|
|
|
|
NSString *string = [[actionDict allKeys] objectAtIndex:idx];
|
|
if (!string)
|
|
return NULL;
|
|
|
|
char_u *plainStr = (char_u*)[string UTF8String];
|
|
|
|
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;
|
|
}
|
|
|
|
return plainStr;
|
|
}
|
|
|
|
|
|
int
|
|
is_valid_macaction(char_u *action)
|
|
{
|
|
int isValid = NO;
|
|
NSDictionary *actionDict = [[MMBackend sharedInstance] actionDict];
|
|
if (actionDict) {
|
|
action = CONVERT_TO_UTF8(action);
|
|
NSString *string = [NSString stringWithUTF8String:(char*)action];
|
|
isValid = (nil != [actionDict objectForKey:string]);
|
|
CONVERT_TO_UTF8_FREE(action);
|
|
}
|
|
|
|
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 }
|
|
};
|
|
|
|
size_t 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;
|
|
}
|
|
|
|
|
|
|
|
// -- Job and Channel Support ------------------------------------------------------
|
|
|
|
#if defined(FEAT_JOB_CHANNEL)
|
|
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);
|
|
}
|
|
|
|
void
|
|
gui_macvim_cleanup_job_all(void)
|
|
{
|
|
if (is_macos_high_sierra_or_later)
|
|
job_cleanup_all();
|
|
}
|
|
|
|
#endif // FEAT_JOB_CHANNEL
|
|
|
|
|
|
// -- 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;
|
|
|
|
const int txtSize = 2; // This is specified in the docs, and is expected to always be the case.
|
|
|
|
[[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_couldnt_read_in_sign_data));
|
|
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(
|
|
void *target UNUSED,
|
|
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(BalloonEval *beval UNUSED)
|
|
{
|
|
// NOP
|
|
}
|
|
|
|
void
|
|
gui_mch_disable_beval_area(BalloonEval *beval UNUSED)
|
|
{
|
|
// 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(BalloonEval *beval, char_u *mesg)
|
|
{
|
|
vim_free(beval->msg);
|
|
beval->msg = mesg == NULL ? NULL : vim_strsave(mesg);
|
|
|
|
NSString *toolTip = [NSString stringWithVimString:mesg];
|
|
[[MMBackend sharedInstance] setLastToolTip:toolTip];
|
|
}
|
|
|
|
#endif // FEAT_BEVAL
|
|
|
|
void
|
|
gui_macvim_set_blur(int radius)
|
|
{
|
|
[[MMBackend sharedInstance] setBlurRadius:radius];
|
|
}
|
|
|
|
void
|
|
gui_macvim_set_background(int dark)
|
|
{
|
|
[[MMBackend sharedInstance] setBackground:dark];
|
|
}
|
|
|
|
#pragma region MacVim builtin functions
|
|
#pragma mark MacVim builtin functions
|
|
|
|
/// Implementation of showdefinition()
|
|
void f_showdefinition(typval_T *argvars, typval_T *rettv UNUSED)
|
|
{
|
|
if (!gui.in_use) {
|
|
emsg(_("E9000-M: Command only available in GUI mode"));
|
|
return;
|
|
}
|
|
|
|
if (in_vim9script() && check_for_string_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
char_u *lookup_text = tv_get_string(&argvars[0]);
|
|
|
|
varnumber_T screen_row = -1;
|
|
varnumber_T screen_col = -1;
|
|
|
|
if (argvars[1].v_type == VAR_DICT) {
|
|
// Retrieve the optional row/col from the caller. Note that this is
|
|
// designed so that the input object could just be the output of
|
|
// screenpos().
|
|
dict_T *d = argvars[1].vval.v_dict;
|
|
if (d != NULL) {
|
|
screen_row = dict_get_number_def(d, "row", -1);
|
|
screen_col = dict_get_number_def(d, "col", -1);
|
|
}
|
|
}
|
|
|
|
if (screen_row <= 0 || screen_col <= 0) {
|
|
// row/col are optional parameters, so if not given we just use the
|
|
// cursor position.
|
|
// We are essentially doing the following:
|
|
// var curpos = getcurpos()
|
|
// var screenpos = screenpos(win_getid(), curpos[1], curpos[2])
|
|
// showDefinition(text, screenpos['row'], screenpos['col'])
|
|
//
|
|
// Note that we could either take screenpos['cursorcol'] or
|
|
// screenpos['col']. Both could make sense in some situations, but just
|
|
// for consistency with how this function is used, we just use 'col'.
|
|
// (It's consistent because this function is designed so that you can
|
|
// just pass the output of screenpos() directly into the 2nd argument).
|
|
varnumber_T lnum = 0, col = 0;
|
|
{
|
|
typval_T arg_winid_unknown;
|
|
init_tv(&arg_winid_unknown);
|
|
arg_winid_unknown.v_type = VAR_UNKNOWN;
|
|
|
|
typval_T args[1] = { arg_winid_unknown };
|
|
|
|
typval_T lrettv;
|
|
|
|
f_getcurpos(args, &lrettv);
|
|
if (lrettv.v_type == VAR_LIST) {
|
|
lnum = list_find(lrettv.vval.v_list, 1)->li_tv.vval.v_number;
|
|
col = list_find(lrettv.vval.v_list, 2)->li_tv.vval.v_number;
|
|
list_unref(lrettv.vval.v_list);
|
|
}
|
|
}
|
|
{
|
|
typval_T arg_winid;
|
|
init_tv(&arg_winid);
|
|
arg_winid.v_type = VAR_NUMBER;
|
|
arg_winid.vval.v_number = curwin->w_id;
|
|
|
|
typval_T arg_lnum;
|
|
init_tv(&arg_lnum);
|
|
arg_lnum.v_type = VAR_NUMBER;
|
|
arg_lnum.vval.v_number = lnum;
|
|
|
|
typval_T arg_col;
|
|
init_tv(&arg_col);
|
|
arg_col.v_type = VAR_NUMBER;
|
|
arg_col.vval.v_number = col;
|
|
|
|
typval_T args[3] = {
|
|
arg_winid,
|
|
arg_lnum,
|
|
arg_col,
|
|
};
|
|
typval_T lrettv;
|
|
|
|
f_screenpos(args, &lrettv);
|
|
|
|
screen_row = dict_get_number_def(lrettv.vval.v_dict, "row", -1);
|
|
screen_col = dict_get_number_def(lrettv.vval.v_dict, "col", -1);
|
|
|
|
dict_unref(lrettv.vval.v_dict);
|
|
}
|
|
}
|
|
|
|
NSString *lookup_text_str = [NSString stringWithVimString:lookup_text];
|
|
[[MMBackend sharedInstance] showDefinition:lookup_text_str
|
|
row:screen_row
|
|
col:screen_col];
|
|
}
|
|
|
|
#pragma endregion
|
|
|
|
|
|
// -- Netbeans Integration Support -------------------------------------------
|
|
|
|
#if defined(FEAT_NETBEANS_INTG) || defined(PROTO)
|
|
void
|
|
netbeans_draw_multisign_indicator(int row UNUSED)
|
|
{
|
|
// NOP
|
|
}
|
|
#endif // FEAT_NETBEANS_INTG
|