您的位置:首页 > 编程语言 > PHP开发

跨平台PHP调试器设计及使用方法——界面设计和实现

2016-11-03 18:34 591 查看
        一个优秀的交互设计往往会影响一个产品的命运。在设计这款调试器时,我一直在构思这款调试器该长什么样子。简单、好用是我设计的原则,于是在《跨平台PHP调试器设计及使用方法——立项》一文中,我给出了一个Demo。之后实现的效果也与之变化并不大。(转载请指明出于breaksoftware的csdn博客)
        在《跨平台PHP调试器设计及使用方法——立项》一文中,我阐述了该款调试器将采用网页的形式提供交互操作。这样我们需要一个Python实现的Web框架。在这个大的开源的环境下,我们可以轻易找到一些优秀的库来辅助我们快速开发一些功能。比如之前我选择pydbgp库去和Xdebug进行通信,这样就规避了很多协议的底层实现工作。同样在Web框架这块,我决定使用比较轻量级bottle库。
        bottle库的官方网址是http://www.bottlepy.org/。其文档可见http://www.bottlepy.org/docs/dev/。bottle库使用起来非常方便,在我们工程的phpdebugserver.py中,我们先引入bottle库from bottle import route, run
from bottle import static_file
from bottle import template
from bottle import request, response, get, post        route是路由功能。当我们请求一个URL时,Web框架需要将URL中路径信息或者参数信息对应到一个处理逻辑中。比如我们对http://xxxx/cmd进行发送Post请求,则可以通过下面这种路由方式指定处理逻辑
@route('/cmd', method='POST')
def request_cmd_post():
ParamValue = request.forms.get("ParamKey")
        我们可以从request.forms里获取Post请求的参数,上例中就是获取请求中ParamKey对应的值。        static_file用于返回一个文件。作为一个提供Web服务的服务器,它上面可能保存了一些用户需要直接拉取的文件。最典型的一个例子就是网页中往往包含了一些JavaScript文件。这些文件就可以通过这个接口返回。比如@route('/files/<filepath:path>')
def highlight_file(filepath):
return static_file(filepath, root='views')        当浏览器中请求http://xxxx/files/window.js时,就会被路由到该函数。static_file方法传入的是第一个参数是相对路径,即“files/windows.js”,第二个参数传递的是相对目录对应的根目录。上例中我们传递的是views,则返回的文件是在当前路径下的views/files/windows.js。        template是bottle提供的模板功能。当我们提供一些网页时,其大体的框架是相同的,改变的是其内容实体。比如我们展现编辑已保存的请求的界面时@route("/request", method='get')
def request_action():
……
elif "edit_data" == action:
data = rdb.get_request(param_de)
return template('component/edit_request', data = json.dumps(data), name = param_en)        其窗体格式样式一样,而内容我们可以通过上述template方法传入





        我基本也就使用了上述几个bottle的功能。在发布工程时,把bottle.py这个文件放在工程中即可。
        有了Web服务器,我们还需要一个界面框架。这么大一个工具,我们可以想象界面上需要类似桌面系统中软件界面的相关控件,比如ViewList等。当然这些东西也不会是我们从头开始实现,我们还是采用拿来主义。这次我选择的是Jquery EasyUI库。
        选择Jquery EasyUI前,我先翻看了下它的支持控件和相关文档。我可以预见的控件基本都在这个框架中被找到,而且其样例和文档也非常丰富。最终我决定选择它作为我们UI框架。Jquery EasyUI的官网主页是http://www.jeasyui.com/;Demo的地址是http://www.jeasyui.com/demo/main/index.php;相关文档的地址是http://www.jeasyui.com/documentation/index.php。
        我们先看下网页文件的组织结构



        frame.tpl是我们主界面的描述文件,实际它的内容比较空,它只是包含了若干JavaScript文件、样式文件以及界面的组成模板的路径。<html>
<head>
<link rel="stylesheet" type="text/css" href="/files/third/jquery-easyui-1.4.5/themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="/files/third/jquery-easyui-1.4.5/themes/icon.css">
<link rel="stylesheet" href="/files/third/highlight/styles/tomorrow-night-eighties.css"></link>
<link rel="stylesheet" type="text/css" href="/files/themes/debugger.css">

