Add Close / Navigate to environment actions

This commit is contained in:
Jerome Lelong 2018-04-11 14:06:01 +02:00
parent 0b555a6dc8
commit 8c3f6b9f0a
6 changed files with 215 additions and 1 deletions

15
.vscode/settings.json vendored
View File

@ -8,5 +8,18 @@
},
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
"vsicons.presets.angular": false,
"tslint.exclude": "node_modules"
"tslint.exclude": "node_modules",
"cSpell.enabledLanguageIds": [
"csharp",
"go",
"handlebars",
"html",
"latex",
"markdown",
"plaintext",
"restructuredtext",
"text",
"typescriptreact",
"yml"
]
}

View File

@ -22,6 +22,9 @@ LaTeX Workshop is an extension for [Visual Studio Code](https://code.visualstudi
- Real-time linting of LaTeX with ChkTeX to pick up common LaTeX issues as you type.
- Code Actions (automatic fixes) are offered for many issues found by ChkTeX.
- LaTeX file formatting.
- Auto close LaTeX environments: just call _LaTeX Workshop: Close current environment_ from the **Command Palette**. You can easily assign a shortcut to this action through the **Keyboard Shortcuts** menu, search for `latex-workshop.close-env`.
- Navigate from `\begin/\end` to the corresponding `\end/\begin`: while on the `begin` or `end` keyword, call _LaTeX Workshop: Navigate to matching begin/end_ from the **Command Palette**. To define a shortcut, search for `latex-workshop.navigate-envpair` in the **Keyboard Shortcuts** menu.
## Requirements

View File

@ -149,6 +149,16 @@
}
],
"commands": [
{
"command": "latex-workshop.navigate-envpair",
"title": "Navigate to matching begin/end",
"category": "LaTeX Workshop"
},
{
"command": "latex-workshop.close-env",
"title": "Close current environment",
"category": "LaTeX Workshop"
},
{
"command": "latex-workshop.build",
"title": "Build LaTeX project",

View File

@ -225,6 +225,22 @@ export class Commander {
})
}
navigateToEnvPair() {
this.extension.logger.addLogMessage(`JumpToEnvPair command invoked.`)
if (!vscode.window.activeTextEditor || !this.extension.manager.isTex(vscode.window.activeTextEditor.document.fileName)) {
return
}
this.extension.envPair.gotoPair()
}
closeEnv() {
this.extension.logger.addLogMessage(`CloseEnv command invoked.`)
if (!vscode.window.activeTextEditor || !this.extension.manager.isTex(vscode.window.activeTextEditor.document.fileName)) {
return
}
this.extension.envPair.closeEnv()
}
actions() {
this.extension.logger.addLogMessage(`ACTIONS command invoked.`)
const configuration = vscode.workspace.getConfiguration('latex-workshop')

167
src/components/envpair.ts Normal file
View File

@ -0,0 +1,167 @@
import * as vscode from 'vscode'
import { Extension } from '../main'
/**
* Remove the comments if any
* @param line
*/
function stripComments(line: string) : string {
let commentPos = line.search(/(?!\\)%/)
if (commentPos !== -1) {
commentPos++
return line.slice(0, commentPos)
}
return line
}
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')
}
function regexpAllMatches(str: string, reg: RegExp) {
let m
const res: any[] = []
while (m = reg.exec(str)) {
res.push(m)
}
return res
}
interface MatchPair {
str: string
pos: vscode.Position
}
interface MatchEnv {
name: string
type: string // 'begin' or 'end'
pos: vscode.Position
}
export class EnvPair {
extension: Extension
beginLength = '\\begin'.length
endLength = '\\end'.length
constructor(extension: Extension) {
this.extension = extension
}
getEnvName(line: string, ind: number, beginOrEnd: string) : string | null {
const subline = line.slice(ind)
const re = new RegExp('^' + beginOrEnd + '\\{([^\\{\\}]*)\\}')
const env = subline.match(re)
if (env && env.length === 2) {
return env[1]
}
return null
}
tokenizeLine(document: vscode.TextDocument, pos: vscode.Position) : MatchEnv | null {
const line = stripComments(document.lineAt(pos).text)
const ind = pos.character
if (ind > line.length) {
console.log('We are in a comment')
return null
}
const lineUpToInd = line.slice(0, ind + 1)
const startInd = lineUpToInd.lastIndexOf('\\')
const startPos = new vscode.Position(pos.line, startInd)
if (startInd + this.beginLength >= ind && line.slice(startInd, startInd + this.beginLength) === '\\begin') {
const envName = this.getEnvName(line, startInd, '\\\\begin')
if (envName) {
return {pos: startPos, type: 'begin', name: envName}
}
} else if (startInd + this.endLength >= ind && line.slice(startInd, startInd + this.endLength) === '\\end') {
const envName = this.getEnvName(line, startInd, '\\\\end')
if (envName) {
return {pos: startPos, type: 'end', name: envName}
}
}
return null
}
locateMatchingPair(pattern: string, dir: number, pos: vscode.Position, doc: vscode.TextDocument) : MatchPair | null {
const patRegexp = new RegExp(pattern, 'g')
let lineNumber = pos.line
let nested = 0
let line = doc.lineAt(lineNumber).text
/* Drop the pattern on the current line */
if (dir === 1) {
line = line.slice(line.indexOf('}', pos.character) + 1)
} else if (dir === -1) {
line = line.slice(0, pos.character)
}
while (true) {
line = stripComments(line)
let allMatches = regexpAllMatches(line, patRegexp)
if (dir === -1) {
allMatches = allMatches.reverse()
}
for (let m of allMatches) {
if ((m[1] === 'begin' && dir === 1) || (m[1] === 'end' && dir === -1)) {
nested += 1
}
if ((m[1] === 'end' && dir === 1) || (m[1] === 'begin' && dir === -1)) {
if (nested === 0) {
const matchPos = new vscode.Position(lineNumber, m.index + 1)
const matchStr = m[0]
return {str: matchStr, pos: matchPos}
}
nested -= 1
}
}
lineNumber += dir
if (lineNumber < 0 || lineNumber >= doc.lineCount) {
break
}
line = doc.lineAt(lineNumber).text
}
return null
}
gotoPair() {
const editor = vscode.window.activeTextEditor
if (!editor || editor.document.languageId !== 'latex') {
return
}
const curPos = editor.selection.active
const document = editor.document
const tokens = this.tokenizeLine(document, curPos)
if (!tokens) {
return
}
const startPos = tokens.pos
const pattern = '\\\\(begin|end)\\{' + escapeRegExp(tokens.name) + '\\}'
const dir = (tokens.type === 'begin') ? 1 : -1
const resMatchingPair = this.locateMatchingPair(pattern, dir, startPos, document)
if (resMatchingPair) {
const newPos = resMatchingPair.pos
editor.selection = new vscode.Selection(newPos, newPos)
editor.revealRange(new vscode.Range(newPos, newPos))
}
}
closeEnv() {
const editor = vscode.window.activeTextEditor
if (!editor || editor.document.languageId !== 'latex') {
return
}
const document = editor.document
const curPos = editor.selection.active
const pattern = '\\\\(begin|end)\\{[^\\{\\}]*\\}'
const dir = -1
const resMatchingPair = this.locateMatchingPair(pattern, dir, curPos, document)
if (resMatchingPair) {
const endEnv = resMatchingPair.str.replace('begin', 'end')
const edits = [vscode.TextEdit.insert(curPos, endEnv)]
const uri = document.uri
const edit = new vscode.WorkspaceEdit()
edit.set(uri, edits)
vscode.workspace.applyEdit(edit)
}
}
}

