Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat function autocompletion #42

Merged
merged 6 commits into from
Jul 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function activate(context: ExtensionContext) {
languages.registerDefinitionProvider([pug].concat(wxml), propDefinitionProvider),

// 自动补全
languages.registerCompletionItemProvider(wxml, autoCompletionWxml, '<', ' ', ':', '@', '.', '-', '"', '\'', '\n'),
languages.registerCompletionItemProvider(wxml, autoCompletionWxml, '<', ' ', ':', '@', '.', '-', '"', '\'', '\n', '/'),
languages.registerCompletionItemProvider(pug, autoCompletionPug, '\n', ' ', '(', ':', '@', '.', '-', '"', '\''),
// trigger 需要是上两者的和
languages.registerCompletionItemProvider(vue, autoCompletionVue, '<', ' ', ':', '@', '.', '-', '(', '"', '\'')
Expand Down
143 changes: 117 additions & 26 deletions src/plugin/AutoCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import {

import * as path from 'path'

import {Config} from './lib/config'
import {getCustomOptions, getTextAtPosition, getRoot, getEOL} from './lib/helper'
import {LanguageConfig} from './lib/language'
import {getTagAtPosition} from './getTagAtPosition/'
import { Config } from './lib/config'
import { getCustomOptions, getTextAtPosition, getRoot, getEOL, getLastChar } from './lib/helper'
import { LanguageConfig } from './lib/language'
import { getTagAtPosition } from './getTagAtPosition/'
import * as s from './res/snippets'
import { getClass } from './lib/StyleFile'
import { getCloseTag } from './lib/closeTag'
import { getProp } from './lib/ScriptFile'

export default abstract class AutoCompletion {
abstract id: 'wxml' | 'wxml-pug'
Expand All @@ -32,7 +34,7 @@ export default abstract class AutoCompletion {
return this.isPug ? this.config.pugQuoteStyle : this.config.wxmlQuoteStyle
}

constructor(public config: Config) {}
constructor(public config: Config) { }

getCustomOptions(doc: TextDocument) {
return getCustomOptions(this.config, doc)
Expand All @@ -42,7 +44,7 @@ export default abstract class AutoCompletion {
let c = tag.component
let item = new CompletionItem(c.name, CompletionItemKind.Module)

let {attrQuote, isPug} = this
let { attrQuote, isPug } = this
let allAttrs = c.attrs || []
let attrs = allAttrs
.filter(a => a.required || a.subAttrs)
Expand Down Expand Up @@ -80,7 +82,7 @@ export default abstract class AutoCompletion {
defaultValue = a.enum && a.enum[0].value
}

let {attrQuote, isPug} = this
let { attrQuote, isPug } = this

if (a.boolean) {
item.insertText = new SnippetString(isPug && defaultValue === 'false' ? `${a.name}=false` : a.name)
Expand All @@ -90,7 +92,7 @@ export default abstract class AutoCompletion {
: this.setDefault(1, defaultValue)

// 是否有可选值,如果有可选值则触发命令的自动补全
let values = a.enum ? a.enum : a.subAttrs ? a.subAttrs.map(sa => ({value: sa.equal})) : []
let values = a.enum ? a.enum : a.subAttrs ? a.subAttrs.map(sa => ({ value: sa.equal })) : []
if (values.length) {
value = '\${1}'
item.command = autoSuggestCommand()
Expand Down Expand Up @@ -149,7 +151,7 @@ export default abstract class AutoCompletion {

// 添加 Snippet
let userSnippets = this.config.snippets
let allSnippets: s.Snippets = (this.isPug ? {...s.PugSnippets, ...userSnippets.pug} : {...s.WxmlSnippets, ...userSnippets.wxml})
let allSnippets: s.Snippets = (this.isPug ? { ...s.PugSnippets, ...userSnippets.pug } : { ...s.WxmlSnippets, ...userSnippets.wxml })
items.push(...Object.keys(allSnippets)
.filter(k => filter(k))
.map(k => {
Expand Down Expand Up @@ -180,34 +182,44 @@ export default abstract class AutoCompletion {
if (!tag) return []
if (tag.isOnTagName) {
return this.createComponentSnippetItems(lc, doc, pos, tag.name)
} else if (tag.isOnAttrValue && tag.attrName) {
}
if (tag.isOnAttrValue && tag.attrName) {
let attrValue = tag.attrs[tag.attrName]
if (tag.attrName === 'class') {
let existsClass = (tag.attrs.class || '') as string
return this.autoCompleteClassNames(doc, existsClass ? existsClass.trim().split(/\s+/) : [])
} else if (typeof attrValue === 'string' && attrValue.trim() === '') {
let values = await autocompleteTagAttrValue(tag.name, tag.attrName, lc, this.getCustomOptions(doc))
if (!values.length) return []
let range = doc.getWordRangeAtPosition(pos, /['"]\s*['"]/)
if (range) {
range = new Range(
new Position(range.start.line, range.start.character + 1),
new Position(range.end.line, range.end.character - 1)
)
} else if (typeof attrValue === 'string') {
if ((tag.attrName.startsWith('bind') || tag.attrName.startsWith('catch'))) {
// 函数自动补全
return this.autoCompleteMethods(doc, attrValue.replace(/"|'/, ''))
} else if (attrValue.trim() === '') {
let values = await autocompleteTagAttrValue(tag.name, tag.attrName, lc, this.getCustomOptions(doc))
if (!values.length) return []
let range = doc.getWordRangeAtPosition(pos, /['"]\s*['"]/)
if (range) {
range = new Range(
new Position(range.start.line, range.start.character + 1),
new Position(range.end.line, range.end.character - 1)
)
}
return values.map(v => {
let it = new CompletionItem(v.value, CompletionItemKind.Value)
it.documentation = new MarkdownString(v.markdown)
it.range = range
return it
})
}
return values.map(v => {
let it = new CompletionItem(v.value, CompletionItemKind.Value)
it.documentation = new MarkdownString(v.markdown)
it.range = range
return it
})

// } else if ((tag.attrName.startsWith('bind') || tag.attrName.startsWith('catch')) && typeof attrValue === 'string') {

// return this.autoCompleteMethods(doc, attrValue.replace(/"|'/, ''))
}
return []
} else {
let res = await autocompleteTagAttr(tag.name, tag.attrs, lc, this.getCustomOptions(doc))
let triggers: CompletionItem[] = []

let {natives, basics} = res
let { natives, basics } = res
let noBasics = lc.noBasicAttrsComponents || []

if (noBasics.indexOf(tag.name) < 0) {
Expand Down Expand Up @@ -299,6 +311,85 @@ export default abstract class AutoCompletion {

return items
}

/**
* 闭合标签自动完成
* @param doc
* @param pos
*/
async createCloseTagCompletionItem(doc: TextDocument, pos: Position): Promise<CompletionItem[]> {
const text = doc.getText(new Range(new Position(0, 0), pos))
if (text.length < 2 || text.substr(text.length - 2) !== '</') {
return []
}
const closeTag = getCloseTag(text)
if (closeTag) {
const completionItem = new CompletionItem(closeTag)
completionItem.kind = CompletionItemKind.Property
completionItem.insertText = closeTag

const nextPos = new Position(pos.line, pos.character + 1)
if (getLastChar(doc, nextPos) === '>') {
completionItem.range = new Range(pos, nextPos)
completionItem.label = closeTag.substr(0, closeTag.length - 1)
}
return [completionItem]
}

return []
}

/**
* 函数自动提示
* @param doc
* @param prefix 函数前缀,空则查找所有函数
*/
autoCompleteMethods(doc: TextDocument, prefix: string): CompletionItem[] {
/**
* 页面周期和组件 生命周期函数,
* 显示时置于最后
* 列表中顺序决定显示顺序
*/
const lowPriority = [
'onPullDownRefresh', 'onReachBottom', 'onPageScroll',
'onShow', 'onHide', 'onTabItemTap', 'onLoad', 'onReady', 'onResize', 'onUnload', 'onShareAppMessage',
'error', 'creaeted', 'attached', 'ready', 'moved', 'detached', 'observer',
]
const methods = getProp(doc.uri.fsPath, 'method', (prefix || '[\\w_$]') + '[\\w\\d_$]*')
const root = getRoot(doc)
return methods.map(l => {
const c = new CompletionItem(l.name, getMethodKind(l.detail))
const filePath = root ? path.relative(root, l.loc.uri.fsPath) : path.basename(l.loc.uri.fsPath)
// 低优先级排序滞后
const priotity = lowPriority.indexOf(l.name) + 1
c.detail = `${filePath}\n[${l.loc.range.start.line}行,${l.loc.range.start.character}列]`
c.documentation = new MarkdownString('```ts\n' + l.detail + '\n```')
/**
* 排序显示规则
* 1. 正常函数 如 `onTap`
* 2. 下划线函数 `_save`
* 3. 生命周期函数 `onShow`
*/
if (priotity > 0) {
c.detail += '(生命周期函数)'
c.kind = CompletionItemKind.Field
c.sortText = '}'.repeat(priotity)
} else {
c.sortText = l.name.replace('_', '{')
}
return c
})
}

}

/**
* 是否为属性式函数声明
* 如 属性式声明 `foo:()=>{}`
* @param text
*/
function getMethodKind(text: string) {
return /^\s*[\w_$][\w_$\d]*\s*:/.test(text) ? CompletionItemKind.Property : CompletionItemKind.Method
}

function autoSuggestCommand() {
Expand Down
8 changes: 4 additions & 4 deletions src/plugin/PropDefinitionProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Config } from './lib/config'
import {DefinitionProvider, TextDocument, Position, CancellationToken, Location, Uri, Range} from 'vscode'
import { DefinitionProvider, TextDocument, Position, CancellationToken, Location, Uri, Range } from 'vscode'
import { getTagAtPosition } from './getTagAtPosition'
import { getClass } from './lib/StyleFile'
import { getProp } from './lib/ScriptFile'
Expand All @@ -9,13 +9,13 @@ const reserveWords = [
]

export class PropDefinitionProvider implements DefinitionProvider {
constructor(public config: Config) {}
constructor(public config: Config) { }
public async provideDefinition(document: TextDocument, position: Position, token: CancellationToken) {
const tag = getTagAtPosition(document, position)
const locs: Location[] = []

if (tag) {
const {attrs, attrName, posWord} = tag
const { attrs, attrName, posWord } = tag
const rawAttrValue = ((attrs['__' + attrName] || '') as string).replace(/^['"]|['"]$/g, '') // 去除引号

// 不在属性上
Expand All @@ -42,7 +42,7 @@ export class PropDefinitionProvider implements DefinitionProvider {
}

searchScript(type: 'prop' | 'method', word: string, doc: TextDocument) {
return getProp(doc.fileName, type, word)
return getProp(doc.fileName, type, word).map(p => p.loc)
}

searchStyle(className: string, document: TextDocument, position: Position) {
Expand Down
5 changes: 5 additions & 0 deletions src/plugin/WxmlAutoCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export default class extends AutoCompletion implements CompletionItemProvider {
id = 'wxml' as 'wxml'

provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: CompletionContext): Promise<CompletionItem[]> {
if (token.isCancellationRequested) {
return Promise.resolve([])
}
let language = getLanguage(document, position)
if (!language) return [] as any

Expand All @@ -39,6 +42,8 @@ export default class extends AutoCompletion implements CompletionItemProvider {
case '-': // v-if
case '.': // 变量或事件的修饰符
return this.createSpecialAttributeSnippetItems(language, document, position)
case '/': // 闭合标签
return this.createCloseTagCompletionItem(document, position)
default:
if (char >= 'a' && char <= 'z') {
// 输入属性时自动提示
Expand Down
Loading