Compare commits

...

8 Commits

Author SHA1 Message Date
Lewis Russell d1b233a785
Merge a26a91727d into 435dee74bb 2024-04-26 22:56:15 +00:00
dundargoc a26a91727d build: simplify cmake code 2024-04-27 00:56:00 +02:00
zeertzjq 435dee74bb
vim-patch:9.1.0374: wrong botline in BufEnter (#28530)
Problem:  When :edit an existing buffer, line('w$') may return a
          wrong result.
Solution: Reset w_valid in curwin_init() (Jaehwang Jung)

`do_ecmd()` reinitializes the current window (`curwin_init()`) whose
`w_valid` field may have `VALID_BOTLINE` set. Resetting `w_botline`
without marking it as invalid makes subsequent `validate_botline()`
calls a no-op, thus resulting in wrong `line('w$')` value.

closes: vim/vim#14642

eb80b8304e

Co-authored-by: Jaehwang Jung <tomtomjhj@gmail.com>
2024-04-27 06:32:25 +08:00
zeertzjq 694756252b
Merge pull request #28529 from zeertzjq/vim-fe1e2b5e2d65
vim-patch: clarify syntax vs matching mechanism
2024-04-27 06:31:55 +08:00
zeertzjq e81eb34aa1 vim-patch:9525f6213604
runtime(doc): fix typo synconcealend -> synconcealed (vim/vim#14644)

9525f62136

Co-authored-by: Philip H <47042125+pheiduck@users.noreply.github.com>
2024-04-27 05:52:47 +08:00
zeertzjq a1568f5df0 vim-patch:00ae5c5cba7b
runtime(doc): fix typo

00ae5c5cba

Co-authored-by: Christian Brabandt <cb@256bit.org>
2024-04-27 05:52:15 +08:00
zeertzjq f1f5fb911b vim-patch:fe1e2b5e2d65
runtime(doc): clarify syntax vs matching mechanism

fixes: vim/vim#14643

fe1e2b5e2d

Co-authored-by: Christian Brabandt <cb@256bit.org>
2024-04-27 05:51:52 +08:00
Lewis Russell e53686fea3 feat(treesitter): add support for wasm parsers 2024-04-26 13:38:32 +02:00
26 changed files with 299 additions and 24 deletions

View File

@ -21,6 +21,17 @@ env:
INSTALL_PREFIX: ${{ github.workspace }}/nvim-install
jobs:
wasmtime:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: |
cmake -S cmake.deps --preset ci -D ENABLE_WASMTIME=ON
cmake --build .deps
cmake --preset ci -D ENABLE_WASMTIME=ON
cmake --build build
old-cmake:
name: Test oldest supported cmake
runs-on: ubuntu-22.04

View File

@ -130,6 +130,7 @@ else()
option(ENABLE_LTO "enable link time optimization" ON)
endif()
option(ENABLE_LIBINTL "enable libintl" ON)
option(ENABLE_WASMTIME "enable wasmtime" OFF)
message(STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")

View File

@ -37,6 +37,7 @@ option(USE_BUNDLED_MSGPACK "Use the bundled msgpack." ${USE_BUNDLED})
option(USE_BUNDLED_TS "Use the bundled treesitter runtime." ${USE_BUNDLED})
option(USE_BUNDLED_TS_PARSERS "Use the bundled treesitter parsers." ${USE_BUNDLED})
option(USE_BUNDLED_UNIBILIUM "Use the bundled unibilium." ${USE_BUNDLED})
if(USE_BUNDLED AND MSVC)
option(USE_BUNDLED_GETTEXT "Use the bundled version of gettext." ON)
option(USE_BUNDLED_LIBICONV "Use the bundled version of libiconv." ON)
@ -45,6 +46,19 @@ else()
option(USE_BUNDLED_LIBICONV "Use the bundled version of libiconv." OFF)
endif()
option(ENABLE_WASMTIME "Use treesitter with wasmtime support." OFF)
if(ENABLE_WASMTIME)
if(USE_BUNDLED)
option(USE_BUNDLED_WASMTIME "Use the bundled wasmtime." ON)
else()
option(USE_BUNDLED_WASMTIME "Use the bundled wasmtime." OFF)
endif()
endif()
if(NOT ENABLE_WASMTIME AND USE_BUNDLED_WASMTIME)
message(FATAL_ERROR "ENABLE_WASMTIME is set to OFF while USE_BUNDLED_WASMTIME is set to ON.\
You need set ENABLE_WASMTIME to ON if you want to use wasmtime.")
endif()
option(USE_EXISTING_SRC_DIR "Skip download of deps sources in case of existing source directory." OFF)
set_default_buildtype(Release)
@ -155,6 +169,10 @@ if(USE_BUNDLED_TS_PARSERS)
include(BuildTreesitterParsers)
endif()
if(USE_BUNDLED_WASMTIME)
include(BuildWasmtime)
endif()
if(USE_BUNDLED_TS)
include(BuildTreesitter)
endif()

View File

@ -17,7 +17,8 @@
"cacheVariables": {
"USE_BUNDLED":"OFF",
"USE_BUNDLED_LIBVTERM":"ON",
"USE_BUNDLED_TS":"ON"
"USE_BUNDLED_TS":"ON",
"ENABLE_WASMTIME":"OFF"
},
"inherits": ["base"]
}

View File

@ -1,8 +1,24 @@
if(ENABLE_WASMTIME)
if(USE_BUNDLED_WASMTIME)
set(WASMTIME_CACHE_ARGS "-DCMAKE_C_FLAGS:STRING=-I${DEPS_BUILD_DIR}/src/wasmtime/crates/c-api/wasm-c-api/include -I${DEPS_BUILD_DIR}/src/wasmtime/crates/c-api/include")
else()
find_package(Wasmtime REQUIRED)
set(WASMTIME_CACHE_ARGS "-DCMAKE_C_FLAGS:STRING=-I${WASMTIME_INCLUDE_DIR}")
endif()
string(APPEND WASMTIME_CACHE_ARGS " -DTREE_SITTER_FEATURE_WASM")
set(WASMTIME_ARGS -D CMAKE_C_STANDARD=11)
endif()
get_externalproject_options(treesitter ${DEPS_IGNORE_SHA})
ExternalProject_Add(treesitter
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/treesitter
PATCH_COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/cmake/TreesitterCMakeLists.txt
${DEPS_BUILD_DIR}/src/treesitter/CMakeLists.txt
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
CMAKE_ARGS ${DEPS_CMAKE_ARGS} ${WASMTIME_ARGS}
CMAKE_CACHE_ARGS ${WASMTIME_CACHE_ARGS}
${EXTERNALPROJECT_OPTIONS})
if(USE_BUNDLED_WASMTIME)
add_dependencies(treesitter wasmtime)
endif()

View File

@ -0,0 +1,8 @@
get_externalproject_options(wasmtime ${DEPS_IGNORE_SHA})
ExternalProject_Add(wasmtime
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/wasmtime
SOURCE_SUBDIR crates/c-api
USES_TERMINAL_BUILD TRUE
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
# CMAKE_CACHE_ARGS "WASMTIME_USER_CARGO_BUILD_OPTIONS:STRING=--target-dir ${DEPS_LIB_DIR}/wasmtime"
${EXTERNALPROJECT_OPTIONS})

View File

@ -9,7 +9,7 @@ add_compile_options(-w)
add_library(tree-sitter lib/src/lib.c)
target_include_directories(tree-sitter
PRIVATE lib/src lib/include)
PRIVATE lib/src lib/src/wasm lib/include)
install(FILES
lib/include/tree_sitter/api.h

View File

@ -59,3 +59,6 @@ TREESITTER_MARKDOWN_URL https://github.com/MDeiml/tree-sitter-markdown/archive/v
TREESITTER_MARKDOWN_SHA256 4909d6023643f1afc3ab219585d4035b7403f3a17849782ab803c5f73c8a31d5
TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.22.5.tar.gz
TREESITTER_SHA256 6bc22ca7e0f81d77773462d922cf40b44bfd090d92abac75cb37dbae516c2417
WASMTIME_URL https://github.com/bytecodealliance/wasmtime/archive/356710deed6db80697d5fb1c6a41233b81d12fee.tar.gz
WASMTIME_SHA256 3bb1329d9c9155a3147c9bb687686f9683dff6e443b21344c664d505dffc2b45

11
cmake/FindWasmtime.cmake Normal file
View File

@ -0,0 +1,11 @@
find_path2(WASMTIME_INCLUDE_DIR wasmtime.h)
find_library2(WASMTIME_LIBRARY wasmtime)
find_package_handle_standard_args(Wasmtime
REQUIRED_VARS WASMTIME_INCLUDE_DIR WASMTIME_LIBRARY)
add_library(wasmtime INTERFACE)
target_include_directories(wasmtime SYSTEM BEFORE INTERFACE ${WASMTIME_INCLUDE_DIR})
target_link_libraries(wasmtime INTERFACE ${WASMTIME_LIBRARY})
mark_as_advanced(WASMTIME_INCLUDE_DIR WASMTIME_LIBRARY)

View File

@ -8208,6 +8208,10 @@ synconcealed({lnum}, {col}) *synconcealed()*
synconcealed(lnum, 5) [1, 'X', 2]
synconcealed(lnum, 6) [0, '', 0]
Note: Doesn't consider |matchadd()| highlighting items,
since syntax and matching highlighting are two different
mechanisms |syntax-vs-match|.
synstack({lnum}, {col}) *synstack()*
Return a |List|, which is the stack of syntax items at the
position {lnum} and {col} in the current window. {lnum} is

View File

@ -1375,6 +1375,19 @@ Finally, these constructs are unique to Perl:
==============================================================================
10. Highlighting matches *match-highlight*
*syntax-vs-match*
Note that the match highlight mechanism is independent
of |syntax-highlighting|, which is (usually) a buffer-local
highlighting, while matching is window-local, both methods
can be freely mixed. Match highlighting functions give you
a bit more flexibility in when and how to apply, but are
typically only used for temporary highlighting, without strict
rules. Both methods can be used to conceal text.
Thus the matching functions like |matchadd()| won't consider
syntax rules and functions like |synconcealed()| and the
other way around.
*:mat* *:match*
:mat[ch] {group} /{pattern}/
Define a pattern to highlight in the current window. It will

View File

@ -3825,7 +3825,9 @@ Whether or not it is actually concealed depends on the value of the
'conceallevel' option. The 'concealcursor' option is used to decide whether
concealable items in the current line are displayed unconcealed to be able to
edit the line.
Another way to conceal text is with |matchadd()|.
Another way to conceal text is with |matchadd()|, but internally this works a
bit differently |syntax-vs-match|.
concealends *:syn-concealends*
@ -3833,7 +3835,9 @@ When the "concealends" argument is given, the start and end matches of
the region, but not the contents of the region, are marked as concealable.
Whether or not they are actually concealed depends on the setting on the
'conceallevel' option. The ends of a region can only be concealed separately
in this way when they have their own highlighting via "matchgroup"
in this way when they have their own highlighting via "matchgroup". The
|synconcealed()| function can be used to retrieve information about conealed
items.
cchar *:syn-cchar*
*E844*

View File

@ -36,6 +36,13 @@ treesitter parser for buffers with filetype `svg` or `xslt`, use: >lua
vim.treesitter.language.register('xml', { 'svg', 'xslt' })
<
*treesitter-parsers-wasm*
If Nvim is built with `ENABLE_WASMTIME`, then wasm parsers can also be
loaded: >lua
vim.treesitter.language.add('python', { path = "/path/to/python.wasm" })
<
==============================================================================
TREESITTER TREES *treesitter-tree*

View File

@ -948,7 +948,7 @@ Syntax and highlighting: *syntax-functions* *highlighting-functions*
synIDattr() get a specific attribute of a syntax ID
synIDtrans() get translated syntax ID
synstack() get list of syntax IDs at a specific position
synconcealed() get info about concealing
synconcealed() get info about (syntax) concealing
diff_hlID() get highlight ID for diff mode at a position
matchadd() define a pattern to highlight (a "match")
matchaddpos() define a list of positions to highlight

View File

@ -9752,6 +9752,10 @@ function vim.fn.synIDtrans(synID) end
--- synconcealed(lnum, 5) [1, 'X', 2]
--- synconcealed(lnum, 6) [0, '', 0]
---
--- Note: Doesn't consider |matchadd()| highlighting items,
--- since syntax and matching highlighting are two different
--- mechanisms |syntax-vs-match|.
---
--- @param lnum integer
--- @param col integer
--- @return {[1]: integer, [2]: string, [3]: integer}

View File

@ -69,7 +69,11 @@ vim._ts_get_language_version = function() end
--- @param path string
--- @param lang string
--- @param symbol_name? string
vim._ts_add_language = function(path, lang, symbol_name) end
vim._ts_add_language_from_object = function(path, lang, symbol_name) end
--- @param path string
--- @param lang string
vim._ts_add_language_from_wasm = function(path, lang) end
---@return integer
vim._ts_get_minimum_language_version = function() end

View File

@ -28,6 +28,9 @@ function M.check()
)
end
end
local can_wasm = vim._ts_add_language_from_wasm ~= nil
health.info(string.format('Can load WASM parsers: %s', tostring(can_wasm)))
end
return M

View File

@ -106,7 +106,14 @@ function M.add(lang, opts)
path = paths[1]
end
vim._ts_add_language(path, lang, symbol_name)
if vim.endswith(path, '.wasm') then
if not vim._ts_add_language_from_wasm then
error(string.format("Unable to load wasm parser '%s': not built with ENABLE_WASMTIME ", path))
end
vim._ts_add_language_from_wasm(path, lang)
else
vim._ts_add_language_from_object(path, lang, symbol_name)
end
M.register(lang, filetype)
end

View File

@ -50,6 +50,12 @@ if(ENABLE_LIBINTL)
target_link_libraries(main_lib INTERFACE libintl)
endif()
if(ENABLE_WASMTIME)
find_package(Wasmtime REQUIRED)
target_link_libraries(main_lib INTERFACE wasmtime)
target_compile_definitions(nvim_bin PRIVATE HAVE_WASMTIME)
endif()
target_compile_definitions(main_lib INTERFACE HAVE_UNIBILIUM)
# The unit test lib requires LuaJIT; it will be skipped if LuaJIT is missing.

View File

@ -47,8 +47,8 @@ typedef struct {
#define VALID_VIRTCOL 0x04 // w_virtcol (file col) is valid
#define VALID_CHEIGHT 0x08 // w_cline_height and w_cline_folded valid
#define VALID_CROW 0x10 // w_cline_row is valid
#define VALID_BOTLINE 0x20 // w_botine and w_empty_rows are valid
#define VALID_BOTLINE_AP 0x40 // w_botine is approximated
#define VALID_BOTLINE 0x20 // w_botline and w_empty_rows are valid
#define VALID_BOTLINE_AP 0x40 // w_botline is approximated
#define VALID_TOPLINE 0x80 // w_topline is valid (for cursor position)
// flags for b_flags

View File

@ -11621,6 +11621,10 @@ M.funcs = {
synconcealed(lnum, 4) [1, 'X', 2]
synconcealed(lnum, 5) [1, 'X', 2]
synconcealed(lnum, 6) [0, '', 0]
Note: Doesn't consider |matchadd()| highlighting items,
since syntax and matching highlighting are two different
mechanisms |syntax-vs-match|.
]=],
name = 'synconcealed',
params = { { 'lnum', 'integer' }, { 'col', 'integer' } },

View File

@ -923,6 +923,7 @@ void nlua_free_all_mem(void)
lua_State *lstate = global_lstate;
nlua_unref_global(lstate, require_ref);
nlua_common_free_all_mem(lstate);
tslua_free();
}
static void nlua_common_free_all_mem(lua_State *lstate)
@ -1901,8 +1902,13 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, tslua_push_querycursor);
lua_setfield(lstate, -2, "_create_ts_querycursor");
lua_pushcfunction(lstate, tslua_add_language);
lua_setfield(lstate, -2, "_ts_add_language");
lua_pushcfunction(lstate, tslua_add_language_from_object);
lua_setfield(lstate, -2, "_ts_add_language_from_object");
#ifdef HAVE_WASMTIME
lua_pushcfunction(lstate, tslua_add_language_from_wasm);
lua_setfield(lstate, -2, "_ts_add_language_from_wasm");
#endif
lua_pushcfunction(lstate, tslua_has_language);
lua_setfield(lstate, -2, "_ts_has_language");

