preface
While working on sketch plugin some time ago, I encountered a situation where I had to manually delete/comment out the log before each development. On second thought, with so many files, it is too laborious to delete logs one by one before each development. So look for plugins, but they don’t quite fit the scene, so make one yourself.
To explain why you should manually annotate logs: Since devTool for Sketch70-72 has performance issues that block processes for 2-3 seconds per log, it is best to output as few logs as possible when developing. And the project is developed by several people at the same time, so you have to comment out all the logs before starting, and then write your own logs.
Build the project
Put the scaffold
npm install -g yo generator-code
Copy the code
Scaffolding build project (note: Code is not the project name)
yo code
Copy the code
Then a bunch of options pop up (like vue and React scaffolding) and press enter if you don’t want to fill them in. Then the project is built.
General directory structure:
Project ├ ─ ─. Vscode / / generally do not need to pay attention to │ ├ ─ ─ launch. The json / / plug-in loading configuration and debugging │ └ ─ ─ the tasks. The json / / configuration TypeScript compiling ├ ─ ─ the extension, js // Import file (package.json can be modified) ├─ package.jsonCopy the code
Json and SRC /extension.json.
Tip: to run the plugin locally, press “F5” (MAC: fn + 5), or use the run Extension from vscode’s debug sidebar (the fourth icon in the sidebar shows a bug on the triangle)
Configuration file package.json
The following is a brief introduction to the configuration we will use later. The detailed configuration will be expanded when we talk about functions, but some additional configurations will not be mentioned here.
{"name": "console-key", // Plugin name // prefix "activationEvents": ["*"], // plugin trigger condition "main": "./ SRC /index.js", // entry file "contributes": {// Core configuration" configuration": {}, // Set configurable item "commands": [], // configure the command "keybindings": [], // to their own command binding shortcut keys "menus: {}, // can be vscode each function menu with our own small button},}Copy the code
Let’s start with “activationEvents” and “main”. The other properties will look familiar later.
“ActivationEvents” : used to tell vscode when to start your plug-in. For example, if the configuration value is [“onLanguage:javascript”, “onLanguage:json”], this means that your plug-in will only be activated when you open a JS file or JSON file. The [“*”] I wrote earlier means that the plugin will automatically start whenever vscode starts. More Configuration – English
“Main” : indicates the address of the plug-in’s entry file. The default value should be “./extension.js “, which I changed to “./ SRC /index.js “for my own programming habits.
Entrance to the file
The entry file has two built-in functions, activate and deactivate, which are immediately executed when the plug-in is activated, so the initialization, registration, and execution code is written in this file. Deactivate is invoked when the plugin is closed. For example, when the plugin is uninstalled, disabled, or the vscode window is closed, deactivate is invoked. You can usually do some cleanup at this point, such as logging out, clearing the cache, and saying goodBye.
To explain what a register command is: in simple terms, we need to define some commands and then tell vscode when to execute them and how to execute them.
The summary of the process is:
- Define a command
- Tell vscode when to execute this command
- Tell vscode what to do when executing this command
So my plugin is actually made up of commands, and it runs as a command drive.
Small demo
Before we start coding, let’s watch a little demo to go through the process.
Demo: Add a “Say Hello” option to the right mouse button function list, click “Hellow World!”
Step 1: Define a command
Add child objects to contributes. mands in package.json file
{"contributes": {"commands": [{// Contributes a subobject "title": "say Hello", "Test-plugin. helloWorld" // command identifier (custom)},],},}Copy the code
As above, we create a command “say Hello” identified as “test-plugin.helloworld”
Step 2: tell vscode when to execute this command
For example, if we want to add a “say Hello” option to the menu that appears when we right click the mouse in the file, the command will be executed when we click it
As shown above, we also need to add two configurations to package.json
{ "contributes": { "commands": [ { "title": "say Hello", "command": "test-plugin.helloWorld" }, ], "menus": {// Add a new menus attribute "editor/context": [// add a menu option "when": "EditorFocus ", // indicates that after the file is focused, the right mouse button should be clicked to display the option "command": "Group ": "navigation@1" // indicates the location where the option appears}]}},}Copy the code
Location of options:
At this point, we’ve completed the first two steps, and now we just need to tell vscode what to do when executing this command
Step 3: tell vscode what to do when this command is triggered
This is essentially a callback function for the command we defined earlier. When the command is fired, the callback function is executed. (Sort of like addEventListener)
// -- index.js (entry) const vscode = require('vscode'); Export function activate(context) {export function activate(context) {// Register a command, This API takes two arguments [ID, callback] // @id identifies the command registered in package.json // @callback needs to execute the function when executing the command // this tells VScode, Command "test - the plugin. The helloWorld" is triggered when the executive function "sayHello" let the disposable = vscode.com mands. RegisterCommand (' test - the plugin. The helloWorld, sayHello); / / will just subscribe to the list of commands into vscode registered context. Subscriptions. Push (the disposable); } function sayHello (){showInformationMessage Parameter is the "text" remind vscode. Window. ShowInformationMessage (' Hello World! '); }Copy the code
And then “F5” is done with one click (MAC FN + 5)
Plug-in “Console Key”
Here is a plugin for processing log information mentioned in the introduction. Then I’ll go through the full development process and some API extensions (this is important, don’t forget).
Plug-in functions are divided into two parts:
- One-click log insertion
- Delete logs in one click
One-click log insertion
Function effect
The Position and Range
To start, add two important properties, Position and Range, that will be used frequently
-
Position: The Position of a character, line number and column number. For example, in the following code segment, the position of the character “A” is line:0, character:6 (both lines and columns start at 0).
-
Range: The content between two positions is a Range, so a Range consists of two positions: “start” and “end”. For example, the following code snippet, const, starts at the first character on the first line (line:0,character:0) and ends at the sixth character on the first line (line:0,character:5). So const start:{line:0, character:0}, end:{line:0, character:5}
const a = 1
let b = 2
Copy the code
logic
Despite the amount of implementation code below, there are actually only about 20 lines of valid code in the addConsole function without comments. The detailed implementation logic is as follows:
- Get the editor of the currently open file (a set of apis for editing the current file are in it)
- Get the location information of the selected text in the current file from the editor
- Obtain the corresponding text from the location information and generate the log, for example: text: “first”, log: “console.log(‘first:’, first)”
- Cursor newline (insert log without newline)
- In order to know where to insert the log, you also need to get the new Ranges after the line break, and then convert to position, okay
- Call editor.edit to insert the log at the appropriate location
code
// -- index.js (entry) const vscode = require('vscode'); Function activate(context) {// Register let disposable = vscode.commands.registerCommand('test-plugin.addConsole', addConsole); context.subscriptions.push(disposable); } function addConsole () {/ / get the current open file editor const editor = vscode. Window. ActiveTextEditor / / editor = = = undefind If (! editor) return; Const textArray = [] // Array of location information for the currently selected text const Ranges = Editor.Selections Ranges. ForEach ((range) => {// Const text = editor.document.gettext (range) let insertText = 'console.log(); ' if (text) { insertText = `console.log('${text.replace(/'/g,'"')} : ', ${text}); '} textarray.push (insertText)}) // cursor newline calls vscode's built-in newline command, All focus cursor will wrap vscode.com mands. ExecuteCommand (' editor. Action. InsertLineAfter). Then (() = > {const editor = vscode.window.activeTextEditor; const Ranges = editor.selections; const positionList = [] Ranges.forEach((range, Const position = new vscode. position (range.start.line, range.start.character); Positionlist.push (position)}) // Edit the current file editor.edit((editBuilder) => {positionList.foreach ((position, Insert (position, textArray[index]); insert(position, textArray[index]); })}); }) } module.exports = { activate }Copy the code
Autoselect text
In order to make things easier, we’ll add a feature that automatically selects text, like this:
Select the first word to the left of the cursor, and then print log.
Does “select the first word to the left of the cursor” sound familiar? Yes, that’s what the lazy person’s “Shift + Alt +left” shortcut does. In other words, vscode already has this functionality, so can we use it directly? Of course you can, this is the next thing to say, vscode built-in function calls.
code
Async function addConsole () {// Just add this line and call the built-in command "cursorWordLeftSelect" before starting any other logic. So you need to add a async await wait for await vscode.com mands. ExecuteCommand (' cursorWordLeftSelect)... Other code}Copy the code
Type of the built-in command
Remember the front can realize the function of “cursor line”, also call vscode built-in command vscode.com mands. ExecuteCommand (‘ editor. Action. InsertLineAfter ‘), ‘editor. Action. InsertLineAfter’ is “cursor newline logo,” and this command is available on the official document [document stamp here]
However, the “cursorWordLeftSelect” command to “select the first word to the left of the cursor” is not found in official documentation and is not officially recommended (just not, it’s not like you can’t use 😂).
Therefore, I roughly divide vscode’s built-in commands into the following three types: 1. Built-in commands, which can be found in the command ID (ID) on the official website; 2. Built-in commands, which cannot be found in the official website, can be found in the keyboard Shortcut of VScode software. 3. Commands defined by ourselves or extension commands defined by other plug-ins.
The red line below is the extension command ID for other plug-ins.
Didn’t expect that? Can also call someone else’s plugin command. Wouldn’t that be doing whatever you wanted? Yeah, whatever you want. So it’s fun to develop, but at the end of the day, it’s up to us not to mess around…
Add shortcut keys
// -- package.json { "contributes": { "commands": [ { "command": "test-plugin.addConsole", "title": "Add Console"}]," Keybindings ": [// Add key {"command": "Test-plugin. addConsole", // The command ID must be the same as the command ID in the commands. "key":" Alt +c", // Windows shortcut "MAC ": "Alt + C ", // MAC shortcut "when": "editorFocus" // When this shortcut can be triggered}]},}Copy the code
User-defined attributes
Sometimes you don’t just need a simple console, you also need a recognizable console.
For example, with color:
Another example is a background color with a prefix:
And while you might like blue one day and use red the next, or change your prefix one day (forcing 😂), it’s best to make it configurable (user-defined).
So let’s just add two properties, style and prefix.
Add Configuration Add configuration in package.json
// -- package.json { "contributes": { "configuration": { "title": "Add Console Params", "properties": Suffix ": {// Attribute "test-plugin.suffix": {// attribute identifier (custom) "type": "string", "default": "[a flirty prefix => ", // attribute default" description": "Console prefix"}, "test-plugin.fixStyle": {// Attribute identifier (custom) "type": "string", "default": "Color :# FFF;background:#000", // attribute default "description":" prefix style "}}},}}Copy the code
It can then be found and modified in the vscode Settings file
Using a configuration
Using the “user custom properties” in the addConsole function
async function addConsole () { await vscode.commands.executeCommand('cursorWordLeftSelect') const editor = vscode.window.activeTextEditor if(! editor) return; Const textArray = [] const Ranges = Editor. selections // --------- const suffix = vscode.workspace.getConfiguration().get("test-plugin.suffix") const fixStyle = Vscode. Workspace. GetConfiguration (). The get (" test - plugin. FixStyle ") / / -- -- -- -- -- -- -- -- -- in the middle is to add the code -- -- -- -- -- -- -- -- -- Ranges. The forEach ((range) => { const text = editor.document.getText(range) let insertText = 'console.log(); 'if (text) {/ / -- -- -- -- -- -- -- -- -- here also want to change -- -- -- -- -- -- -- -- -- / / using the custom properties "prefix" (suffix) and "style" (fixStyle insertText =) `console.log('${suffix}${text.replace(/'/g,'"')} : ', '${fixStyle}', ${text}); ` } textArray.push(insertText) }) vscode.commands.executeCommand('editor.action.insertLineAfter') .then(()=> { const editor = vscode.window.activeTextEditor; const Ranges = editor.selections; const positionList = [] Ranges.forEach((range, index) => { const position = new vscode.Position(range.start.line, range.start.character); positionList.push(position) }) editor.edit((editBuilder) => { positionList.forEach((position, index) => { editBuilder.insert(position, textArray[index]); })}); })}Copy the code
And then you’re done
Delete logs in one click
Function effect
logic
The main logic is similar to “add Console”, which is to take the corresponding Range array and replace Console with an empty string through Range
- Gets the text of the file
- The re matches all the strings that match the rules, and reorganizes them into a Range based on the string index and length
- Iterate over Ranges, replacing Console
code
// -- index.js (entry) const vscode = require('vscode'); Function activate(context) {// Same as inject delete log command let disposable = vscode.commands.registerCommand('test-plugin.deleteConsole', deleteConsole); context.subscriptions.push(disposable); } function deleteConsole () {// rematches console.log, console.info, and the log() function // I encountered a special case, the log() function also need to delete. And just in case you're wondering, So the re remains as const logRegex = as the source code /(console.(log|debug|info|warn|error|assert|dir|dirxml|trace|group|groupEnd|time|timeEnd|profile|profileEnd|count)\((.*) \)| log\((.*)\)); ? /g; const editor = vscode.window.activeTextEditor if (! editor) return; Const document = editor.document const documentText = document.gettext () // Range array const Ranges = [] let match; While (match = logregex.exec (documentText)) {// Document.positionat (char) matchRange = new vscode.Range(document.positionAt(match.index), document.positionAt(match.index + match[0].length)) if (! matchRange.isEmpty) Ranges.push(matchRange); } // Delete the range, Editor.edit ((editBuilder) => {Ranges. ForEach ((range, index) => {editBuilder.replace(range, "); })}). Then (() => { How many the console is deleted vscode. Window. ShowInformationMessage (` ${logStatements. Length} the console, logs does `)})} module.exports = { activate }Copy the code
Scheme is advanced
In fact, there are some problems with re matching, some complex log scenarios can not handle. Such as:
- Log ends and calls the function without a line break
- Log internally puts a newline self-calling function
Both of these problems are caused by the fact that the regex cannot correctly identify the complete function call, so we can move from the “character” dimension of regex to the “syntax” dimension. Use the virtual syntax tree (AST) to find the complete log function.
I won’t go into details about AST here, otherwise it would be too long, and I won’t cover vscode plug-in development. Interested students can use the following code to try, or take a look at the source code.
code
// -- index.js (entry) const vscode = require('vscode'); / / -- -- -- -- -- -- -- -- -- in the middle is to add the code -- -- -- -- -- -- -- -- -- const recast = the require (" recast "); / / -- -- -- -- -- -- -- -- -- in the middle is to add the code -- -- -- -- -- -- -- -- -- the function activate (context) {let the disposable = vscode.commands.registerCommand('test-plugin.deleteConsole', deleteConsole); context.subscriptions.push(disposable); } function deleteConsole () { const logRegex = /(console.(log|debug|info|warn|error|assert|dir|dirxml|trace|group|groupEnd|time|timeEnd|profile|profileEnd|count)\((.*) \)| log\((.*)\)); ? /g; const editor = vscode.window.activeTextEditor if (! editor) return; Const document = editor.document const documentText = document.gettext () const Ranges = [] // --------- replace the regular match --------- / / the code into the abstract syntax tree (AST) const AST = recast. Parse (documentText) recast. Visit (AST, {visitExpressionStatement: Function (path) {// Const node = path.node const callee = node.expression.callee; // If the calling object is "console" or the calling function is "log" // contains log(), console.log(), console.info, console.warn, etc... if(callee && ((callee.object && callee.object.name === 'console') || (callee.name === 'log'))){ const _location_ = Loc // AST Number of parsed lines from 1 to 0 const start = new vscode.Position(_location_.start.line - 1, _location_.start.column) const end = new vscode.Position(_location_.end.line - 1, _location_.end.column) const endNext = new vscode.Position(_location_.end.line - 1, _location_.end.column + 1) const nextRange = creatRange(end, endNext) let matchRange = null; if(document.getText(nextRange) === '; '){ matchRange = creatRange(start, endNext) } else { matchRange = creatRange(start, end) } if (! matchRange.isEmpty) Ranges.push(matchRange); }}}) return false / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- - instead of regular matching - editor. Edit ((editBuilder) = > {Ranges. ForEach ((range, index) => { editBuilder.replace(range, ''); }) }).then(() => { vscode.window.showInformationMessage(`${logStatements.length} console.logs deleted`) }) } module.exports = { activate }Copy the code
The results of
The source code
[source code]
For easy reading, the code in the article is written in the same function, so there are some discrepancies with the source code. Is there anything wrong, please point it out in the comments section