<script src="/files/third/jquery3_1/jquery-3.1.0.js" type="text/javascript"></script>

<script type="text/javascript" src="/files/js/edit_request.js"></script>
<script type="text/javascript" src="/files/js/debug_setting.js"></script>
<script type="text/javascript" src="/files/js/tools.js"></script>
<script type="text/javascript" src="/files/js/modify_variable.js"></script>
<script type="text/javascript" src="/files/js/request.js"></script>
<script type="text/javascript" src="/files/js/status.js"></script>
<script type="text/javascript" src="/files/js/console.js"></script>
<script type="text/javascript" src="/files/js/variables.js"></script>
<script type="text/javascript" src="/files/js/call_stack.js"></script>
<script type="text/javascript" src="/files/js/breakpoint.js"></script>
<script type="text/javascript" src="/files/js/files_watch.js"></script>
<script type="text/javascript" src="/files/js/variables_watch.js"></script>
<script type="text/javascript" src="/files/js/debug.js"></script>
<script type="text/javascript" src="/files/js/files_tree.js"></script>
<script type="text/javascript" src="/files/js/view.js"></script>
<script type="text/javascript" src="/files/third/jquery-easyui-1.4.5/jquery.min.js"></script>
<script type="text/javascript" src="/files/third/jquery-easyui-1.4.5/jquery.easyui.min.js"></script>
<script src="/files/third/jquery_timer/jquery.timer.js"></script>
<script src="/files/third/highlight/highlight.pack.js"></script>
<script src="/files/third/json-viewer/jquery.json-viewer.js"></script>
<script>hljs.initHighlightingOnLoad();</script>

<script src="/files/third/jquery_base64_js/jquery.base64.js"></script>

<script type="text/javascript">
//$.ajaxSetup({
// async: false,
// cache:false
//});
$.ajaxSetup({
timeout : 10000
});

function base64_decode(data) {
return $.base64.atob(data, true);
}
function base64_encode(data) {
return $.base64.btoa(data);
}
function highlight_code(type, source) {
return hljs.highlight(type, source);
}
</script>

<title>Cmd Shell</title>
</head>
<body>
<div id="php_debugger" class="easyui-window" title="Php Debugger" data-options="iconCls:'icon-sum',footer:'#ft'" style="width:100%;height:900px;min-width:900px;min-height:800px;padding:0px;top:0;">
<div class="easyui-layout" style="width:100%;height:100%;min-width:800px;padding:0px;">
%include('top_menu_layout.tpl')
%include('botton_tab_layout.tpl')
%include('folder_layout.tpl')
%include('source_layout.tpl')
%include('request_layout.tpl')
</div>
</div>

<div id="ft" style="padding:5px;">OFF</div>

%include('component/file_menu.tpl')
%include('component/console_dlg.tpl')
%include('component/add_folder_dlg.tpl')
%include('component/edit_request_dlg.tpl')
%include('component/save_request_dlg.tpl')
%include('component/variables_show_dlg.tpl')
%include('component/add_files_watch_dlg.tpl')
%include('component/modify_variable_dlg.tpl')
%include('component/breakpoint_add_dialog.tpl')
%include('component/add_variables_watch_dlg.tpl')
%include('component/setting_dlg.tpl')

</body>
</html>        上述简单的描述,便可以组织出下图的界面。这种模板组织方式还是非常方便使用的。


        除了上述几个大的模板,还有代码中列出的小的模板文件。这些文件一般是一些弹窗界面描述,以console_dlg.tpl为例,它是



        由于该调试器界面元素非常多,我也不可能在一篇博文中将所有实现和细节讲完。但是为了契合该博文的标题,我就以上图界面为例,讲解下该界面的实现和工作原理。
        我们先看下界面描述内容<div id="console_dlg" class="easyui-dialog" title="Debug Console" style="width:900px;height:800px;padding:10px" data-options="iconCls:'icon-search',resizable:true,modal:true" closed="true">