View File

@ -15,6 +15,10 @@
#include <tree_sitter/api.h>
#include <uv.h>
#ifdef HAVE_WASMTIME
# include <wasm.h>
#endif
#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
#include "nvim/buffer_defs.h"
@ -24,6 +28,7 @@
#include "nvim/map_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/os/fs.h"
#include "nvim/pos_defs.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
@ -53,6 +58,11 @@ typedef struct {
static PMap(cstr_t) langs = MAP_INIT;
#ifdef HAVE_WASMTIME
static wasm_engine_t *wasmengine;
static TSWasmStore *ts_wasmstore;
#endif
// TSLanguage
int tslua_has_language(lua_State *L)
@ -62,8 +72,59 @@ int tslua_has_language(lua_State *L)
return 1;
}
static TSLanguage *load_language(lua_State *L, const char *path, const char *lang_name,
const char *symbol)
#ifdef HAVE_WASMTIME
static char *read_file(const char *path, size_t *len)
FUNC_ATTR_MALLOC
{
FILE *file = os_fopen(path, "r");
if (file == NULL) {
return NULL;
}
fseek(file, 0L, SEEK_END);
*len = (size_t)ftell(file);
fseek(file, 0L, SEEK_SET);
char *data = xmalloc(*len);
if (fread(data, *len, 1, file) != 1) {
xfree(data);
fclose(file);
return NULL;
}
fclose(file);
return data;
}
static const char *wasmerr_to_str(TSWasmErrorKind werr)
{
switch (werr) {
case TSWasmErrorKindParse:
return "PARSE";
case TSWasmErrorKindCompile:
return "COMPILE";
case TSWasmErrorKindInstantiate:
return "INSTANTIATE";
case TSWasmErrorKindAllocate:
return "ALLOCATE";
default:
return "UNKNOWN";
}
}
#endif
int tslua_add_language_from_wasm(lua_State *L)
{
return add_language(L, true);
}
// Creates the language into the internal language map.
//
// Returns true if the language is correctly loaded in the language map
int tslua_add_language_from_object(lua_State *L)
{
return add_language(L, false);
}
static const TSLanguage *load_language_from_object(lua_State *L, const char *path,
const char *lang_name, const char *symbol)
{
uv_lib_t lib;
if (uv_dlopen(path, &lib)) {
@ -91,16 +152,58 @@ static TSLanguage *load_language(lua_State *L, const char *path, const char *lan
return lang;
}
// Creates the language into the internal language map.
//
// Returns true if the language is correctly loaded in the language map
int tslua_add_language(lua_State *L)
static const TSLanguage *load_language_from_wasm(lua_State *L, const char *path,
const char *lang_name)
{
#ifndef HAVE_WASMTIME
luaL_error(L, "Not supported");
return NULL;
#else
if (wasmengine == NULL) {
wasmengine = wasm_engine_new();
}
assert(wasmengine != NULL);
TSWasmError werr = { 0 };
if (ts_wasmstore == NULL) {
ts_wasmstore = ts_wasm_store_new(wasmengine, &werr);
}
if (werr.kind > 0) {
luaL_error(L, "Error creating wasm store: (%s) %s", wasmerr_to_str(werr.kind), werr.message);
}
size_t file_size = 0;
char *data = read_file(path, &file_size);
if (data == NULL) {
luaL_error(L, "Unable to read file", path);
}
const TSLanguage *lang = ts_wasm_store_load_language(ts_wasmstore, lang_name, data,
(uint32_t)file_size, &werr);
xfree(data);
if (werr.kind > 0) {
luaL_error(L, "Error loading wasm parser: (%s) %s", wasmerr_to_str(werr.kind), werr.message);
}
if (lang == NULL) {
luaL_error(L, "Failed to load parser %s: internal error", path);
}
return lang;
#endif
}
static int add_language(lua_State *L, bool is_wasm)
{
const char *path = luaL_checkstring(L, 1);
const char *lang_name = luaL_checkstring(L, 2);
const char *symbol_name = lang_name;
if (lua_gettop(L) >= 3 && !lua_isnil(L, 3)) {
if (!is_wasm && lua_gettop(L) >= 3 && !lua_isnil(L, 3)) {
symbol_name = luaL_checkstring(L, 3);
}
@ -109,7 +212,9 @@ int tslua_add_language(lua_State *L)
return 1;
}
TSLanguage *lang = load_language(L, path, lang_name, symbol_name);
const TSLanguage *lang = is_wasm
? load_language_from_wasm(L, path, lang_name)
: load_language_from_object(L, path, lang_name, symbol_name);
uint32_t lang_version = ts_language_version(lang);
if (lang_version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION
@ -121,7 +226,7 @@ int tslua_add_language(lua_State *L)
TREE_SITTER_LANGUAGE_VERSION, lang_version);
}
pmap_put(cstr_t)(&langs, xstrdup(lang_name), lang);
pmap_put(cstr_t)(&langs, xstrdup(lang_name), (TSLanguage *)lang);
lua_pushboolean(L, true);
return 1;
@ -186,6 +291,9 @@ int tslua_inspect_lang(lua_State *L)
lua_setfield(L, -2, "fields"); // [retval]
lua_pushboolean(L, ts_language_is_wasm(lang));
lua_setfield(L, -2, "_wasm");
lua_pushinteger(L, ts_language_version(lang)); // [retval, version]
lua_setfield(L, -2, "_abi_version");
@ -215,6 +323,13 @@ int tslua_push_parser(lua_State *L)
TSParser **parser = lua_newuserdata(L, sizeof(TSParser *));
*parser = ts_parser_new();
#ifdef HAVE_WASMTIME
if (ts_language_is_wasm(lang)) {
assert(wasmengine != NULL);
ts_parser_set_wasm_store(*parser, ts_wasmstore);
}
#endif
if (!ts_parser_set_language(*parser, lang)) {
ts_parser_delete(*parser);
const char *lang_name = luaL_checkstring(L, 1);
@ -1515,3 +1630,15 @@ void tslua_init(lua_State *L)
ts_set_allocator(xmalloc, xcalloc, xrealloc, xfree);
}
void tslua_free(void)
{
#ifdef HAVE_WASMTIME
if (wasmengine != NULL) {
wasm_engine_delete(wasmengine);
}
if (ts_wasmstore != NULL) {
ts_wasm_store_delete(ts_wasmstore);
}
#endif
}

View File

@ -2467,6 +2467,7 @@ void win_init_empty(win_T *wp)
wp->w_topline = 1;
wp->w_topfill = 0;
wp->w_botline = 2;
wp->w_valid = 0;
wp->w_s = &wp->w_buffer->b_s;
}

View File

@ -57,8 +57,12 @@ describe('treesitter language API', function()
local keys, fields, symbols = unpack(exec_lua([[
local lang = vim.treesitter.language.inspect('c')
local keys, symbols = {}, {}
for k,_ in pairs(lang) do
keys[k] = true
for k, v in pairs(lang) do
if type(v) == 'boolean' then
keys[k] = v
else
keys[k] = true
end
end
-- symbols array can have "holes" and is thus not a valid msgpack array
@ -69,7 +73,7 @@ describe('treesitter language API', function()
return {keys, lang.fields, symbols}
]]))
eq({ fields = true, symbols = true, _abi_version = true }, keys)
eq({ fields = true, symbols = true, _abi_version = true, _wasm = false }, keys)
local fset = {}
for _, f in pairs(fields) do

View File

@ -4105,4 +4105,16 @@ func Test_SwapExists_set_other_buf_modified()
bwipe!
endfunc
func Test_BufEnter_botline()
set hidden
call writefile(range(10), 'Xxx1', 'D')
call writefile(range(20), 'Xxx2', 'D')
edit Xxx1
edit Xxx2
au BufEnter Xxx1 call assert_true(line('w$') > 1)
edit Xxx1
au! BufEnter Xxx1
set hidden&vim
endfunc
" vim: shiftwidth=2 sts=2 expandtab