View File

@ -14,6 +14,7 @@ import {Linter} from './components/linter'
import {Cleaner} from './components/cleaner'
import {Counter} from './components/counter'
import {TeXMagician} from './components/texmagician'
import {EnvPair} from './components/envpair'
import {Completer} from './providers/completion'
import {CodeActions} from './providers/codeactions'
@ -141,6 +142,8 @@ export async function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand('latex-workshop.log', () => extension.commander.log())
vscode.commands.registerCommand('latex-workshop.code-action', (d, r, c, m) => extension.codeActions.runCodeAction(d, r, c, m))
vscode.commands.registerCommand('latex-workshop.goto-section', (filePath, lineNumber) => extension.commander.gotoSection(filePath, lineNumber))
vscode.commands.registerCommand('latex-workshop.navigate-envpair', () => extension.commander.navigateToEnvPair())
vscode.commands.registerCommand('latex-workshop.close-env', () => extension.commander.closeEnv())
const formatter = new LatexFormatterProvider(extension)
vscode.languages.registerDocumentFormattingEditProvider('latex', formatter)
@ -259,6 +262,7 @@ export class Extension {
codeActions: CodeActions
nodeProvider: SectionNodeProvider
texMagician: TeXMagician
envPair: EnvPair
constructor() {
this.extensionRoot = path.resolve(`${__dirname}/../../`)
@ -277,6 +281,7 @@ export class Extension {
this.codeActions = new CodeActions(this)
this.nodeProvider = new SectionNodeProvider(this)
this.texMagician = new TeXMagician(this)
this.envPair = new EnvPair(this)
this.logger.addLogMessage(`LaTeX Workshop initialized.`)
}