Skip to content

Commit

Permalink
fix: regression issue of wxml pair tag highlight (#134)
Browse files Browse the repository at this point in the history
  • Loading branch information
iChenLei authored Jan 5, 2022
1 parent bea88f5 commit 50f4b0b
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 2 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
2.4.6 / 2022-01-05
==================

* Fix [#133](https://github.com/wx-minapp/minapp-vscode/issues/133), [v2.4.2](https://github.com/wx-minapp/minapp-vscode/compare/v2.4.1...v2.4.2) delete `WxmlDocumentHighlightProvider` unexpectedly

2.4.5 / 2021-12-08
==================

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "minapp-vscode",
"displayName": "WXML - Language Service",
"description": "wechat miniprogram .wxml file syntax highlight,code autocomplete(support native miniprogram、mpvue and wepy framework,provide code snippets)",
"version": "2.4.5",
"version": "2.4.6",
"publisher": "qiu8310",
"extensionKind": [
"workspace"
Expand Down
5 changes: 5 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { PropDefinitionProvider } from './plugin/PropDefinitionProvider'
import WxmlAutoCompletion from './plugin/WxmlAutoCompletion'
import PugAutoCompletion from './plugin/PugAutoCompletion'
import VueAutoCompletion from './plugin/VueAutoCompletion'
import WxmlDocumentHighlight from './plugin/WxmlDocumentHighlight'
import ActiveTextEditorListener from './plugin/ActiveTextEditorListener'
import { config, configActivate, configDeactivate } from './plugin/lib/config'
import { createMiniprogramComponent } from './commands/createMiniprogramComponent'
Expand All @@ -30,6 +31,7 @@ export function activate(context: ExtensionContext): void {
const linkProvider = new LinkProvider(config)
const autoCompletionPug = new PugAutoCompletion(config)
const autoCompletionVue = new VueAutoCompletion(autoCompletionPug, autoCompletionWxml)
const documentHighlight = new WxmlDocumentHighlight(config)
const propDefinitionProvider = new PropDefinitionProvider(config)

const wxml = config.documentSelector.map(l => schemes(l))
Expand All @@ -50,6 +52,9 @@ export function activate(context: ExtensionContext): void {
// 添加 link
languages.registerDocumentLinkProvider([pug].concat(wxml), linkProvider),

// 高亮匹配的标签
languages.registerDocumentHighlightProvider(wxml, documentHighlight),

// 格式化
languages.registerDocumentFormattingEditProvider(wxml, formatter),
languages.registerDocumentRangeFormattingEditProvider(wxml, formatter),
Expand Down
120 changes: 120 additions & 0 deletions src/plugin/WxmlDocumentHighlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { DocumentHighlightProvider, TextDocument, Position, CancellationToken, Range, DocumentHighlight } from 'vscode'

import { Config } from './lib/config'

const TagRegexp = /<([\w:-]+)|<\/([\w:-]+)>/g

export default class WxmlDocumentHighlight implements DocumentHighlightProvider {
constructor(public config: Config) {}

provideDocumentHighlights(doc: TextDocument, pos: Position, _token: CancellationToken): DocumentHighlight[] {
const range = doc.getWordRangeAtPosition(pos, /[\w:-]+/)
if (!range) return []

const content = doc.getText()
const name = doc.getText(range)
const res: Range[] = [range]

if (prev(doc, range.start) === '<') {
// start tag
const nextStartIndex = doc.offsetAt(range.end)
const nextText = this.normalize(content.slice(nextStartIndex))

if (!/^[^<>]*\/>/.test(nextText)) {
// not self close tag
const nextEndIndex = this.findMatchedEndTag(name, nextText)
if (typeof nextEndIndex === 'number') {
res.push(
new Range(
doc.positionAt(nextStartIndex + nextEndIndex + 2),
doc.positionAt(nextStartIndex + nextEndIndex + 2 + name.length)
)
)
}
}
} else if (prev(doc, range.start, 2) === '</' && next(doc, range.end, 1) === '>') {
// end tag
const prevEndIndex = doc.offsetAt(range.start) - 2
const prevText = this.normalize(content.slice(0, prevEndIndex))
const prevStartIndex = this.findMatchedStartTag(name, prevText)
if (typeof prevStartIndex === 'number') {
res.push(new Range(doc.positionAt(prevStartIndex + 1), doc.positionAt(prevStartIndex + 1 + name.length)))
}
}

return res.map(r => new DocumentHighlight(r))
}

/**
* 因为只需要 tag,所以这里把所有的注释属性替换成特殊字符
*/
private normalize(content: string) {
const replacer = (raw: string) => new Array(raw.length).fill(' ').join('')

return (
content
.replace(/<!--.*?-->/g, replacer) // 去除注释
.replace(/("[^"]*"|'[^']*')/g, replacer) // 去除属性
// .replace(/>[\s\S]*?</g, (raw) => '>' + replacer(raw.substr(2)) + '<') // 去除标签内容
.replace(/<[\w:-]+\s+[^<>]*\/>/g, replacer)
) // 去除自闭合的标签
}

private findMatchedEndTag(name: string, nextContent: string) {
TagRegexp.lastIndex = 0
let mat: RegExpExecArray | null
const stack: string[] = []
while ((mat = TagRegexp.exec(nextContent))) {
const [, startName, endName] = mat
if (startName) {
stack.push(startName)
} else if (endName) {
const last = stack.pop()
if (!last) {
// 最后一个了
if (endName === name) {
return mat.index
}
} else if (last !== endName) {
// wxml 解析错误
break
}
}
}
return
}

private findMatchedStartTag(name: string, prevContent: string) {
TagRegexp.lastIndex = 0
let mat: RegExpExecArray | null
const stack: [string, number][] = []
while ((mat = TagRegexp.exec(prevContent))) {
const [, startName, endName] = mat
if (startName) {
stack.push([startName, mat.index])
} else if (endName) {
const last = stack.pop()
if (!last) {
break
} else if (last[0] !== endName) {
// wxml 解析错误
break
}
}
}
const l = stack[stack.length - 1]
return l ? l[1] : undefined
}
}

function next(doc: TextDocument, pos: Position, length = 1) {
const start = new Position(pos.line, pos.character)
const end = new Position(pos.line, pos.character + length)
return doc.getText(new Range(start, end))
}

function prev(doc: TextDocument, pos: Position, length = 1) {
const start = new Position(pos.line, pos.character - length)
const end = new Position(pos.line, pos.character)
return doc.getText(new Range(start, end))
}

0 comments on commit 50f4b0b

Please sign in to comment.