mirror of
https://github.com/leafOfTree/vim-vue-plugin.git
synced 2026-11-01 18:12:19 +08:00
Merge pull request #41 from leafOfTree/fix-sync-and-tests
Fix syntax sync and end tag, highlight <script setup> macros, add tests
This commit is contained in:
40 files changed
+1263
-36
No files matched your search
@@ -0,0 +1,15 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Vim and Neovim
|
||||
run: sudo apt-get update && sudo apt-get install -y vim neovim
|
||||
- name: Run tests
|
||||
run: ./test/run.sh
|
||||
@@ -1,3 +1,4 @@
|
||||
README.html
|
||||
test.vue
|
||||
.DS_STORE
|
||||
test/deps/
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
dist: focal
|
||||
|
||||
language: minimal
|
||||
|
||||
before_install:
|
||||
- sudo add-apt-repository ppa:neovim-ppa/stable -y
|
||||
- sudo apt-get update
|
||||
- sudo apt-get -y install neovim
|
||||
|
||||
install:
|
||||
- git clone https://github.com/leafOfTree/test ../test --depth=1
|
||||
|
||||
- git clone https://github.com/digitaltoad/vim-pug ../vim-pug --depth=1
|
||||
- git clone https://github.com/kchmck/vim-coffee-script ../vim-coffee-script --depth=1
|
||||
- git clone https://github.com/leafgarland/typescript-vim ../typescript-vim --depth=1
|
||||
- git clone https://github.com/iloginow/vim-stylus.git ../vim-stylus --depth=1
|
||||
|
||||
script:
|
||||
- bash ../test/start.sh vue vim-vue-plugin
|
||||
@@ -37,14 +37,14 @@ You could install it just like other plugins. The filetype will be set to `vue`.
|
||||
:PlugInstall
|
||||
```
|
||||
|
||||
- Or manually, clone this plugin to `path/to/this_plugin`, and add it to `rtp` in vimrc
|
||||
- Or manually, clone this plugin to `path/to/this_plugin`, and add it to the start of `rtp` in vimrc. Vim 9 and Neovim come with placeholder `vue` syntax and indent files, which are used instead if this plugin is added to the end by `set rtp+=`.
|
||||
|
||||
```vim
|
||||
set rtp+=path/to/this_plugin
|
||||
set rtp^=path/to/this_plugin
|
||||
|
||||
" If filetype is not set to 'vue', try
|
||||
filetype off
|
||||
set rtp+=path/to/this_plugin
|
||||
set rtp^=path/to/this_plugin
|
||||
filetype plugin indent on
|
||||
```
|
||||
<br />
|
||||
@@ -208,6 +208,24 @@ For example, the built-in syntax `sass.vim` and `less.vim` in vim8.1 runtime and
|
||||
+ endif
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Run tests with Vim and Neovim, whichever is installed
|
||||
|
||||
```sh
|
||||
./test/run.sh
|
||||
./test/run.sh basic template # Only these cases
|
||||
```
|
||||
|
||||
Each case is a `.vue` or `.wpy` file in [test/cases](test/cases), with an optional `.vim` file for the config and checks. A case fails if
|
||||
|
||||
- indenting the whole file changes it
|
||||
- with `g:test_flatten`, indenting the file after removing all indent doesn't restore it
|
||||
- a check in `Check()` fails, such as syntax, `GetVueTag()`, `GetVueSyntax()` or fold level
|
||||
- there is an error message
|
||||
|
||||
Tests also run on GitHub Actions for each push and pull request.
|
||||
|
||||
## Acknowledgments & Refs
|
||||
|
||||
- [mxw/vim-jsx][1]
|
||||
|
||||
+3
-2
@@ -143,8 +143,9 @@ function! s:SyntaxListAtEnd(lnum)
|
||||
let plnum = prevnonblank(a:lnum)
|
||||
let pline = getline(plnum)
|
||||
|
||||
" return [] if prevnonblank is an end tag
|
||||
if pline =~ '^<\/'
|
||||
" return [] if prevnonblank is an end tag, e.g. for blank lines between
|
||||
" blocks, but not for the end tag line itself
|
||||
if plnum != a:lnum && pline =~ '^<\/'
|
||||
return []
|
||||
endif
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ function! s:GetMatchOption()
|
||||
endfunction
|
||||
|
||||
" Vue keywords as option key
|
||||
let s:vue_keywords = 'name parent functional delimiters comments components directives filters extends mixins inheritAttrs model props propsData data computed watch methods template render renderError inject provide beforeCreate created beforeMount mounted beforeUpdate updated activated deactivated beforeDestroy destroyed setup beforeUnmount unmounted errorCaptured renderTracked renderTriggered'
|
||||
let s:vue_keywords = 'name parent functional delimiters comments components directives filters extends mixins inheritAttrs model props propsData data computed watch methods template render renderError inject provide beforeCreate created beforeMount mounted beforeUpdate updated activated deactivated beforeDestroy destroyed setup beforeUnmount unmounted errorCaptured renderTracked renderTriggered emits expose slots compilerOptions serverPrefetch'
|
||||
|
||||
let s:indent = s:GetIndent()
|
||||
let s:keywords_regexp = '\v^\s{'.s:indent.'}(async )?<('
|
||||
@@ -61,21 +61,26 @@ execute 'syntax match vueObjectFuncKey display /'
|
||||
\.s:match_option
|
||||
\.' nextgroup=jsFuncArgs'
|
||||
|
||||
" Vue3 keywords as API, https://v3.vuejs.org/api/
|
||||
let s:basic_reactive = 'reactive readonly isProxy isReactive isReadonly toRaw markRaw shallowReactive shallowReadonly'
|
||||
let s:refs = 'ref unref toRef toRefs isRef customRef shallowRef triggerRef'
|
||||
let s:computed_and_watch = 'computed watchEffect watchPostEffect watchSyncEffect watch'
|
||||
let s:composition = 'setup onBeforeMount onMounted onBeforeUpdate onUpdated onBeforeUnmount onUnmounted onErrorCaptured onRenderTracked onRenderTriggered onActivated onDeactivated getCurrentInstance InjectionKey provide inject'
|
||||
let s:global = 'createApp h defineComponent defineAsyncComponent defineCustomElement resolveComponent resolveDynamicComponent resolveDirective withDirectives createRenderer nextTick mergeProps useCssModule'
|
||||
let s:vue3_keywords = join([s:basic_reactive, s:refs, s:computed_and_watch, s:composition, s:global], ' ')
|
||||
" Vue3 keywords as API, https://vuejs.org/api/
|
||||
let s:basic_reactive = 'reactive readonly isProxy isReactive isReadonly isShallow toRaw markRaw shallowReactive shallowReadonly'
|
||||
let s:refs = 'ref unref toRef toValue toRefs isRef customRef shallowRef triggerRef'
|
||||
let s:computed_and_watch = 'computed watchEffect watchPostEffect watchSyncEffect watch onWatcherCleanup'
|
||||
let s:effect_scope = 'effectScope getCurrentScope onScopeDispose'
|
||||
let s:composition = 'setup onBeforeMount onMounted onBeforeUpdate onUpdated onBeforeUnmount onUnmounted onErrorCaptured onRenderTracked onRenderTriggered onActivated onDeactivated onServerPrefetch getCurrentInstance InjectionKey provide inject hasInjectionContext'
|
||||
let s:helpers = 'useAttrs useSlots useModel useTemplateRef useId useCssModule useHost useShadowRoot useSSRContext'
|
||||
" Compiler macros in <script setup>
|
||||
let s:script_setup = 'defineProps defineEmits defineModel defineExpose defineOptions defineSlots withDefaults'
|
||||
let s:global = 'createApp createSSRApp h defineComponent defineAsyncComponent defineCustomElement resolveComponent resolveDynamicComponent resolveDirective withDirectives withModifiers createRenderer nextTick mergeProps cloneVNode isVNode'
|
||||
let s:vue3_keywords = join([s:basic_reactive, s:refs, s:computed_and_watch, s:effect_scope, s:composition, s:helpers, s:script_setup, s:global], ' ')
|
||||
|
||||
let s:vue3_keywords_regexp = '\v<('
|
||||
\.join(split(s:vue3_keywords, ' '), '|')
|
||||
\.')\ze'
|
||||
|
||||
" Followed by ( or type arguments like defineProps<{ foo: string }>()
|
||||
execute 'syntax match vue3Keyword display /'
|
||||
\.s:vue3_keywords_regexp
|
||||
\.'\_s*\(/'
|
||||
\.'(\_s*\(|\<)/'
|
||||
\.s:match_option
|
||||
|
||||
highlight default link vueObjectKey vueObjectKeyword
|
||||
|
||||
+3
-3
@@ -124,9 +124,6 @@ function! s:SetSyntax(block, syntax, has_lang)
|
||||
\.' end=+'.end.'+'
|
||||
\.' keepend contains='.syntax_group.', vueTag'
|
||||
|
||||
execute 'syntax sync match vueSync groupthere '.name.' +'.start.'+'
|
||||
execute 'syntax sync match vueSync groupthere NONE +'.end.'+'
|
||||
|
||||
" Block like <script src="...">...</script>
|
||||
let oneline = start.'.*'.end_tag
|
||||
execute 'syntax match '.name.' fold '
|
||||
@@ -135,7 +132,10 @@ function! s:SetSyntax(block, syntax, has_lang)
|
||||
endfunction
|
||||
|
||||
function! s:SetBlockSyntax(config_syntax)
|
||||
" Parse from start, as syncing by block start doesn't restore states inside
|
||||
" the block like a multi-line css rule
|
||||
syntax sync clear
|
||||
syntax sync fromstart
|
||||
|
||||
for [block, syntax] in items(a:config_syntax)
|
||||
let type = type(syntax)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
" Without attribute and keyword highlight
|
||||
let g:vim_vue_plugin_config = {'attribute': 0, 'keyword': 0}
|
||||
let g:test_flatten = 1
|
||||
|
||||
function! Check()
|
||||
call ExpectSyntax('@click', 'VueKey')
|
||||
call ExpectSyntax('toggle', 'VueValue')
|
||||
call ExpectNoSyntax('$t', 'VueInject')
|
||||
call ExpectNoSyntax('1"', 'javaScriptNumber')
|
||||
call ExpectSyntax('message', 'VueExpression')
|
||||
call ExpectEqual(matchstr(execute('highlight VueKey'), 'links to \zs\S\+'),
|
||||
\ 'htmlArg', 'VueKey link')
|
||||
call ExpectNoSyntax('data', 'vueObjectFuncName')
|
||||
endfunction
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div @click="toggle(item)" :title="$t('title')" v-if="count > 1">
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,26 @@
|
||||
" Template syntax with attribute highlight
|
||||
let g:vim_vue_plugin_config = {'attribute': 1}
|
||||
let g:test_flatten = 1
|
||||
|
||||
function! Check()
|
||||
" Directives
|
||||
call ExpectSyntax(':class', 'VueKey')
|
||||
call ExpectSyntax('@click', 'VueKey')
|
||||
call ExpectSyntax('v-if', 'VueKey')
|
||||
" Values as expressions
|
||||
call ExpectSyntax('1"', 'javaScriptNumber')
|
||||
call ExpectSyntax('$t', 'VueInject')
|
||||
call ExpectSyntax("'title'", 'javaScriptStringS')
|
||||
" Components and attributes
|
||||
normal! gg
|
||||
call ExpectSyntax('MyComponent', 'VueComponentName')
|
||||
call ExpectSyntax('ref=', 'htmlArg')
|
||||
call ExpectSyntax('data-id', 'htmlArg')
|
||||
call ExpectSyntax('my-component', 'VueComponentName')
|
||||
call ExpectSyntax('transition', 'VueComponentName')
|
||||
" Interpolation
|
||||
call ExpectSyntax('{{', 'VueBrace')
|
||||
call ExpectSyntax('message', 'VueExpression')
|
||||
call ExpectEqual(matchstr(execute('highlight VueKey'), 'links to \zs\S\+'),
|
||||
\ 'Type', 'VueKey link')
|
||||
endfunction
|
||||
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div :class="{ active: isActive }" @click="toggle(item)" v-if="count > 1">
|
||||
<MyComponent ref="comp" data-id="1" :title="$t('title')" />
|
||||
<my-component v-model="text"></my-component>
|
||||
<transition name="fade">
|
||||
<p>{{ message.toUpperCase() }}</p>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
count: 0,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,19 @@
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'script': ['javascript'],
|
||||
\ 'template': ['html'],
|
||||
\ 'style': ['css'],
|
||||
\},
|
||||
\}
|
||||
let g:test_flatten = 1
|
||||
packadd matchit
|
||||
|
||||
" Jump between tags with % by matchit. Use :normal for its mapping
|
||||
function! Check()
|
||||
for [lnum, col, expected] in [[1, 2, [3, '/template']], [2, 4, [2, '/p']]]
|
||||
call cursor(lnum, col)
|
||||
normal %
|
||||
call ExpectEqual([line('.'), matchstr(getline('.'), '\%'.col('.').'c/\w\+')],
|
||||
\ expected, printf('after %% from line %d col %d', lnum, col))
|
||||
endfor
|
||||
endfunction
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<p>{{ greeting }}</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
greeting: 'Hello'
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
" OnChangeVueSyntax() is called when syntax under cursor changes. It's
|
||||
" defined before the plugin is loaded
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'template': ['html'],
|
||||
\ 'script': ['javascript', 'typescript'],
|
||||
\ 'style': ['css', 'scss'],
|
||||
\},
|
||||
\}
|
||||
let g:test_changes = []
|
||||
|
||||
function! OnChangeVueSyntax(syntax)
|
||||
call add(g:test_changes, a:syntax)
|
||||
endfunction
|
||||
|
||||
function! Check()
|
||||
for lnum in [2, 3, 6, 6, 10, 2]
|
||||
call cursor(lnum, 1)
|
||||
doautocmd CursorMoved
|
||||
endfor
|
||||
call ExpectEqual(g:test_changes, ['html', 'typescript', 'scss', 'html'],
|
||||
\ 'syntax changes')
|
||||
call ExpectEqual(GetVueSyntax(), 'html', 'GetVueSyntax()')
|
||||
call ExpectTag(6, 'script')
|
||||
call ExpectTag(10, 'style')
|
||||
endfunction
|
||||
let g:test_flatten = 1
|
||||
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
const a: number = 1
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$a: 1px;
|
||||
</style>
|
||||
@@ -0,0 +1,40 @@
|
||||
" Uses syntax plugins in test/deps
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'template': ['html', 'pug'],
|
||||
\ 'script': ['javascript', 'typescript', 'coffee'],
|
||||
\ 'style': ['css', 'scss', 'sass', 'less', 'stylus'],
|
||||
\ 'docs': 'markdown',
|
||||
\ 'i18n': ['json', 'yaml'],
|
||||
\ 'route': 'json',
|
||||
\},
|
||||
\'attribute': 1,
|
||||
\'keyword': 1,
|
||||
\'foldexpr': 1,
|
||||
\'full_syntax': ['scss', 'less'],
|
||||
\}
|
||||
|
||||
" Block tag, syntax by lang="..." or the first in config, and block group
|
||||
" with css prefix for emmet-vim
|
||||
function! Check()
|
||||
call ExpectBlock('div.set', 'template', 'pug', 'pugTemplateBlock')
|
||||
call ExpectBlock('<template></template>', 'template', 'html', 'htmlTemplateBlock')
|
||||
call ExpectBlock('export default', 'script', 'javascript', 'javascriptScriptBlock')
|
||||
call ExpectBlock('import Vue', 'script', 'typescript', 'typescriptScriptBlock')
|
||||
call ExpectBlock('# Assignment', 'script', 'coffee', 'coffeeScriptBlock')
|
||||
call ExpectBlock('p {', 'style', 'css', 'cssStyleBlock')
|
||||
call ExpectBlock('$a: 100px', 'style', 'scss', 'cssScssStyleBlock')
|
||||
call ExpectBlock('$w: 128px', 'style', 'sass', 'sassStyleBlock')
|
||||
call ExpectBlock('@list: 10 12', 'style', 'less', 'cssLessStyleBlock')
|
||||
call ExpectBlock("@import 'vendor'", 'style', 'stylus', 'cssStylusStyleBlock')
|
||||
call ExpectBlock('"requiresAuth"', 'route', 'json', 'jsonRouteBlock')
|
||||
call ExpectBlock('# This is', 'docs', 'markdown', 'markdownDocsBlock')
|
||||
call ExpectBlock('en:', 'i18n', 'yaml', 'yamlI18nBlock')
|
||||
call ExpectBlock('"en"', 'i18n', 'json', 'jsonI18nBlock')
|
||||
|
||||
" Syntax from a sync point, as after jumping to the end
|
||||
silent edit!
|
||||
normal! G
|
||||
call search('"en"', 'bW')
|
||||
call ExpectEqual(GetVueSyntax(), 'json', 'syntax at the end after reload')
|
||||
endfunction
|
||||
@@ -0,0 +1,160 @@
|
||||
<template lang="pug">
|
||||
div.set
|
||||
span(id="123")
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<template></template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<p>{{ greeting }}</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
greeting: 'Hello'
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import { User } from '@/types'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'Home' as string,
|
||||
data() {
|
||||
return {
|
||||
user: {} as User
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fullName(): string { // Computed Property returns a string
|
||||
return `${this.user.firstName} ${this.user.lastName}`
|
||||
}
|
||||
},
|
||||
mounted(): void { // The mounted hook returns nothing
|
||||
...
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="coffee">
|
||||
# Assignment:
|
||||
number = 42
|
||||
opposite = true
|
||||
|
||||
# Conditions:
|
||||
number = -42 if opposite
|
||||
|
||||
# Functions:
|
||||
square = (x) -> x * x
|
||||
|
||||
# Arrays:
|
||||
list = [1, 2, 3, 4, 5]
|
||||
|
||||
`
|
||||
function hello() {}
|
||||
console.log
|
||||
a = 123;
|
||||
`
|
||||
# Objects:
|
||||
math =
|
||||
root: Math.sqrt
|
||||
square: square
|
||||
cube: (x) -> x * square x
|
||||
|
||||
# Splats:
|
||||
race = (winner, runners...) ->
|
||||
print winner, runners
|
||||
|
||||
# Existence:
|
||||
alert "I knew it!" if elvis?
|
||||
|
||||
# Array comprehensions:
|
||||
cubes = (math.cube num for num in list)
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
$a: 100px;
|
||||
|
||||
div {
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="sass">
|
||||
$w: 128px;
|
||||
|
||||
.bg
|
||||
$w: 128px;
|
||||
background: white;
|
||||
align-items: center;
|
||||
|
||||
canvas
|
||||
width: 100vw;
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
@list: 10 12;
|
||||
canvas {
|
||||
background: white;
|
||||
width: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="stylus">
|
||||
@import 'vendor'
|
||||
|
||||
body
|
||||
font: 12px Helvetica, Arial, sans-serif
|
||||
|
||||
a.button
|
||||
border-radius: 5px
|
||||
</style>
|
||||
|
||||
<route>
|
||||
{
|
||||
"name": "home",
|
||||
"meta": {
|
||||
"requiresAuth": true
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<docs>
|
||||
# This is the documentation for component.
|
||||
</docs>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en:
|
||||
hello: "Hello World!"
|
||||
ja:
|
||||
hello: "こんにちは、世界!"
|
||||
</i18n>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"language": "Language",
|
||||
"hello": "hello, world!"
|
||||
},
|
||||
"ja": {
|
||||
"language": "言語",
|
||||
"hello": "こんにちは、世界!"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
@@ -0,0 +1,16 @@
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'script': ['javascript'],
|
||||
\ 'template': ['html'],
|
||||
\ 'style': ['css'],
|
||||
\},
|
||||
\'foldexpr': 1,
|
||||
\}
|
||||
|
||||
function! Check()
|
||||
let levels = [1, 2, 1, 1, 1, 2, 3, 4, 4, 3, 3, 1, 1, 1, 1, 1, 2, 2, 1, 1]
|
||||
for lnum in range(1, len(levels))
|
||||
call ExpectFoldlevel(lnum, levels[lnum - 1])
|
||||
endfor
|
||||
endfunction
|
||||
let g:test_flatten = 1
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<p>{{ greeting }}</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
greeting: 'Hello'
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'template': ['html'],
|
||||
\ 'script': ['javascript'],
|
||||
\ 'style': ['css', 'sass', 'scss'],
|
||||
\ 'codepen-additional': ['javascript', 'json'],
|
||||
\},
|
||||
\'initial_indent': ['script', 'style', 'codepen-additional'],
|
||||
\}
|
||||
let g:test_flatten = 1
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<p>{{ greeting }}</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
greeting: 'Hello'
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
p {
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<codepen-additional>
|
||||
const easings = {
|
||||
linear: '',
|
||||
easeInQuad: '',
|
||||
}
|
||||
</codepen-additional>
|
||||
@@ -0,0 +1,10 @@
|
||||
" initial_indent as `syntax` and `tag.syntax`
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'script': ['javascript'],
|
||||
\ 'style': ['css', 'scss'],
|
||||
\ 'i18n': ['json', 'yaml'],
|
||||
\},
|
||||
\'initial_indent': ['scss', 'i18n.json'],
|
||||
\}
|
||||
let g:test_flatten = 1
|
||||
@@ -0,0 +1,28 @@
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.a {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.b {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"hello": "Hello"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en:
|
||||
hello: Hello
|
||||
</i18n>
|
||||
@@ -0,0 +1,17 @@
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'template': ['html'],
|
||||
\ 'script': ['javascript', 'typescript'],
|
||||
\},
|
||||
\'keyword': 1,
|
||||
\'initial_indent': ['script', 'style', 'codepen-additional'],
|
||||
\}
|
||||
|
||||
function! Check()
|
||||
call ExpectSyntax('name', 'vueObjectKey')
|
||||
call ExpectSyntax('data', 'vueObjectFuncKey')
|
||||
call ExpectSyntax('data', 'vueObjectFuncName')
|
||||
call ExpectSyntax('computed', 'vueObjectKey')
|
||||
call ExpectSyntax('mounted', 'vueObjectFuncName')
|
||||
endfunction
|
||||
let g:test_flatten = 1
|
||||
@@ -0,0 +1,45 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'Home' as string,
|
||||
data: function () {
|
||||
return {
|
||||
greeting: 'Hello'
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
greeting: 'Hello'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
fullName() { // Computed Property returns a string
|
||||
return `${this.user.firstName} ${this.user.lastName}`
|
||||
}
|
||||
},
|
||||
mounted() { // The mounted hook returns nothing
|
||||
...
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import { User } from '@/types'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'Home' as string,
|
||||
data() {
|
||||
return {
|
||||
user: {} as User
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fullName(): string { // Computed Property returns a string
|
||||
return `${this.user.firstName} ${this.user.lastName}`
|
||||
}
|
||||
},
|
||||
mounted(): void { // The mounted hook returns nothing
|
||||
...
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,9 @@
|
||||
" .wpy for miniprogram, with initial indent for script and style by default
|
||||
let g:test_flatten = 1
|
||||
|
||||
function! Check()
|
||||
call ExpectSyntax('view', 'VueCustomTag')
|
||||
call ExpectSyntax('wx:if', 'VueKey')
|
||||
call ExpectSyntax('show', 'VueExpression')
|
||||
call ExpectSyntax('text', 'VueCustomTag')
|
||||
endfunction
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<view wx:if="{{ show }}" class="greeting">
|
||||
<text>{{ greeting }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
greeting: 'Hello'
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.greeting {
|
||||
font-size: 2em;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
" One-line blocks like <style>p { color: red; }</style>
|
||||
|
||||
function! Check()
|
||||
call ExpectSyntax('script', 'javascriptScriptBlock')
|
||||
call ExpectSyntax('p {', 'cssStyleBlock')
|
||||
call ExpectSyntax('color', 'cssTextProp')
|
||||
call ExpectSyntax('p {', 'cssStyleBlock')
|
||||
call ExpectSyntax('color', 'cssTextProp')
|
||||
call ExpectSyntax('Hello', 'htmlTemplateBlock')
|
||||
call CheckSync()
|
||||
endfunction
|
||||
|
||||
" Syntax from a sync point, as after jumping to a line not displayed yet
|
||||
function! CheckSync()
|
||||
silent edit!
|
||||
call cursor(line('$'), 1)
|
||||
call search('Hello', 'cW')
|
||||
if index(SyntaxList(), 'htmlTemplateBlock') == -1
|
||||
call Fail('after syncing, Hello should be htmlTemplateBlock, but got '
|
||||
\.string(SyntaxList()))
|
||||
endif
|
||||
endfunction
|
||||
let g:test_flatten = 1
|
||||
@@ -0,0 +1,12 @@
|
||||
<script src="./tmp"> </script>
|
||||
|
||||
<style lang="css">
|
||||
p {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<style>p { color: red; }</style>
|
||||
|
||||
<template><div>Hello</div></template>
|
||||
@@ -0,0 +1,11 @@
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'template': ['html'],
|
||||
\ 'script': ['javascript'],
|
||||
\ 'style': [],
|
||||
\},
|
||||
\'attribute': 1,
|
||||
\'keyword': 1,
|
||||
\'foldexpr': 1,
|
||||
\}
|
||||
let g:test_flatten = 1
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="editor-page">
|
||||
<div class="container page">
|
||||
<div class="row">
|
||||
<div class="col-md-10 offset-md-1 col-xs-12">
|
||||
<RwvListErrors :errors="errors" />
|
||||
<form @submit.prevent="onPublish(article.slug)">
|
||||
<fieldset :disabled="inProgress">
|
||||
<fieldset class="form-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control form-control-lg"
|
||||
v-model="article.title"
|
||||
placeholder="Article Title"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
v-model="article.description"
|
||||
placeholder="What's this article about?"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="8"
|
||||
v-model="article.body"
|
||||
placeholder="Write your article (in markdown)"
|
||||
>
|
||||
</textarea>
|
||||
</fieldset>
|
||||
<fieldset class="form-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Enter tags"
|
||||
v-model="tagInput"
|
||||
@keypress.enter.prevent="addTag(tagInput)"
|
||||
/>
|
||||
<div class="tag-list">
|
||||
<span
|
||||
class="tag-default tag-pill"
|
||||
v-for="(tag, index) of article.tagList"
|
||||
:key="tag + index"
|
||||
>
|
||||
<i class="ion-close-round" @click="removeTag(tag)"> </i>
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
<button
|
||||
:disabled="inProgress"
|
||||
class="btn btn-lg pull-xs-right btn-primary"
|
||||
type="submit"
|
||||
>
|
||||
Publish Article
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
import store from "@/store";
|
||||
import RwvListErrors from "@/components/ListErrors";
|
||||
import {
|
||||
ARTICLE_PUBLISH,
|
||||
ARTICLE_EDIT,
|
||||
FETCH_ARTICLE,
|
||||
ARTICLE_EDIT_ADD_TAG,
|
||||
ARTICLE_EDIT_REMOVE_TAG,
|
||||
ARTICLE_RESET_STATE
|
||||
} from "@/store/actions.type";
|
||||
|
||||
export default {
|
||||
name: "RwvArticleEdit",
|
||||
components: { RwvListErrors },
|
||||
props: {
|
||||
previousArticle: {
|
||||
type: Object,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
async beforeRouteUpdate(to, from, next) {
|
||||
// Reset state if user goes from /editor/:id to /editor
|
||||
// The component is not recreated so we use to hook to reset the state.
|
||||
await store.dispatch(ARTICLE_RESET_STATE);
|
||||
return next();
|
||||
},
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// SO: https://github.com/vuejs/vue-router/issues/1034
|
||||
// If we arrive directly to this url, we need to fetch the article
|
||||
await store.dispatch(ARTICLE_RESET_STATE);
|
||||
if (to.params.slug !== undefined) {
|
||||
await store.dispatch(
|
||||
FETCH_ARTICLE,
|
||||
to.params.slug,
|
||||
to.params.previousArticle
|
||||
);
|
||||
}
|
||||
return next();
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
await store.dispatch(ARTICLE_RESET_STATE);
|
||||
next();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tagInput: null,
|
||||
inProgress: false,
|
||||
errors: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["article"])
|
||||
},
|
||||
methods: {
|
||||
onPublish(slug) {
|
||||
let action = slug ? ARTICLE_EDIT : ARTICLE_PUBLISH;
|
||||
this.inProgress = true;
|
||||
this.$store
|
||||
.dispatch(action)
|
||||
.then(({ data }) => {
|
||||
this.inProgress = false;
|
||||
this.$router.push({
|
||||
name: "article",
|
||||
params: { slug: data.article.slug }
|
||||
});
|
||||
})
|
||||
.catch(({ response }) => {
|
||||
this.inProgress = false;
|
||||
this.errors = response.data.errors;
|
||||
});
|
||||
},
|
||||
removeTag(tag) {
|
||||
this.$store.dispatch(ARTICLE_EDIT_REMOVE_TAG, tag);
|
||||
},
|
||||
addTag(tag) {
|
||||
this.$store.dispatch(ARTICLE_EDIT_ADD_TAG, tag);
|
||||
this.tagInput = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'template': ['html'],
|
||||
\ 'script': ['javascript', 'typescript'],
|
||||
\},
|
||||
\'keyword': 1,
|
||||
\}
|
||||
|
||||
function! Check()
|
||||
call ExpectSyntax('withDefaults', 'vue3Keyword')
|
||||
call ExpectSyntax('defineProps<', 'vue3Keyword')
|
||||
call ExpectSyntax('defineEmits<', 'vue3Keyword')
|
||||
call ExpectSyntax('defineModel<', 'vue3Keyword')
|
||||
call ExpectSyntax('useTemplateRef', 'vue3Keyword')
|
||||
call ExpectSyntax('ref<number>', 'vue3Keyword')
|
||||
call ExpectNoSyntax('value < 3', 'vue3Keyword')
|
||||
call ExpectSyntax('defineExpose', 'vue3Keyword')
|
||||
endfunction
|
||||
let g:test_flatten = 1
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div ref="el">{{ props.foo }}</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(defineProps<{ foo?: string }>(), {
|
||||
foo: 'a',
|
||||
})
|
||||
const emit = defineEmits<{ (e: 'change', id: number): void }>()
|
||||
const model = defineModel<string>()
|
||||
const el = useTemplateRef('el')
|
||||
const count = ref<number>(1)
|
||||
const small = count.value < 3
|
||||
defineExpose({ el })
|
||||
</script>
|
||||
@@ -0,0 +1,19 @@
|
||||
" Nested <template> and <docs> in <template>
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'script': ['javascript'],
|
||||
\ 'template': ['html'],
|
||||
\ 'style': ['css'],
|
||||
\ 'docs': 'markdown',
|
||||
\},
|
||||
\}
|
||||
|
||||
function! Check()
|
||||
while search('/template', 'W')
|
||||
call ExpectTag(line('.'), 'template')
|
||||
endwhile
|
||||
normal! gg
|
||||
call ExpectNoSyntax('/docs', 'markdownDocsBlock')
|
||||
call ExpectSyntax('/docs', 'markdownDocsBlock')
|
||||
endfunction
|
||||
let g:test_flatten = 1
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<div></div>
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
<div></div>
|
||||
<docs></docs>
|
||||
<div></div>
|
||||
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<template></template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<docs>
|
||||
# Hello
|
||||
</docs>
|
||||
@@ -0,0 +1,16 @@
|
||||
let g:vim_vue_plugin_config = {
|
||||
\'syntax': {
|
||||
\ 'template': ['html'],
|
||||
\ 'script': ['javascript', 'typescript'],
|
||||
\ 'style': ['css'],
|
||||
\},
|
||||
\'keyword': 1,
|
||||
\}
|
||||
|
||||
function! Check()
|
||||
call ExpectSyntax('v-bind', 'cssFunctionName')
|
||||
call ExpectSyntax('slotted', 'cssPseudoClassId')
|
||||
call ExpectSyntax('global', 'cssPseudoClassId')
|
||||
call ExpectSyntax('deep', 'cssPseudoClassId')
|
||||
endfunction
|
||||
let g:test_flatten = 1
|
||||
@@ -0,0 +1,8 @@
|
||||
<style scoped>
|
||||
button {
|
||||
color: v-bind(color);
|
||||
}
|
||||
.a :slotted(.b) :global(.c) :deep(.d) {
|
||||
/* ... */
|
||||
}
|
||||
</style>
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
# Run all test cases, or the ones given as arguments, with vim and nvim if
|
||||
# they are installed. Vim runs in a pseudo terminal by `script`, as syntax
|
||||
# in Ex mode (-es) isn't the same as in a terminal.
|
||||
# ./test/run.sh
|
||||
# ./test/run.sh basic template
|
||||
cd "$(dirname "$0")/.." || exit 1
|
||||
|
||||
# Syntax plugins used by test/cases/example.vue
|
||||
mkdir -p test/deps
|
||||
for repo in digitaltoad/vim-pug kchmck/vim-coffee-script \
|
||||
leafgarland/typescript-vim iloginow/vim-stylus; do
|
||||
dir=test/deps/${repo#*/}
|
||||
[ -d "$dir" ] || git clone -q --depth 1 "https://github.com/$repo" "$dir"
|
||||
done
|
||||
|
||||
if [ $# -gt 0 ]; then
|
||||
cases=$*
|
||||
else
|
||||
cases=$(ls test/cases/*.vue test/cases/*.wpy 2>/dev/null | sed 's#.*/##; s#\.[a-z]*$##')
|
||||
fi
|
||||
|
||||
out=$(mktemp)
|
||||
status=0
|
||||
expected=0
|
||||
for name in $cases; do
|
||||
if command -v vim >/dev/null; then
|
||||
expected=$((expected + 1))
|
||||
VUE_TEST_CASE=$name VUE_TEST_OUT=$out timeout 60 script -qec \
|
||||
'stty rows 40 cols 120; vim -N -u NONE -i NONE -S test/run.vim' \
|
||||
/dev/null >/dev/null || status=1
|
||||
fi
|
||||
if command -v nvim >/dev/null; then
|
||||
expected=$((expected + 1))
|
||||
VUE_TEST_CASE=$name VUE_TEST_OUT=$out timeout 60 \
|
||||
nvim --headless -u NONE -i NONE -S test/run.vim || status=1
|
||||
fi
|
||||
done
|
||||
|
||||
cat "$out"
|
||||
passed=$(grep -c '^ok' "$out")
|
||||
failed=$(grep -c '^FAIL' "$out")
|
||||
missing=$((expected - passed - failed))
|
||||
echo "$passed passed, $failed failed, $missing without result"
|
||||
[ $failed -eq 0 ] && [ $missing -eq 0 ] || status=1
|
||||
rm -f "$out"
|
||||
exit $status
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
" Run one test case, see test/run.sh
|
||||
" VUE_TEST_CASE=basic vim -N -u NONE -i NONE -S test/run.vim
|
||||
"
|
||||
" A case is test/cases/<name>.vue (or .wpy), with optional
|
||||
" test/cases/<name>.vim to set g:vim_vue_plugin_config and define Check() for
|
||||
" more assertions. It fails if
|
||||
" - indenting the whole file changes it
|
||||
" - with g:test_flatten, indenting the file without any indent doesn't
|
||||
" restore it
|
||||
" - Check() calls Fail() or an Expect*() function fails
|
||||
" - there are error messages
|
||||
"
|
||||
" Results are appended to $VUE_TEST_OUT. Exit code is 1 if the case fails.
|
||||
|
||||
set nocompatible
|
||||
let s:root = expand('<sfile>:p:h:h')
|
||||
let s:name = $VUE_TEST_CASE
|
||||
let s:case = s:root.'/test/cases/'.s:name
|
||||
let s:file = filereadable(s:case.'.vue') ? s:case.'.vue' : s:case.'.wpy'
|
||||
let s:errors = []
|
||||
|
||||
" Plugin first, before Vim's placeholder vue syntax and indent files
|
||||
let &runtimepath = join([s:root, s:root.'/test/runtime']
|
||||
\ + glob(s:root.'/test/deps/*', 0, 1)
|
||||
\ + [&runtimepath], ',')
|
||||
set expandtab shiftwidth=2
|
||||
" Plugin scripts aren't loaded with -u NONE, e.g. vim-stylus sets g:css_units
|
||||
runtime! plugin/**/*.vim
|
||||
|
||||
" Helpers for Check()
|
||||
function! Fail(msg)
|
||||
call add(s:errors, a:msg)
|
||||
endfunction
|
||||
|
||||
function! SyntaxList()
|
||||
return map(synstack(line('.'), col('.')), { _, id -> synIDattr(id, 'name') })
|
||||
endfunction
|
||||
|
||||
" Search pattern forward from cursor and check syntax at the match
|
||||
function! ExpectSyntax(pattern, syntax)
|
||||
if !search('\V'.a:pattern, 'W')
|
||||
return Fail('not found: '.a:pattern)
|
||||
endif
|
||||
if index(SyntaxList(), a:syntax) == -1
|
||||
call Fail(printf('%s at line %d should be %s, but got %s',
|
||||
\ a:pattern, line('.'), a:syntax, string(SyntaxList())))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ExpectNoSyntax(pattern, syntax)
|
||||
if !search('\V'.a:pattern, 'W')
|
||||
return Fail('not found: '.a:pattern)
|
||||
endif
|
||||
if index(SyntaxList(), a:syntax) != -1
|
||||
call Fail(printf('%s at line %d should not be %s, but got %s',
|
||||
\ a:pattern, line('.'), a:syntax, string(SyntaxList())))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ExpectFoldlevel(lnum, level)
|
||||
if foldlevel(a:lnum) != a:level
|
||||
call Fail(printf('foldlevel at line %d should be %d, but got %d',
|
||||
\ a:lnum, a:level, foldlevel(a:lnum)))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ExpectEqual(actual, expected, desc)
|
||||
if a:actual !=# a:expected
|
||||
call Fail(printf('%s should be %s, but got %s',
|
||||
\ a:desc, string(a:expected), string(a:actual)))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Search pattern forward from cursor and check the block there by
|
||||
" GetVueTag(), GetVueSyntax() and the block syntax group at the line end
|
||||
function! ExpectBlock(pattern, tag, syntax, group)
|
||||
if !search('\V'.a:pattern, 'W')
|
||||
return Fail('not found: '.a:pattern)
|
||||
endif
|
||||
let desc = printf('%s at line %d: ', a:pattern, line('.'))
|
||||
call ExpectEqual(GetVueTag(), a:tag, desc.'tag')
|
||||
call ExpectEqual(GetVueSyntax(), a:syntax, desc.'syntax')
|
||||
let group = synIDattr(synstack(line('.'), col('$') - 1)[0], 'name')
|
||||
call ExpectEqual(group, a:group, desc.'block group')
|
||||
endfunction
|
||||
|
||||
function! ExpectTag(lnum, tag)
|
||||
if GetVueTag(a:lnum) !=# a:tag
|
||||
call Fail(printf('tag at line %d should be %s, but got %s',
|
||||
\ a:lnum, string(a:tag), string(GetVueTag(a:lnum))))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
if filereadable(s:case.'.vim')
|
||||
execute 'source '.fnameescape(s:case.'.vim')
|
||||
endif
|
||||
filetype plugin indent on
|
||||
syntax on
|
||||
|
||||
messages clear
|
||||
execute 'silent edit '.fnameescape(s:file)
|
||||
redraw
|
||||
|
||||
function! s:ExpectLines(lines, desc)
|
||||
let actual = getline(1, '$')
|
||||
if actual == a:lines
|
||||
return
|
||||
endif
|
||||
let i = 0
|
||||
while i < len(a:lines) && get(actual, i) ==# a:lines[i]
|
||||
let i += 1
|
||||
endwhile
|
||||
call Fail(printf('%s changed line %d: expected %s, but got %s',
|
||||
\ a:desc, i + 1, string(get(a:lines, i, '')), string(get(actual, i, ''))))
|
||||
endfunction
|
||||
|
||||
" Indent
|
||||
let s:lines = getline(1, '$')
|
||||
silent normal! gg=G
|
||||
call s:ExpectLines(s:lines, 'indent')
|
||||
silent edit!
|
||||
|
||||
" Indent without any indent
|
||||
if get(g:, 'test_flatten', 0)
|
||||
silent %s/^\s\+//e
|
||||
silent normal! gg=G
|
||||
call s:ExpectLines(s:lines, 'indent from flat')
|
||||
silent edit!
|
||||
endif
|
||||
|
||||
" Assertions
|
||||
normal! gg
|
||||
if exists('*Check')
|
||||
call Check()
|
||||
endif
|
||||
|
||||
" Errors
|
||||
for s:msg in split(execute('messages'), "\n")
|
||||
if s:msg =~# '^E\d\+:\|^Error detected'
|
||||
call Fail('message: '.s:msg)
|
||||
endif
|
||||
endfor
|
||||
|
||||
let s:editor = has('nvim') ? 'nvim' : 'vim'
|
||||
call writefile(empty(s:errors)
|
||||
\ ? [printf('ok %s %s', s:editor, s:name)]
|
||||
\ : [printf('FAIL %s %s', s:editor, s:name)]
|
||||
\ + map(copy(s:errors), '" ".v:val'),
|
||||
\ $VUE_TEST_OUT, 'a')
|
||||
if !empty(s:errors)
|
||||
cquit
|
||||
endif
|
||||
qall!
|
||||
@@ -0,0 +1,157 @@
|
||||
" Vim indent file
|
||||
" Language: YAML
|
||||
" Maintainer: Nikolai Pavlov <zyx.vim@gmail.com>
|
||||
" Last Update: Lukas Reineke
|
||||
" Last Change: 2020 Jun 07
|
||||
|
||||
" Only load this indent file when no other was loaded.
|
||||
if exists('b:did_indent')
|
||||
finish
|
||||
endif
|
||||
|
||||
let s:save_cpo = &cpo
|
||||
set cpo&vim
|
||||
|
||||
let b:did_indent = 1
|
||||
|
||||
setlocal indentexpr=GetYAMLIndent(v:lnum)
|
||||
setlocal indentkeys=!^F,o,O,0#,0},0],<:>,0-
|
||||
setlocal nosmartindent
|
||||
|
||||
let b:undo_indent = 'setlocal indentexpr< indentkeys< smartindent<'
|
||||
|
||||
" Only define the function once.
|
||||
if exists('*GetYAMLIndent')
|
||||
finish
|
||||
endif
|
||||
|
||||
function s:FindPrevLessIndentedLine(lnum, ...)
|
||||
let prevlnum = prevnonblank(a:lnum-1)
|
||||
let curindent = a:0 ? a:1 : indent(a:lnum)
|
||||
while prevlnum
|
||||
\&& indent(prevlnum) >= curindent
|
||||
\&& getline(prevlnum) !~# '^\s*#'
|
||||
let prevlnum = prevnonblank(prevlnum-1)
|
||||
endwhile
|
||||
return prevlnum
|
||||
endfunction
|
||||
|
||||
function s:FindPrevLEIndentedLineMatchingRegex(lnum, regex)
|
||||
let plilnum = s:FindPrevLessIndentedLine(a:lnum, indent(a:lnum)+1)
|
||||
while plilnum && getline(plilnum) !~# a:regex
|
||||
let plilnum = s:FindPrevLessIndentedLine(plilnum)
|
||||
endwhile
|
||||
return plilnum
|
||||
endfunction
|
||||
|
||||
let s:mapkeyregex='\v^\s*\#@!\S@=%(\''%([^'']|\''\'')*\'''.
|
||||
\ '|\"%([^"\\]|\\.)*\"'.
|
||||
\ '|%(%(\:\ )@!.)*)\:%(\ |$)'
|
||||
let s:liststartregex='\v^\s*%(\-%(\ |$))'
|
||||
|
||||
let s:c_ns_anchor_char = '\v%([\n\r\uFEFF \t,[\]{}]@!\p)'
|
||||
let s:c_ns_anchor_name = s:c_ns_anchor_char.'+'
|
||||
let s:c_ns_anchor_property = '\v\&'.s:c_ns_anchor_name
|
||||
|
||||
let s:ns_word_char = '\v[[:alnum:]_\-]'
|
||||
let s:ns_tag_char = '\v%('.s:ns_word_char.'|[#/;?:@&=+$.~*''()])'
|
||||
let s:c_named_tag_handle = '\v\!'.s:ns_word_char.'+\!'
|
||||
let s:c_secondary_tag_handle = '\v\!\!'
|
||||
let s:c_primary_tag_handle = '\v\!'
|
||||
let s:c_tag_handle = '\v%('.s:c_named_tag_handle.
|
||||
\ '|'.s:c_secondary_tag_handle.
|
||||
\ '|'.s:c_primary_tag_handle.')'
|
||||
let s:c_ns_shorthand_tag = '\v'.s:c_tag_handle . s:ns_tag_char.'+'
|
||||
let s:c_non_specific_tag = '\v\!'
|
||||
let s:ns_uri_char = '\v%('.s:ns_word_char.'\v|[#/;?:@&=+$,.!~*''()[\]])'
|
||||
let s:c_verbatim_tag = '\v\!\<'.s:ns_uri_char.'+\>'
|
||||
let s:c_ns_tag_property = '\v'.s:c_verbatim_tag.
|
||||
\ '\v|'.s:c_ns_shorthand_tag.
|
||||
\ '\v|'.s:c_non_specific_tag
|
||||
|
||||
let s:block_scalar_header = '\v[|>]%([+-]?[1-9]|[1-9]?[+-])?'
|
||||
|
||||
function GetYAMLIndent(lnum)
|
||||
if a:lnum == 1 || !prevnonblank(a:lnum-1)
|
||||
return 0
|
||||
endif
|
||||
|
||||
let prevlnum = prevnonblank(a:lnum-1)
|
||||
let previndent = indent(prevlnum)
|
||||
|
||||
let line = getline(a:lnum)
|
||||
if line =~# '^\s*#' && getline(a:lnum-1) =~# '^\s*#'
|
||||
" Comment blocks should have identical indent
|
||||
return previndent
|
||||
elseif line =~# '^\s*[\]}]'
|
||||
" Lines containing only closing braces should have previous indent
|
||||
return indent(s:FindPrevLessIndentedLine(a:lnum))
|
||||
endif
|
||||
|
||||
" Ignore comment lines when calculating indent
|
||||
while getline(prevlnum) =~# '^\s*#'
|
||||
let prevlnum = prevnonblank(prevlnum-1)
|
||||
if !prevlnum
|
||||
return previndent
|
||||
endif
|
||||
endwhile
|
||||
|
||||
let prevline = getline(prevlnum)
|
||||
let previndent = indent(prevlnum)
|
||||
|
||||
" Any examples below assume that shiftwidth=2
|
||||
if prevline =~# '\v[{[:]$|[:-]\ [|>][+\-]?%(\s+\#.*|\s*)$'
|
||||
" Mapping key:
|
||||
" nested mapping: ...
|
||||
"
|
||||
" - {
|
||||
" key: [
|
||||
" list value
|
||||
" ]
|
||||
" }
|
||||
"
|
||||
" - |-
|
||||
" Block scalar without indentation indicator
|
||||
return previndent+shiftwidth()
|
||||
elseif prevline =~# '\v[:-]\ [|>]%(\d+[+\-]?|[+\-]?\d+)%(\#.*|\s*)$'
|
||||
" - |+2
|
||||
" block scalar with indentation indicator
|
||||
"#^^ indent+2, not indent+shiftwidth
|
||||
return previndent + str2nr(matchstr(prevline,
|
||||
\'\v([:-]\ [|>])@<=[+\-]?\d+%([+\-]?%(\s+\#.*|\s*)$)@='))
|
||||
elseif prevline =~# '\v\"%([^"\\]|\\.)*\\$'
|
||||
" "Multiline string \
|
||||
" with escaped end"
|
||||
let qidx = match(prevline, '\v\"%([^"\\]|\\.)*\\')
|
||||
return virtcol([prevlnum, qidx+1])
|
||||
elseif line =~# s:liststartregex
|
||||
" List line should have indent equal to previous list line unless it was
|
||||
" caught by one of the previous rules
|
||||
return indent(s:FindPrevLEIndentedLineMatchingRegex(a:lnum,
|
||||
\ s:liststartregex))
|
||||
elseif line =~# s:mapkeyregex
|
||||
" Same for line containing mapping key
|
||||
let prevmapline = s:FindPrevLEIndentedLineMatchingRegex(a:lnum,
|
||||
\ s:mapkeyregex)
|
||||
if getline(prevmapline) =~# '^\s*- '
|
||||
return indent(prevmapline) + 2
|
||||
else
|
||||
return indent(prevmapline)
|
||||
endif
|
||||
elseif prevline =~# '^\s*- '
|
||||
" - List with
|
||||
" multiline scalar
|
||||
return previndent+2
|
||||
elseif prevline =~# s:mapkeyregex . '\v\s*%(%('.s:c_ns_tag_property.
|
||||
\ '\v|'.s:c_ns_anchor_property.
|
||||
\ '\v|'.s:block_scalar_header.
|
||||
\ '\v)%(\s+|\s*%(\#.*)?$))*'
|
||||
" Mapping with: value
|
||||
" that is multiline scalar
|
||||
return previndent+shiftwidth()
|
||||
endif
|
||||
return previndent
|
||||
endfunction
|
||||
|
||||
let &cpo = s:save_cpo
|
||||
|
||||
Reference in new issue
Block a user