<div id="console_dlg_div" style="width:100%;height:100%;">
<div data-options="region:'center'" style="width:100%;">
<div style="margin:0px 0;width:100%;height:100%">
<textarea id="console_dlg_view" style="width:100%;height:100%;" readonly="true"></textarea>
</div>
</div>
<div data-options="region:'south'" style="height:26px;width:100%;">
<div style="margin:0px 0;width:100%;height:100%">
<input id="console_dlg_cmd" class="easyui-textbox" data-options="multiline:false" value="" style="width:100%;height:100%">
</div>
</div>
</div>
</div>        该界面的ID是console_dlg。它是该窗体的唯一标示,我们在主界面中点下Console按钮后,执行下面Javascript以打开该窗口
function console_dlg_open() {
$('#console_dlg').dialog('open').dialog('center');
}
        console_dlg的控件类型是其class描述的easyui-dialog。它可以通过东、南、西、北、中五个模块去组合。我们主界面就是通过这五个模块组合的。而console_dlg窗口只使用了中、南两个模块。位于中间的这个模块是调试窗口的输出内容的载体,其核心是一个ID为console_dlg_view的textarea控件。当我们在调试窗口输入调试指令后,它会显示在该区域中,然后该指令的结果也会显示在该区域。这些都是通过下面这个函数实现的function append_debug_view(text) {
var new_text = $('#console_dlg_view')[0].value + "\n" + text;
$('#console_dlg_view')[0].value = new_text;
var scrollTop = $("#console_dlg_view")[0].scrollHeight ;
$("#console_dlg_view").scrollTop(scrollTop);
}        为了更加人性化,第4、5两行实现了滚动条的自动更新。        位于南部的是一个输入框,它的ID是console_dlg_cmd。我们在这个输入框中输入命令,按回车使得命令执行并清除该输入框内容。还可以按上下键翻看前后的历史命令记录。这块内容我们放在窗口初始化后的执行事件中。var console_cmd_list = [];
var console_cmd_list_index = 0;

$(document).ready(function(){
$('#console_dlg_cmd').textbox('textbox').bind('keydown', function(e){
if (e.keyCode == 13){ // when press ENTER key, accept the inputed value.
var cmd = $(this).val();
console_cmd_list.push(cmd);
console_cmd_list_index = console_cmd_list.length;
excute_console_cmd(cmd);
$('#console_dlg_cmd').textbox('setValue', '');
} else if (e.keyCode == 38){
if (console_cmd_list_index > 0) {
console_cmd_list_index = console_cmd_list_index -1;
var cmd = console_cmd_list[console_cmd_list_index];
$('#console_dlg_cmd').textbox('setValue', cmd);
}
} else if (e.keyCode == 40){
if (console_cmd_list_index < console_cmd_list.length - 1) {
console_cmd_list_index = console_cmd_list_index +1;
var cmd = console_cmd_list[console_cmd_list_index];
$('#console_dlg_cmd').textbox('setValue', cmd);
}
}
});

$('#console_dlg_div').layout();
});        第10行的excute_console_cmd方法才是界面和我们调试器核心交互的地方。我们看下它的实现
function excute_console_cmd(cmd) {
var param = '{"cmd":"' + base64_encode(cmd) + '"}';
var param_en = base64_encode(param);
$.post("do", {"action":"query", "param":param_en},
function(data){
append_debug_view(cmd);
append_debug_view(eval(data));
console.log(data);
}, "");
}
        第二行我们先组织一个Json型的字符串指令,然后第三行对其进行Base64编码。第四行我们向服务器请求一个路径为do的请求,其参数分为action和param两个部分。在Python的Web服务器层,我们通过Bottle框架的如下代码实现对该请求的响应
@route('/do', method='POST')
def request_do_post():
action = request.forms.get("action")
if None == action or len(action) == 0:
return template('index')
param = request.forms.get("param")
global debugger
(ret,type) = debugger.do(action, param);
if type == "json":
return json.dumps(ret)
else:
return template('index', **request.forms)
        服务器返回结果后,我们便可以通过append_debug_view方法将内容显示到界面中了。
        除了一般的界面,我们还有个非常重要的控件——highlight。它负责将源码文件进行渲染,否则网页中打开的代码可能就是文本文件风格,非常不友好。它的地址是https://highlightjs.org/。目前这个控件还不支持显示行号,所以这块我们做了特殊处理。具体的处理方法可以参见之后公开的源码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