Merge pull request #895 from ychin/filename_sanitization_fixes

Fix opening files that have special chars like '$' in filename
This commit is contained in:
Yee Cheng Chin
2019-05-22 10:20:13 -07:00
committed by GitHub
7 changed files with 399 additions and 122 deletions
+9 -20
View File
@@ -976,21 +976,11 @@ fsEventCallback(ConstFSEventStreamRef streamRef,
//
// NOTE: Raise window before passing arguments, otherwise the
// selection will be lost when selectionRange is set.
firstFile = [firstFile stringByEscapingSpecialFilenameCharacters];
NSString *bufCmd = @"tab sb";
switch (layout) {
case MMLayoutHorizontalSplit: bufCmd = @"sb"; break;
case MMLayoutVerticalSplit: bufCmd = @"vert sb"; break;
case MMLayoutArglist: bufCmd = @"b"; break;
}
NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
"%@ %@|let &swb=oldswb|unl oldswb|"
"cal foreground()<CR>", bufCmd, firstFile];
[vc addVimInput:input];
NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
firstFile, @"filename",
[NSNumber numberWithInt:layout], @"layout",
nil];
[vc sendMessage:SelectAndFocusOpenedFileMsgID data:[args dictionaryAsData]];
}
[vc passArguments:arguments];
@@ -1465,16 +1455,15 @@ fsEventCallback(ConstFSEventStreamRef streamRef,
if (!dirIndicator)
path = [path stringByDeletingLastPathComponent];
path = [path stringByEscapingSpecialFilenameCharacters];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
MMVimController *vc;
if (openInCurrentWindow && (vc = [self topmostVimController])) {
NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
":tabe|cd %@<CR>", path];
[vc addVimInput:input];
NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
path, @"path",
nil];
[vc sendMessage:NewFileHereMsgID data:[args dictionaryAsData]];
} else {
[self launchVimProcessWithArguments:nil workingDirectory:path];
}
+353 -46
View File
@@ -189,6 +189,8 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
- (void)startOdbEditWithArguments:(NSDictionary *)args;
- (void)handleXcodeMod:(NSData *)data;
- (void)handleOpenWithArguments:(NSDictionary *)args;
- (void)handleSelectAndFocusOpenedFile:(NSDictionary *)args;
- (void)handleNewFileHere:(NSDictionary *)args;
- (int)checkForModifiedBuffers;
- (void)addInput:(NSString *)input;
- (void)redrawScreen;
@@ -2075,6 +2077,10 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
[self handleXcodeMod:data];
} else if (OpenWithArgumentsMsgID == msgid) {
[self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
} else if (SelectAndFocusOpenedFileMsgID == msgid) {
[self handleSelectAndFocusOpenedFile:[NSDictionary dictionaryWithData:data]];
} else if (NewFileHereMsgID == msgid) {
[self handleNewFileHere:[NSDictionary dictionaryWithData:data]];
} else if (FindReplaceMsgID == msgid) {
[self handleFindReplace:[NSDictionary dictionaryWithData:data]];
} else if (UseSelectionForFindMsgID == msgid) {
@@ -2637,6 +2643,7 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
// filenames list of filenames
// dontOpen don't open files specified in above argument
// layout which layout to use to open files
// tabpage a tab page to enter first before opening
// selectionRange range of characters to select
// searchText string to search for
// cursorLine line to position the cursor on
@@ -2653,6 +2660,7 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
int i, numFiles = filenames ? [filenames count] : 0;
BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
int layout = [[args objectForKey:@"layout"] intValue];
int tabpage = [[args objectForKey:@"tabpage"] intValue];
if (starting > 0) {
// When Vim is starting we simply add the files to be opened to the
@@ -2694,6 +2702,11 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
//
// TODO: Figure out a better way to handle this?
if (openFiles && numFiles > 0) {
// Caller specified a tab page to enter first.
if (tabpage > 0) {
goto_tabpage(tabpage);
}
BOOL oneWindowInTab = topframe ? YES
: (topframe->fr_layout == FR_LEAF);
BOOL bufChanged = NO;
@@ -2707,85 +2720,236 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
// potentially cause multiple redraws.
flushDisabled = YES;
BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
if (WIN_TABS == layout && !onlyOneTab) {
// By going to the last tabpage we ensure that the new tabs
// will appear last (if this call is left out, the taborder
// becomes messy).
goto_tabpage(9999);
}
// Make sure we're in normal mode first.
// TODO: The mixing of addInput and Ex commands is a little
// problematic because addInput is asynchronous and will therefore
// run after all the Ex commands. Should fix this in the future.
[self addInput:@"<C-\\><C-N>"];
// Note: We call Ex functions directly to edit files instead of
// using addInput. The reason is that addInput relies on us manually
// constructing an Ex command string which requires us to manually
// escape file names with special characters like "$". In Vim there
// isn't a good way to escape characters like "\n" even if we use
// fnameescape, and the injected newline could be used by a
// malicious actor to craft a file name of the form "someFile\n:q"
// (\n represents the real newline character, not '\' and 'n') where
// the :q will be interpreted as the next Ex command by addInput.
// This could mean dragging a file with malicious filename could
// execute arbitrary Ex cmds which is obviously not good.
//
// To avoid mistakes, just call the C functions directly which
// avoids the problem of command ambiguity in crafting and escaping
// the Ex cmd string to send over.
exarg_T ea;
vim_memset(&ea, 0, sizeof(ea));
if (numFiles > 1) {
cmdmod_T save_cmdmod = cmdmod;
vim_memset(&cmdmod, 0, sizeof(cmdmod));
BOOL splitInNewTab = NO;
// With "split layout" we open a new tab before opening
// multiple files if the current tab has more than one window
// or if there is exactly one window but whose buffer has a
// filename. (The :drop command ensures modified buffers get
// their own window.)
if ((WIN_HOR == layout || WIN_VER == layout) &&
(!oneWindowInTab || bufHasFilename))
[self addInput:@":tabnew<CR>"];
(!oneWindowInTab || bufHasFilename)) {
splitInNewTab = YES;
// The files are opened by constructing a ":drop ..." command
// and executing it.
NSMutableString *cmd = (WIN_TABS == layout)
? [NSMutableString stringWithString:@":tab drop"]
: [NSMutableString stringWithString:@":drop"];
char_u tabnewArgs[] = "";
char_u tabnewCmd[] = "tabnew";
for (i = 0; i < numFiles; ++i) {
NSString *file = [filenames objectAtIndex:i];
file = [file stringByEscapingSpecialFilenameCharacters];
[cmd appendString:@" "];
[cmd appendString:file];
// :tabnew
ea.arg = tabnewArgs;
ea.cmdidx = CMD_tabnew;
ea.cmd = tabnewCmd;
ex_splitview(&ea);
}
// Temporarily clear 'suffixes' so that the files are opened in
// the same order as they appear in the "filenames" array.
[self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
//
// :let mvim_oldsu=&su|set su=
char_u *orig_p_su = p_su;
char_u empty_p_su[] = "";
p_su = empty_p_su;
[self addInput:cmd];
// The files are opened by constructing a ":drop ..." command
// and executing it. The "drop" commands take in a single
// argument with all the filenames in them, and the filenames
// are required to be properly escaped or else names like
// "$filename" will get substituted as a variable which is wrong.
garray_T filename_array;
ga_init2(&filename_array, sizeof(char_u*), numFiles);
for (i = 0; i < numFiles; ++i) {
NSString *file = [filenames objectAtIndex:i];
// Escape each file name and add to the growing array.
char_u *escapedFname = vim_strsave_fnameescape((char_u*)[file UTF8String], FALSE);
ga_add_string(&filename_array, escapedFname);
vim_free(escapedFname);
}
char_u* escapedFilenameList = ga_concat_strings(&filename_array, " ");
ga_clear_strings(&filename_array);
if (WIN_TABS == layout) {
tabpage_T *tp;
int numTabs = 0;
FOR_ALL_TABPAGES(tp) {
numTabs += 1;
}
// Convert ":drop ..." to ":$tab drop ..."
cmdmod.tab = numTabs + 1;
}
if (splitInNewTab) {
// If we are making a new tab to do a full split, don't
// bother using the :drop command which will switch to an
// open buffer if the first file is opened already, which
// we don't want. We just want a full tab with all the
// files split. Just set the arglist and the later :sall
// command will take care of it.
set_arglist(escapedFilenameList);
}
else {
char_u dropCmdname[] = "drop";
// :drop ... / :$tab drop ...
ea.arg = escapedFilenameList;
ea.cmd = dropCmdname;
ea.cmdidx = CMD_drop;
ex_drop(&ea);
}
// Clean up temporary strings.
vim_free(escapedFilenameList);
escapedFilenameList = NULL;
// Split the view into multiple windows if requested.
if (WIN_HOR == layout)
[self addInput:@"|sall"];
else if (WIN_VER == layout)
[self addInput:@"|vert sall"];
if (WIN_HOR == layout || WIN_VER == layout) {
vim_memset(&cmdmod, 0, sizeof(cmdmod));
if (WIN_VER == layout) {
// Convert :sall to :vert sall
cmdmod.split |= WSP_VERT;
}
char_u sallArg[] = "";
char_u sallCmdname[] = "sall";
// :sall / :vert sall
ea.arg = sallArg;
ea.cmd = sallCmdname;
ea.cmdidx = CMD_sall;
ex_all(&ea);
}
// Restore the global cmdmod.
cmdmod = save_cmdmod;
// Restore the old value of 'suffixes'.
[self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
} else {
p_su = orig_p_su;
} else { // numFiles == 1
typedef void (*ex_func_T) (exarg_T *eap);
ex_func_T exfunc = NULL;
char *cmdname = NULL;
cmdmod_T save_cmdmod = cmdmod;
vim_memset(&cmdmod, 0, sizeof(cmdmod));
// When opening one file we try to reuse the current window,
// but not if its buffer is modified or has a filename.
// However, the 'arglist' layout always opens the file in the
// current window.
NSString *file = [[filenames lastObject]
stringByEscapingSpecialFilenameCharacters];
NSString *cmd;
if (WIN_HOR == layout) {
if (!(bufHasFilename || bufChanged))
cmd = [NSString stringWithFormat:@":e %@", file];
else
cmd = [NSString stringWithFormat:@":sp %@", file];
if (!(bufHasFilename || bufChanged)) {
// :e <filename>
ea.cmdidx = CMD_edit;
exfunc = ex_edit;
cmdname = "edit";
} else {
// :sp <filename>
ea.cmdidx = CMD_split;
exfunc = ex_splitview;
cmdname = "spit";
}
} else if (WIN_VER == layout) {
if (!(bufHasFilename || bufChanged))
cmd = [NSString stringWithFormat:@":e %@", file];
else
cmd = [NSString stringWithFormat:@":vsp %@", file];
if (!(bufHasFilename || bufChanged)) {
// :e <filename>
ea.cmdidx = CMD_edit;
exfunc = ex_edit;
cmdname = "edit";
} else {
// :vsp <filename>
ea.cmdidx = CMD_vsplit;
exfunc = ex_splitview;
cmdname = "vsplit";
}
} else if (WIN_TABS == layout) {
if (oneWindowInTab && !(bufHasFilename || bufChanged))
cmd = [NSString stringWithFormat:@":e %@", file];
else
cmd = [NSString stringWithFormat:@":tabe %@", file];
if (oneWindowInTab && !(bufHasFilename || bufChanged)) {
// :e <filename>
ea.cmdidx = CMD_edit;
exfunc = ex_edit;
cmdname = "edit";
} else {
// :$tabedit <filename>
ea.cmdidx = CMD_tabedit;
exfunc = ex_splitview;
cmdname = "tabedit";
tabpage_T *tp;
int numTabs = 0;
FOR_ALL_TABPAGES(tp) {
numTabs += 1;
}
cmdmod.tab = numTabs + 1;
}
} else {
// (The :drop command will split if there is a modified
// buffer.)
cmd = [NSString stringWithFormat:@":drop %@", file];
// :drop <filename>
ea.cmdidx = CMD_drop;
exfunc = ex_drop;
cmdname = "drop";
}
[self addInput:cmd];
[self addInput:@"<CR>"];
char_u *cmdnameSaved = vim_strsave((char_u*)cmdname);
ea.cmd = cmdnameSaved;
NSString *file = [filenames lastObject];
char_u *filename = [file vimStringSave];
ea.arg = filename;
char_u *filenameEscaped = NULL;
if (ea.cmdidx == CMD_drop) {
// :drop works differently internally and we need to escape
// filenames first. Otherwise names like $filename will get
// substituted.
filenameEscaped = vim_strsave_fnameescape(filename, FALSE);
ea.arg = filenameEscaped;
}
if (exfunc != NULL) {
// Execute the Ex command that we have prepared. See notes
// about for why we do this instead of using addInput for
// processing file names.
exfunc(&ea);
}
cmdmod = save_cmdmod;
// Clean up temporary strings.
if (filenameEscaped) {
vim_free(filenameEscaped);
}
vim_free(filename);
vim_free(cmdnameSaved);
}
// Force screen redraw (does it have to be this complicated?).
@@ -2848,6 +3012,149 @@ extern GuiFont gui_mch_retain_font(GuiFont font);
}
}
// This will select the window/tab of a particular window and bring the window
// to focus. If the buffer is hidden, it will make a new tab/split to show it.
//
// It basically does the equivalent of the following:
// let oldswb=&swb | let &swb="useopen,usetab"
// buffer <filename>
// let &swb=oldswb |unl oldswb
// call foreground()
- (void)handleSelectAndFocusOpenedFile:(NSDictionary *)args
{
// ARGUMENT: DESCRIPTION:
// -------------------------------------------------------------
// filename filename of the opened file to focus
// layout which layout to use to open files
ASLogDebug(@"args=%@", args);
NSString *filename = [args objectForKey:@"filename"];
char_u *filename_vim = [filename vimStringSave];
int layout = [[args objectForKey:@"layout"] intValue];
// Does the following the make sure the buffer command will always go to an
// already opened buffer:
// :let oldswb=&swb|let &swb="useopen,usetab"
unsigned orig_swb_flags = swb_flags;
swb_flags = SWB_USEOPEN | SWB_USETAB;
// Need to first manually find the bufnr first as the ex_buffer() command
// already expects that to be provided.
int bufnr = buflist_findpat(
filename_vim, filename_vim + STRLEN(filename_vim), FALSE, FALSE, FALSE);
if (bufnr >= 0)
{
// :sb / :vert sb / :tab sb / :b <filename> (depending on layout)
// The layout will only matter if the buffer is hidden and needs to be
// opened in a new window.
exarg_T ea;
vim_memset(&ea, 0, sizeof(ea));
cmdmod_T save_cmdmod = cmdmod;
vim_memset(&cmdmod, 0, sizeof(cmdmod));
char_u bufferCmd[] = "buffer";
char_u sbufferCmd[] = "sbuffer";
if (WIN_HOR == layout) {
// :sb <filename>
ea.cmdidx = CMD_sbuffer;
ea.cmd = sbufferCmd;
} else if (WIN_VER == layout) {
// :vert sb <filename>
ea.cmdidx = CMD_sbuffer;
ea.cmd = sbufferCmd;
cmdmod.split |= WSP_VERT;
} else if (WIN_TABS == layout) {
// :tab sb <filename>
ea.cmdidx = CMD_sbuffer;
ea.cmd = sbufferCmd;
tabpage_T *tp;
int numTabs = 0;
FOR_ALL_TABPAGES(tp) {
numTabs += 1;
}
cmdmod.tab = numTabs + 1;
} else {
// :b <filename>
ea.cmdidx = CMD_buffer;
ea.cmd = bufferCmd;
}
ea.arg = (char_u *)"";
ea.addr_count = 1;
ea.line2 = bufnr;
goto_buffer(&ea, DOBUF_FIRST, FORWARD, (int)ea.line2);
cmdmod = save_cmdmod;
}
// Restore the old value of 'switchbuf' and command modifiers
swb_flags = orig_swb_flags;
// Same as :call foreground()
[self activate];
vim_free(filename_vim);
// Force screen redraw (see handleOpenWithArguments:).
update_screen(NOT_VALID);
setcursor();
out_flush();
gui_update_cursor(FALSE, FALSE);
maketitle();
}
// This handles the "New File" action. It basically does the following to set up
// a new buffer in the provided directory:
// :tabnew | :tcd <path>
//
// It uses :tcd as this way the whole tab gets the specified path as a basis
// without unncessarily using a global :cd which messes with the other files
// this instance was already editing.
- (void)handleNewFileHere:(NSDictionary *)args
{
// ARGUMENT: DESCRIPTION:
// -------------------------------------------------------------
// path path to make a new buffer
NSString *path = [args objectForKey:@"path"];
char_u *path_vim = [path vimStringSave];
ASLogDebug(@"path=%s", path_vim);
exarg_T ea;
vim_memset(&ea, 0, sizeof(ea));
// :tabnew
char_u tabnewArgs[] = "";
char_u tabnewCmd[] = "tabnew";
ea.arg = tabnewArgs;
ea.cmdidx = CMD_tabnew;
ea.cmd = tabnewCmd;
ex_splitview(&ea);
// :tcd <path>
char_u tcdCmd[] = "tcd";
ea.arg = path_vim;
ea.cmdidx = CMD_tcd;
ea.cmd = tabnewCmd;
ex_cd(&ea);
vim_free(path_vim);
// Force screen redraw (see handleOpenWithArguments:).
update_screen(NOT_VALID);
setcursor();
out_flush();
gui_update_cursor(FALSE, FALSE);
maketitle();
}
- (int)checkForModifiedBuffers
{
// Return 1 if current buffer is modified, -1 if other buffer is modified,
+33 -16
View File
@@ -296,33 +296,50 @@ static BOOL isUnsafeMessage(int msgid);
noteNewRecentFilePaths:filenames];
}
// This is called when a file is dragged on top of a tab. We will open the file
// list similar to drag-and-dropped files.
- (void)file:(NSString *)filename draggedToTabAtIndex:(NSUInteger)tabIndex
{
filename = normalizeFilename(filename);
ASLogInfo(@"filename=%@ index=%ld", filename, tabIndex);
NSString *fnEsc = [filename stringByEscapingSpecialFilenameCharacters];
NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>:silent "
"tabnext %ld |"
"edit! %@<CR>", tabIndex + 1, fnEsc];
[self addVimInput:input];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
// This is similar to dropFiles:forceOpen: except we first switch to the
// selected tab, and just open the first file (this could be modified in the
// future to support multiple files). It also forces layout to be splits
// because we specified one tab to receive the file so doesn't make sense to
// open another tab.
int layout = MMLayoutHorizontalSplit;
if ([ud boolForKey:MMVerticalSplitKey])
layout = MMLayoutVerticalSplit;
NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:layout], @"layout",
@[filename], @"filenames",
[NSNumber numberWithInt:tabIndex + 1], @"tabpage",
nil];
[self sendMessage:OpenWithArgumentsMsgID data:[args dictionaryAsData]];
}
// This is called when a file is dragged on top of the tab bar but not a
// particular tab (e.g. the new tab button). We will open the file list similar
// to drag-and-dropped files.
- (void)filesDraggedToTabBar:(NSArray *)filenames
{
filenames = normalizeFilenames(filenames);
ASLogInfo(@"%@", filenames);
NSUInteger i, count = [filenames count];
NSMutableString *input = [NSMutableString stringWithString:@"<C-\\><C-N>"
":silent! tabnext 9999"];
for (i = 0; i < count; i++) {
NSString *fn = [filenames objectAtIndex:i];
NSString *fnEsc = [fn stringByEscapingSpecialFilenameCharacters];
[input appendFormat:@"|tabedit %@", fnEsc];
}
[input appendString:@"<CR>"];
[self addVimInput:input];
// This is similar to dropFiles:forceOpen: except we just force layout to be
// tabs (since the receipient is the tab bar, we assume that's the
// intention) instead of loading from user defaults.
int layout = MMLayoutTabs;
NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:layout], @"layout",
filenames, @"filenames",
nil];
[self sendMessage:OpenWithArgumentsMsgID data:[args dictionaryAsData]];
}
- (void)dropString:(NSString *)string
+2 -1
View File
@@ -236,6 +236,8 @@ extern const char * const MMVimMsgIDStrings[];
MSG(SetVimStateMsgID) \
MSG(SetDocumentFilenameMsgID) \
MSG(OpenWithArgumentsMsgID) \
MSG(SelectAndFocusOpenedFileMsgID) \
MSG(NewFileHereMsgID) \
MSG(CloseWindowMsgID) \
MSG(SetFullScreenColorMsgID) \
MSG(ShowFindReplaceDialogMsgID) \
@@ -353,7 +355,6 @@ extern NSString *VimFindPboardType;
@interface NSString (MMExtras)
- (NSString *)stringByEscapingSpecialFilenameCharacters;
- (NSString *)stringByRemovingFindPatterns;
- (NSString *)stringBySanitizingSpotlightSearch;
@end
-37
View File
@@ -88,43 +88,6 @@ debugStringForMessageQueue(NSArray *queue)
@implementation NSString (MMExtras)
- (NSString *)stringByEscapingSpecialFilenameCharacters
{
// NOTE: This code assumes that no characters already have been escaped.
NSMutableString *string = [self mutableCopy];
[string replaceOccurrencesOfString:@"\\"
withString:@"\\\\"
options:NSLiteralSearch
range:NSMakeRange(0, [string length])];
[string replaceOccurrencesOfString:@" "
withString:@"\\ "
options:NSLiteralSearch
range:NSMakeRange(0, [string length])];
[string replaceOccurrencesOfString:@"\t"
withString:@"\\\t "
options:NSLiteralSearch
range:NSMakeRange(0, [string length])];
[string replaceOccurrencesOfString:@"%"
withString:@"\\%"
options:NSLiteralSearch
range:NSMakeRange(0, [string length])];
[string replaceOccurrencesOfString:@"#"
withString:@"\\#"
options:NSLiteralSearch
range:NSMakeRange(0, [string length])];
[string replaceOccurrencesOfString:@"|"
withString:@"\\|"
options:NSLiteralSearch
range:NSMakeRange(0, [string length])];
[string replaceOccurrencesOfString:@"\""
withString:@"\\\""
options:NSLiteralSearch
range:NSMakeRange(0, [string length])];
return [string autorelease];
}
- (NSString *)stringByRemovingFindPatterns
{
// Remove some common patterns added to search strings that other apps are
+1 -2
View File
@@ -131,7 +131,6 @@ static void ex_mode(exarg_T *eap);
static void ex_wrongmodifier(exarg_T *eap);
static void ex_find(exarg_T *eap);
static void ex_open(exarg_T *eap);
static void ex_edit(exarg_T *eap);
#ifndef FEAT_GUI
# define ex_gui ex_nogui
static void ex_nogui(exarg_T *eap);
@@ -7138,7 +7137,7 @@ ex_open(exarg_T *eap)
/*
* ":edit", ":badd", ":visual".
*/
static void
void
ex_edit(exarg_T *eap)
{
do_exedit(eap, NULL);
+1
View File
@@ -33,6 +33,7 @@ void alist_expand(int *fnum_list, int fnum_len);
void alist_set(alist_T *al, int count, char_u **files, int use_curbuf, int *fnum_list, int fnum_len);
void alist_add(alist_T *al, char_u *fname, int set_fnum);
void alist_slash_adjust(void);
void ex_edit(exarg_T *eap);
void ex_splitview(exarg_T *eap);
void tabpage_new(void);
void do_exedit(exarg_T *eap, win_T *old_curwin);