起因
前一篇写了反射类生成php速查表。过年了在家也没事,想完成自己想要实现的sublime自动完成文件生成。就是sublime里输入关键字后按tab自动补全一个全的函数名和参数的,按tab可以切换一致到最后的。也是给别人的承诺,今年肯定会实现sublime的tp5插件的。
地址在 https://github.com/yangweijie/SublimeThinkPHP5.0
思路
搞清sublime 完成的写法
"HTML_FILE_SUFFIX",
"TEMPLATE_NAME",
{ "trigger": "_ad", "contents": "protected function _after_delete(//$data,//$options) {/n ${1:}/n}$0" },
{ "trigger": "_af", "contents": "protected function _after_find(&//$result,//$options) {/n ${1:}/n}$0" },
{ "trigger": "_ai", "contents": "protected function _after_insert(//$data,//$options) {/n ${1:}/n}$0" },
{ "trigger": "_as", "contents": "protected function _after_select(&//$result,//$options){/n ${1:foreach(//$result as &//$record)//{/n ${2://$this->_after_find(//$record,//$options);}/n //}}/n}$0" },
{ "trigger": "_au", "contents": "protected function _after_update(//$data,//$options) {/n ${1:}/n}$0" },
{ "trigger": "_bi", "contents": "protected function _before_insert(&//$data,//$options) {/n ${1:}/n}$0" },
{ "trigger": "_bu", "contents": "protected function _before_update(&//$data,//$options) {/n ${1:}/n}$0" },
{ "trigger": "->_empty", "contents": "//$this->_empty()$0" },
{ "trigger": "_get", "contents": "_get('${1://$name}')$0" },
{ "trigger": "_post", "contents": "_post('${1://$name}')$0" },
{ "trigger": "->_sql", "contents": "->_sql('${1://$name}')$0" },
{ "trigger": "->addAll", "contents": "->addAll(//$${1:dataList},//$${2:options},//$${3:replace})$0" },
这个是写在Sublime的ThinkPHP 插件里 php.sublime-completions 文件里的。
completion 和snippet的区别在于 snippet只能一个文件一个补全。命令面版里有提示。
字符串
json 里直接字符串
tab占位
$0
完成的最后光标
tab 选中光标 ${1:XX}
1自增下标,: 选中的内容
trigger 触发器
表示什么文本末按tab会触发完成。
注意点
$ 换行之类的要转义
php如何获取
先上核心代码:
<?php
namespace util;
class SnippetBuilder
{
public $cme;
public function __construct($classes)
{
// if (false == defined('THINK_VERSION')) {
// throw new /Exception('请在thinkphp应用中执行本类', 1);
// }
$this->cme = new ClassMethodExtractor();
// $this->framework_type = version_compare(THINK_VERSION, '5') >= 0 ? 5 : 3;
$this->classes = $classes;
}
public function buildAll($path)
{
$consts = $this->getConsts();
$functions = $this->getFunctions();
$classes = $this->classes;
$this->ret = [];
if ($consts) {
$this->buildConsts($consts);
}
if ($functions) {
$this->buildFunction($functions);
}
if ($classes) {
foreach ($classes as $class) {
$class2 = $this->cme->getClassAnnotation(new /ReflectionClass($class));
$this->buildClass($class2);
}
}
if ($this->ret) {
file_put_contents($path, $this->parseAll($this->ret));
} else {
exit('没有可生成的内容');
}
}
// 获取常量数组
public function getConsts()
{
$all_consts = get_defined_constants(true);
return array_keys($all_consts['user']);
}
// 生成常量完成
public function buildConsts($consts)
{
foreach ($consts as $key => &$const) {
$this->ret[] = $const;
}
}
// 生成类的完成
public function buildClass($classes)
{
}
// 获取定义的函数
public function getFunctions()
{
$arr = get_defined_functions();
$ret = [];
foreach ($arr['user'] as $key => $name) {
$foo = [];
$refFunc = new /ReflectionFunction($name);
$foo['args'] = $refFunc->getParameters();
$ret[$name] = $foo;
}
return $ret;
}
// 生成函数
public function buildFunction($functions)
{
}
}
常量
// 获取常量数组
public function getConsts()
{
$all_consts = get_defined_constants(true);
return array_keys($all_consts['user']);
}
php有函数,且会区分core user 和扩展的。我们只需要user定义,也就是tp框架定义的。
函数
// 获取定义的函数
public function getFunctions()
{
$arr = get_defined_functions();
$ret = [];
foreach ($arr['user'] as $key => $name) {
$foo = [];
$refFunc = new /ReflectionFunction($name);
$foo['args'] = $refFunc->getParameters();
$ret[$name] = $foo;
}
return $ret;
}
php也有个获取全部函数名的方法。同样只拿user
然后新的函数反射类 new /ReflectionFunction($name);
获取它的参数 $refFunc->getParameters();
类
$all_class = get_declared_classes();
这回没有user好事了,不过可以反射类isInternal区分。
// 获取全部用户定义的类
public function getAllUserClasses(){
$all_class = get_declared_classes();
$ret = [];
foreach ($all_class as $class) {
$rf = new /ReflectionClass($class);
if(false == $rf->isInternal()){
if('app/index/controller/Index' == $class){
continue;
}
$ret[] = $class;
}
}
return $ret;
}
if ($classes) {
foreach ($classes as $class) {
$class2 = $this->cme->getClassAnnotation(new /ReflectionClass($class));
$this->buildClass($class2);
}
}
用cme类提取出类和对应方法及参数。
生成
我写了 SublimeSnippetBuilder来继承我之前的 SnippetBuilder,因为我想做的是所有文本编辑器的完成生成。先架构好结构,以后慢慢填坑。
先整体代码:
<?php
namespace util;
class SublimeSnippetBuilder extends SnippetBuilder
{
// 生成函数
public function buildFunction($functions)
{
foreach ($functions as $name => $fun) {
$args_arr = [$name, '(', ')'];
if ($fun['args']) {
$args_arr = [$name, '('];
$index = 1;
foreach ($fun['args'] as $key => $arg) {
$p = new /ReflectionParameter($name, $key);
if ($p->isPassedByReference()) {
$arg_str_new = '/&//$' . $p->getName();
} else {
$arg_str_new = '//$' . $p->getName();
}
if ($p->isOptional() && $p->isDefaultValueAvailable()) {
// 获取某些内部类的参数会抛异常,且异常时$class会变化不是我们想知道的哪个类方法一场了
try {
$defaul = $p->getDefaultValue();
$arg_str_new .= is_array($defaul) ? ' = []' : ' = ' . var_export($defaul, 1);
} catch (/Exception $e) {
trace($p->isVariadic());
trace($name . '_' . $key);
}
}
if ($index == 1) {
$p_str = sprintf('${%d:%s}', $index, $arg_str_new);
} else {
$p_str = sprintf('${%d: ,%s}', $index, $arg_str_new);
}
$args_arr[] = $p_str;
$index++;
}
$args_arr[] = ')';
}
$contens = implode('', $args_arr) . '$0';
$foo = [
'trigger' => $name,
'contents' => $contens,
];
$this->ret[] = $foo;
}
}
public function buildClass($class)
{
if($class['methods']){
foreach ($class['methods'] as $name => $fun) {
switch ($fun['type']) {
case 'public_static':
case 'private_static':
$trigger_name = "::{$name}";
break;
case 'public_public':
case 'private_public':
$trigger_name = "->{$name}";
break;
default:
$trigger_name = '';
break;
}
$args_arr = [$trigger_name, '(', ')'];
if (empty($fun['args']) == false) {
$args_arr = [$trigger_name, '('];
$index = 1;
foreach ($fun['args'] as $key => $p) {
if ($p->isPassedByReference()) {
$arg_str_new = '/&//$' . $p->getName();
} else {
$arg_str_new = '//$' . $p->getName();
}
if ($p->isOptional() && $p->isDefaultValueAvailable()) {
// 获取某些内部类的参数会抛异常,且异常时$class会变化不是我们想知道的哪个类方法一场了
try {
$defaul = $p->getDefaultValue();
$arg_str_new .= is_array($defaul) ? ' = []' : ' = ' . var_export($defaul, 1);
} catch (/Exception $e) {
trace($p->isVariadic());
trace($name . '_' . $key);
}
}
if ($index == 1) {
$p_str = sprintf('${%d:%s}', $index, $arg_str_new);
} else {
$p_str = sprintf('${%d: ,%s}', $index, $arg_str_new);
}
$args_arr[] = $p_str;
$index++;
}
$args_arr[] = ')';
}
$contens = implode('', $args_arr) . '$0';
$foo = [
'trigger' => $trigger_name,
'contents' => $contens,
];
$this->ret[] = $foo;
}
}
}
public function parseAll($ret)
{
// dump($ret);
$ret = [
"scope" => "source.php - variable.other.php",
"completions" => $ret,
];
return json_encode($ret, JSON_PRETTY_PRINT);
}
}
其实 就是生成对应的多维数组,数字索引为常量,函数方法为多维数组。
函数的trigger就是函数名,类无非::
、->
后跟方法名
然后区分代参不带参,带参再遍历参数,区分引用和默认值的情况。用了sprintf 方法拼接${index:content} 这种格式。
最终生成json,用了JSON_PRETTY_PRINT 格式化输出后写文件。
整体思路还是很清晰的。
其他语言的可以借鉴。
效果:
一下子两千行,手写得累死。
其实,大家只需扩展getAllUserClasses 就可以生成其他类的,比如swoole的。
题外话,新插件提交wbond 开始用branch 结果不推荐了要tag。我折腾了几次才通过ci。人家不高兴了。不给我过,有能力的直接git clone吧。视图那没找到规律先手写的。
原创文章,作者:3628473679,如若转载,请注明出处:https://blog.ytso.com/tech/opensource/193921.html