在上一篇文章中我们已经对 VS Code 插件有了一个初步的认识与了解了,接下去我们就要“揭秘”一下市面上那些好用的 VS Code 插件究竟是如何帮我们提高工作效率的。

作者:HelloGitHub-小夏

一、从「整体」到「局部」

在开始正题之前,我们先回忆一下自己在 VS Code 上常用并且获得编码幸福度的是不是包含以下几个点。

1.1、Snippet - 代码片段

我们经常可以在不同后缀的文件还有文件里不同地方都看到代码片段。输入约定的几个短短字符,就可以拥有一片或大或小的代码段,解放双手,节约时间,还能提升每日代码量。

以下图片来自插件:vue-vscode-snippets

1.2、代码提示

解救“懒癌”的另一个常用“解药”就是代码提示了。可能平时你并不会注意到它,但是这个功能对于像我一样单词记忆水平一般且记不全所有枚举值的人来说,简直就是完美!

以下图片来自插件:vue-helper

二、从「远观」到「实践」

相信看了上面的例子,聪明的你已经深有体感啦。那接下去我们就直奔主题——实现上面所说的代码片段和代码提示功能!在这之前,我们先回到 VS Code 官网来看一下 Language Extensions API 以及他可以帮我们实现哪些。

首先 Visual Studio Code 通过语言扩展为不同的编程语言提供了智能编辑功能。虽然他不提供内置语言支持,但却提供了一组支持丰富语言功能的 API。总的来说,VS Code 插件语言类相关的 API 分为两大类,一类是「声明语言特性」,一类是「程序语言特性」。前者主要通过在配置文件中定义,而后者通过在代码中注册而激活。

2.1、Snippet Completion

我们首先从「声明语言特性」的代码片段入手,看看仅仅一份配置文件是如何帮助我们提高工作效率的。

首先,我们在 package.json 里面增加一个 snippets 的入口,位于 contributes 的下级:

"contributes": {"commands": [{"command": "test.helloGitHub","title": "Hello World"},{"command": "test.button","title": "按钮","icon": {"light": "./media/light/preview.svg","dark": "./media/dark/preview.svg"}}],"menus": {"editor/title": [{"command": "test.button","group": "navigation","when": "resourceLangId == javascript"}],"editor/context": [{"command": "test.button","group": "navigation","when": "resourceLangId == javascript"}]},// 就是这里了!!"snippets": [{"language": "javascript","path": "./snippets/javascript.json"}]
},

也就是这个位置,需要你手动新建一个文件夹和文件:

接下去就是重点、重点、重点。我们如何写代码片段的配置文件呢?如果你抱着强烈的好奇心,你可以前往官网查看这份详细的教程。如果你想先看一眼简单的配置该如何写,那就随着本文一起来看吧~

我们还是先「眼见为实」来看看下面的这份配置,会有什么奇妙的效果,先上配置代码:

{"forLoop": {"prefix": "for","body": ["for(let i = 0; i < ${1: array.length}); i++) {","\t$BLOCK_COMMENT_START HelloGitHub: 这里可以写你的代码 $BLOCK_COMMENT_END","}"],"description": "for 循环"}
}

再来看看插件运行后的提示效果(一定要看仔细哪个是来自我们插件的哦):

最后我们自信的按下「Enter」回车键,就会看到一段代码已经在我们的 js 文件里了

for(let i = 0; i < array.length); i++) {/* HelloGitHub: 这里可以写你的代码 */
}

那我们就来回顾一下上面那份配置文件,究竟是如何生成这一份代码的。

字段 含义
forLoop 是代码段名称。如果未提供 description,则通过 IntelliSense 显示
prefix 定义一个或多个在 IntelliSense 中显示摘要的触发词。
body 一或多个内容行,插入时将作为多行加入。换行符和嵌入的选项卡将根据插入代码段的上下文进行格式化
description IntelliSense 显示的代码段的描述(非必填)

首先这份配置会有一个名字即 forLoop  ,是可以用户随意自定义的,我们可以看到它支持大小写,加空格还有加横杠,当然你或许要问它支不支持中文,那我可以告诉你:支持。但是并不建议这么写,因为我们的眼界要放大嘛,走向国际(international)~

其次如果你想要匹配多个 prefix  ,你可以修改你的代码如下:

{"forLoop": {"prefix": ["for", "for-const"],"body": ["for(let i = 0; i < ${1:array.length}); i++) {","\t$BLOCK_COMMENT_START HelloGitHub: 这里可以写你的代码 $BLOCK_COMMENT_END", // \t 表示缩进,$BLOCK_COMMENT_START 和 $BLOCK_COMMENT_END 表示注释的开始和结束。 // 和 /**/ 这两种都支持"}"],"description": "for 循环"}
}

效果如下:

而且子字符串匹配是在前缀上执行的,因此,在这种情况下,fc 可以匹配 for-const

呈现的代码片段:

1、Tabstops

控制编辑器光标在代码内移动。你可以使用$1$2 指定游标的位置,数字表示 Tab 键访问的顺序,出现相同的会被同步更新,$0  表示光标最后一个位置,当光标位于指定位置的情况下就会退出这个模式。

可能光看文字你会有点迷糊,那我们直接修改上面的 for  循环:

{"For Loop": {"prefix": ["for", "for-const"],"body": ["for (const ${2:element} of ${1:array}) {", "\t$0", "}"],"description": "A for loop."}
}

效果(用 tab 切换),顺序是 $1  > $2  > $0

2、占位符

其实从前面的例子你应该就知道了占位符这个东西就是一个带有默认值的语法,例如${1:foo} 。占位符文本将被插入和选择,以便用户可以轻松更改。并且占位符还可以进行嵌套,例如${1:another ${2:placeholder}}

3、选择

当然啦对于喜欢偷懒的“我们”来说,能省一点时间是一点时间,因此占位符也可以让我们只动动上下键就可以完成输入。语法是用逗号分隔的值枚举,触发插入代码段并选择占位符后,选项将提示用户选择其中一个值。

修改我们的代码如下:

{"forLoop": {"prefix": ["for", "for-const"],"body": ["for(let i = 0; i < ${1:array.length}); i++) {","\t$BLOCK_COMMENT_START HelloGitHub: 这里可以写你的代码 $BLOCK_COMMENT_END","\t\t${2|one,two,three|}","}"],"description": "for 循环"},
}

效果:

4、变量

不知道你有没有注意上面代码中的一个小注释:

{..."\t$BLOCK_COMMENT_START HelloGitHub: 这里可以写你的代码 $BLOCK_COMMENT_END", // \t 表示缩进,$BLOCK_COMMENT_START 和 $BLOCK_COMMENT_END 表示注释的开始和结束。 // 和 /**/ 这两种都支持...
}

里面就用到了一个注释的变量 $BLOCK_COMMENT_START  和 $BLOCK_COMMENT_END 。这个语法允许我们使用$name${name:default} 这两种方式来设置插入的变量值。未设置变量时,将插入其默认值或空字符串。当变量未知(即未定义其名称)时,将插入该变量的名称,并将其转换为占位符。从 VS Code 官网上可以看到所有支持的变量:

比如我们修改我们的例子如下:

{"forLoop": {"prefix": ["for", "for-const"],"body": ["for(let i = 0; i < ${1:array.length}); i++) {","\t$BLOCK_COMMENT_START HelloGitHub: 这里可以写你的代码 $BLOCK_COMMENT_END","\t\tconsole.log('choice', ${2|one,two,three|})","\t\tconsole.log('year', ${CURRENT_YEAR})","\t\treturn ${name:value}","}"],"description": "for 循环"}

效果:

到这个例子为止你会发现我们的代码片段变得越来越长,越来越丰富,也就是我们可以偷的懒就“越来越多”,不经意间就可以提高开发效率有没有?

可能我的例子太简单你没有体感,那我们来看一个这个,应该有非常多的人眼熟:

对应的代码配置其实也就是我们上面说的那几个语法:

{"hellogithub": {"prefix": "swiper","body": ["<swiper $0 indicator-dots=\"{{${1:indicatorDots}}}\" autoplay=\"{{${2:autoplay}}}\" interval=\"{{${3:interval}}}\" duration=\"{{${4:duration}}}\">","\t<block wx:for=\"{{${5:imgUrls}}}\">","\t\t<swiper-item>","\t\t\t<image src=\"{{${6:item}}}\" class=\"slide-image\" />","\t\t</swiper-item>","\t</block>","</swiper>$7"],"description": "滑块视图容器"}
}

当然啦如果你有志于写一个非常好用的代码片段,上面这些可能还不能满足你的话,可以学习一下 TextMate 更多高级的语法(上文中其实算是 TextMate 的基础语法,言外之意就是比较常用而且看起来就很简单易懂)。简单的介绍一下 TextMate,它是 Mac下的著名的文本编辑器软件,它可以根据一定的语言规则可以匹配文档的结构,也可以按照一定的语法规则快速生成代码片段。

2.2、Completion Provider

1、初窥

上面介绍了通过配置就可以完成的「声明类语言特性」,让我们再来看一个「程序类语言特性」—— registerCompletionItemProvider

我们首先看个图,是不是也觉得是个“偷懒”神器呀!但是你有没有疑惑过,为什么这个编辑器知道我们即将要写的是什么?为什么它还可以给我们推荐写什么?如果你觉得这是计算机时代智慧的结晶的话,那我也不能说你错。那么今天,我们就亲自来“揭秘”这个功能,可以用registerCompletionItemProvider 这个来实现。

接下去我们就进入代码实现了,还记得上一篇文章的 extension.js  吗?我们在这里加上这么一段代码:

const completion = vscode.languages.registerCompletionItemProvider('javascript',{provideCompletionItems(document, position) {const linePrefix = document.lineAt(position).text.substr(0, position.character);if (!linePrefix.endsWith('hello.')) {return undefined;}return [new vscode.CompletionItem('HelloGitHub', vscode.CompletionItemKind.Property),new vscode.CompletionItem('HelloWorld', vscode.CompletionItemKind.Property),new vscode.CompletionItem('HelloPeople', vscode.CompletionItemKind.Property),];}},'.' // triggered whenever a '.' is being typed
);context.subscriptions.push(completion);

然后先来看一下效果:

这里可能会有小伙伴掉进“坑里”——如果你在实现的过程中发现效果出不来可以按下面的思路先判断和解决试试:

  • 1、看一下当前文件的后缀是不是正确的。比如上面代码里规定了 javascript ,那就要在 .js  后缀的文件里面才有效

  • 2、注册命令当然也和插件的生命周期息息相关,如果你发现上一步是正确的,那你就要去 package.json  文件里面看看 activationEvents  里面的命令是否触发了。如果你忘记如何触发插件激活的生命周期,那你就改成这样。

...
"activationEvents": ["*"
],
...
  • 3、如果上面两个还没有解决你的问题的话,那肯定是你上面代码 ctrl+c ctrl+v 的不对!开个玩笑,如果你还是不能实现的话……那你就留言评论点个赞来个三联么么哒~

回归一下正题,我们来分析一下上面的代码是如何实现的:

const completion = vscode.languages.registerCompletionItemProvider(// 这里是注册这个 Provider 有效的相关文件,支持字符串类型或 DocumentFilter 对象。// 如果你要对多个后缀的文件做操作的话可以用数组的形式,例如 ['javascript', 'plaintext']// DocumentFilter 对象包含三个字段(均非必须),例如:{ language: 'json', scheme: 'untitled', pattern: '**/package.json' }'javascript',...}
...
{// 这是代表了一个 providerprovideCompletionItems(document, position) {// 拿到当前 `position` 的 text 并且判断一下是否以 `hello.` 开头const linePrefix = document.lineAt(position).text.substr(0, position.character);// 没有匹配到则不予提示if (!linePrefix.endsWith('hello.')) {return undefined;}// 如果匹配成功就返回 CompletionItem 有:HelloGitHub、HelloWorld、HelloPeoplereturn [new vscode.('HelloGitHub', vscode.CompletionItemKind.Property),new vscode.CompletionItem('HelloWorld', vscode.CompletionItemKind.Property),new vscode.CompletionItem('HelloPeople', vscode.CompletionItemKind.Property),];}
},
...

可能你会疑惑, vscode.CompletionItemKind.Property  是什么东西呢?说简单一点其实就是个图标的配置。我们可以换几个属性来看看差别:

...
return [new vscode.CompletionItem('HelloGitHub', vscode.CompletionItemKind.Method),new vscode.CompletionItem('HelloWorld', vscode.CompletionItemKind.Enum),new vscode.CompletionItem('HelloPeople', vscode.CompletionItemKind.Property),
];
...

index.d.ts  可以看到它支持以下这么多类型的图标,可以根据不同的需求来选择你想要的图标,当然啦这里就不重点展开啦,有兴趣的可以自己把这些图标都整理一下~

/*** Completion item kinds.*/
export enum CompletionItemKind {Text = 0,Method = 1,Function = 2,Constructor = 3,Field = 4,Variable = 5,Class = 6,Interface = 7,Module = 8,Property = 9,Unit = 10,Value = 11,Enum = 12,Keyword = 13,Snippet = 14,Color = 15,Reference = 17,File = 16,Folder = 18,EnumMember = 19,Constant = 20,Struct = 21,Event = 22,Operator = 23,TypeParameter = 24,User = 25,Issue = 26,
}

最后就解释一下这个触发条件:

...  '.' // 当键盘打 . 的时候触发,支持多个触发
...

我们可能会遇到不同场景需要不同的触发条件,这时候就尽管往后加就好了,例如我们新加几个特殊符号的触发条件(这里先去掉匹配字符串的逻辑,以便于更好的触发):

const completion = vscode.languages.registerCompletionItemProvider(['javascript', 'xml'],{provideCompletionItems(document, position) {// const linePrefix = document.lineAt(position).text.substr(0, position.character);// if (!linePrefix.endsWith('hello')) {//  return undefined;// }return [new vscode.CompletionItem('HelloGitHub', vscode.CompletionItemKind.Method),new vscode.CompletionItem('HelloWorld', vscode.CompletionItemKind.Enum),new vscode.CompletionItem('HelloPeople', vscode.CompletionItemKind.Property),];}},'.',',',' '
);

2、进阶

但是正常情况下,我们往往需要去解析用户输入的不同内容,来给与不同对应的 completion item。所以接下去我们就以 xml 文件为例,来写一个“功能强大”的 Completion Proviwder。

先来分析一下 xml  这种文件常见的 Completion Provider 大致有这么三种:

  • 标签名

  • 属性名

  • 属性值

当然啦,如果像是 vue  里面 template  模板的写法,其实还有事件名这类等。那我们就以 @ 符号作为事件名提示的触发条件,以 < 作为标签名提示的触发条件,以空格、回车作为属性名的触发条件,以单双引号作为属性值的触发条件,先写一个简单的实现:

// 引入两个 mock 文件
const testEventName = require("./mock/testEventName");
const testTagName = require("./mock/testTagName");...
const completion = vscode.languages.registerCompletionItemProvider('xml',{provideCompletionItems(document, // 命令被调用的文档position, // 命令被调用的位置token, // 取消令牌context // 自动补全是怎么触发的) {// 如果校验命中了取消令牌,就不提示if (token.isCancellationRequested) {return Promise.resolve([])}let char = context.triggerCharacterswitch (char) {case '<': // 标签名提示// todocase '@': // 绑定事件// tododefault: // 属性名、属性值等// todo}}},'@','\n',' ','"',"'",'<'
)

mock 文件可以随便定一个结构,下面是本文例子中用到的 mock 数据结构(两个文件):

// ./mock/testEventName
module.exports = [{name: 'onTap',id: 'ontap',desc: '这是一个点击事件的描述'},{name: 'for',id: 'for',desc: '这是一个循环事件的描述'}
]// ./mock/testTagName
module.exports = [{name: 'HelloGitHub',id: 'hg',description: '这是我们的名字'},{name: 'Welcome',id: 'wlc',description: '欢迎关注和喜欢我们'}
]

先来实现一下标签名的 Completion Provider:

const completionArr = []
for (let i = 0; i < testTagName.length; i++) {const commandCompletion = new vscode.CompletionItem(testTagName[i].name);commandCompletion.kind = vscode.CompletionItemKind.Property;commandCompletion.documentation = new vscode.MarkdownString(testTagName[i].description);let snippet = `${testTagName[i].name}\n` +'  name="${1:HelloGitHub}"\n' +'  desc="${2:We are serious about open source}"\n' +'>\n' +`</${testTagName[i].name}>`;commandCompletion.insertText = new vscode.SnippetString(snippet);completionArr.push(commandCompletion)
}
return completionArr;

我们可以看到和上面讲过的内容差不多,也是需要 new 一个 CompletionItem  对象,但是这里把这个对象更加的“丰富化”了,通过增加属性的方式给这个 CompletionItem  增加了图标——kind、说明——documentation、还有片段——insertText

让我们来看一下效果,如果没有自动出现说明,就点一下 Completion 最右侧的小箭头:

同样的我们也来写一下事件的 Completion Provider,简直就是 ctrl+c 和 ctrl+v:

if (testEventName && testEventName.length > 0) {const arr = []for(let i = 0; i < testEventName.length; i++) {const item = testEventName[i]const commandCompletion = new vscode.CompletionItem(item.name);commandCompletion.kind = vscode.CompletionItemKind.Property;commandCompletion.documentation = new vscode.MarkdownString(item.desc || '暂无介绍');let snippet = `${item.name}{}`;commandCompletion.insertText = new vscode.SnippetString(snippet);arr.push(commandCompletion)}return arr
}
return []

效果:

接下去我们就要攻克最后的一个点:属性值和属性名。这就涉及到分析当前文本的结构,我们默认单双引号所在的位置标示属性值,挨着 < 符号的是标签名,剩下的就都是作为属性值。

所以第一步,我们写一个方法,用来解析和获取我们上面想要知道的文档结构,这一部分的代码我们写到一个新的文件引用过去(getTagAtPosition.js ):

function getTagAtPosition(doc, pos) {let offset = doc.offsetAt(pos);let text = doc.getText();// 因为引号里可能会有任何字符,所以做一层替换处理let attrFlagText = text.replace(/("[^"]*"|'[^']*')/g, replacer('%'));// 标签起始位置 [start,length]const range = getBracketRange(attrFlagText, offset);if (!range) {return null}const [start, end] = range;offset = offset - start;text = text.substr(start, end);attrFlagText = attrFlagText.substr(start, end);const tagNameMatcher = attrFlagText.match(/^<([\w-:.]+)/);if (!tagNameMatcher) {return null;}const name = tagNameMatcher[1]; // 标签名称const isOnAttrValue = attrFlagText[offset] === '%';const attrName = isOnAttrValue ? getAttrName(attrFlagText.substring(0, offset)) : '' // 当前输入对应的属性const isOnTagName = offset <= name.length + 1;const isOnAttrName = !isOnTagName && !isOnAttrValuereturn {name,            // 标签名attrName,        // 属性名isOnTagName,     // 是否处于 tag 上isOnAttrName,    // 是否处于属性名上isOnAttrValue,   // 是否处于属性值上}
}// 字符替换的方法
const replacer = (char) => (raw) => char.repeat(raw.length);// 获取 <> 标签的位置
function getBracketRange(text, pos) {const textBeforePos = text.substr(0, pos)const startBracket = textBeforePos.lastIndexOf('<')if (startBracket < 0 || textBeforePos[startBracket + 1] === '!' || textBeforePos.lastIndexOf('>') > startBracket) {// 前没有开始符<,// 或者正在注释中: <!-- | -->// 或者不在标签中: <view > | </view>return null}// 从光标位置后面找 > 标签let endBracket = text.indexOf('>', pos + 1)if (endBracket < 0) {// 未找到闭合 > 文件结束位置为结束// 如 <image ... | EOFendBracket = text.length}// 可能尚未输入闭合标签,取下一个标签的头<// 此时找到的闭合标签是下一个标签// <view xxx | ... <view ></view>const nextStart = text.indexOf('<', pos + 1)if (nextStart > 0 && nextStart < endBracket) {endBracket = nextStart}return [startBracket, endBracket - startBracket]
}

对应 extension.js  里面加上我们新写的逻辑:

...
default: // 属性、标签等
// step1. 找最近的标签名
let tag = getTagAtPosition(document, position);
if (!tag) {return null
}
// 属性值提示
if (tag.isOnAttrValue) {return getAttrValueCompletionArr(tag.attrName || '', targetObj.children)
} else {// 属性提示return getAttrCompletionArr(targetObj.children)
}
...

接下来我们加一个新的 mock 数据,并且结构是一个树状结构,每个标签下面都有它可能的属性名列表(children),同时每一个属性名都有对应的属性值列表(children):

module.exports = [{name: 'HelloGitHub',id: 'hg',description: '这是我们的名字',children: [{name: 'hgAttrName1',children: [{name: 'hgAttrVal1'},{name: 'hgAttrVal2'}]},{name: 'hgAttrName2'}]},{name: 'Welcome',id: 'wlc',description: '欢迎关注和喜欢我们'}
]

看一下上面 getAttrCompletionArr  这个方法做的事情,其实就是从数据里取值出来展示这么简单:

function getAttrCompletionArr (completionArr) {const arr = []if (completionArr.length > 0) {for(let j = 0; j < completionArr.length; j++) {if (completionArr[j] && completionArr[j].name) {const commandCompletion = new vscode.CompletionItem(completionArr[j].name);commandCompletion.kind = vscode.CompletionItemKind.Property;arr.push(commandCompletion)}}}return arr
}module.exports = getAttrCompletionArr;

那属性值的列表的话,我们就要知道它是在哪个标签名下的属性名下面了:

function getAttrValueCompletionArr (attrName, completionArr) {const enumValue = completionArr.find(item => item.name === attrName) || {};if (enumValue.children && enumValue.children.length > 0) {const arr = []for(let i = 0; i < enumValue.children.length; i++) {const commandCompletion = new vscode.CompletionItem(enumValue.children[i].name);commandCompletion.kind = vscode.CompletionItemKind.Property;arr.push(commandCompletion)}return arr}return []
}

最后的效果:

可能有的朋友对于上面一串解析文档的方法有很多疑惑,代码里虽然有注释,但是可能还是没有体感,这时候就建议最好动手实践一下,因为都是 VS Code Extension 提供的方法,所以这里不会过多展开,毕竟也不是这篇文章的重点内容嘛~

三、「总结」和「预告」

那今天给大家介绍了两种“偷懒”并且可以帮助我们提高打代码效率的两种方法:

  • 代码片段(Snippet)

  • 自动补充(Completion Provider)

也是众多 VS Code 插件中非常常见的功能之一,其实走近了看也不是很难吧~

今天的内容可能略多一点,如果你看完了第一篇,第二篇是在第一篇基础上改的,相信你一定可以跟得上。那下篇文章,我们就要来看看 VS Code 插件中另一个非常强大的功能——WebView。也就是支持在插件中打开网页、和网页通信、还可以写酷炫的 CSS 样式等等。虽然它的功能很强大,但是像一把双刃剑,他对于资源的占用也是很大的,想知道可以怎么用吗?请期待下一期。

????「点击关注」第一时间收到更新????

那些好用的 VS Code 插件,究竟是如何提高编码效率的?相关推荐

  1. 从零开始开发 VS Code 插件之 Translator Helper

    本文目录 Translator Helper 介绍 开发概述 创建第一个VS Code Extension 需求分析 操作文本 调用Google Translation API 实现核心功能 配置命令 ...

  2. 提高文档翻译效率神器:VS Code 插件之 Translator Helper

    微软 Docs 网站上线之后,我发现很多中文内容是由机器翻译的,可读性比较差.2017 年开始我参与了中文文档的本地化工作,对机器翻译的文本进行校对.Docs 的内容全部托管在 GitHub 上,参与 ...

  3. 2 snippets vue 修改配置_vue-snippets: 支持 Vue 3 的语法高亮,代码格式化和代码提示的 Visual Studio Code 插件。...

    Vue 2/3 代码片段 语法高亮 格式化插件 这是一款在 Vue 2 或者 Vue 3 开发中提供代码片段,语法高亮和格式化的 VS Code 插件,能极大提高你的开发效率. 你可以在 VS Cod ...

  4. 离线安装Visual Studio Code插件

    在使用Visual Studio Code 开发时候,有时可能会碰到需要离线安装插件的情况.这时候就需要单独下载插件包,本文就以C/C++插件包为例说明如何离线安装Visual Studio Code ...

  5. vscode插件可以直接复制到_一款可以让代码“跳舞”的 VS Code 插件:Power Mode

    VS Code 插件市场最近上架了一款名为 "Power Mode" 的插件.这款插件可以让开发者的代码跳起曼妙的舞蹈 ,比如下方的颗粒特效! 颗粒特效 这款插件启用方式也很简单, ...

  6. 作为JavaScript开发人员,这些必备的VS Code插件你都用过吗

    本文翻译自:https://www.sitepoint.com/vs-code-extensions-java-developers/ 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解 ...

  7. 程序员请收好:10个非常有用的Visual Studio Code插件

    作者 | Daan 译者 | Elle 出品 | CSDN(ID:CSDNnews) [导读]一个插件列表,可以让你的程序员生活变得轻松许多.无论你是经验丰富的开发人员还是刚刚开始第一份工作的初级开发 ...

  8. 程序员请收好:10个非常实用的 VS Code 插件

    点击上方视学算法,选择设为星标 编译:CSDN-Elle,作者:Daan 阅读文本大概需要 5 分钟 无论你是经验丰富的开发人员还是刚刚开始第一份工作的初级开发人员,你都会想让自己的开发工作尽可能轻松 ...

  9. 程序员请收好:10个非常有用的 Visual Studio Code 插件!

    一个插件列表,可以让你的程序员生活变得轻松许多. 作者 | Daan 译者 | Elle 出品 | CSDN(ID:CSDNnews) 以下为译文: 无论你是经验丰富的开发人员还是刚刚开始第一份工作的 ...

最新文章

  1. svn mysql认证_svnapachemysql 认证搭建
  2. linux lynx 源码,Lynx字符浏览器移植
  3. python学习(三)数字类型示例
  4. M1支持Windows arm_新款Macbook air,史上最强M1芯片,能买吗?有哪些注意的
  5. python 各种排序
  6. 孩子学习缺乏主动性,应该怎么做?
  7. tomcat中间件的默认端口号_死磕Tomcat系列(1)——整体架构
  8. mooc中的习题--然后是几点
  9. 【CS224N笔记】词向量和词义
  10. Linux与FreeBSD的多网卡绑定增加服务器流量
  11. 面试题算法题 URL化and重新排列字符
  12. 快解析助力服装企业实现ERP远程外网访问
  13. 使用html表单制作简单网页(加表单详细知识点)
  14. 巧用京东物流分享链接批量查询多个京东快递的物流信息
  15. opencv-图像翻转问题
  16. A/B【费马小定理】
  17. 生产计划排产的十二个应用场景
  18. css cursor 鼠标指针样式总结
  19. Windows10环境下自己配置Pytracking详细流程(有参考博客)
  20. 视觉检测设计与实践答题卡检测实验报告

热门文章

  1. C#读mp3歌曲信息
  2. 使用CreateFileMapping来进行进程间的通信和使用信号量来进行同步操作——进程间实现图片传输
  3. 采用HVS的图像相似度准则计算WPSNR
  4. 2023五一杯建模C题思路 - “双碳”目标下低碳建筑研究
  5. python文件属性判断(是否存在,是否为空)
  6. 机器视觉技术在汽车行业的应用
  7. 虫虫blog html教程,虫虫的成长历程
  8. 线上服务质量的问题该如何去处理?你有什么思路?
  9. Java程序员必经的实践之路:docker离线导入镜像
  10. Jbuild2005安装