QuickxDev插件(二) debug.log文件的处理

  1. 1. 一. 对 debug.log 文件进行配色
  2. 2. 二. 启动 Player 时打开 debug.log 文件
  3. 3. 三. 错误堆栈/dump 的跳转
    1. 3.1. 1. 添加鼠标双击的响应
    2. 3.2. 2. 获取鼠标双击的所在行
    3. 3.3. 3. 计算跳转的文件和行号
    4. 3.4. 3. 跳转

上篇文章中我们说过要对debug.log文件做一些特殊处理, 这里我们来看看具体的实现.

一. 对 debug.log 文件进行配色

debug.log文件默认是没有配色的, 灰不拉几不好看. 实现这个其实非常简单, 每一个打开的文件都是一个sublime.View, sublime.View中有现成的函数set_syntax_file来设置文件句法规则, 让我们来看下定义:

set_syntax_file(syntax_file)
Changes the syntax used by the view. syntax_file should be a name along the lines of Packages/Python/Python.tmLanguage. To retrieve the current syntax, use view.settings().get(‘syntax’).

这个函数其实对应的是 Sublime 菜单栏中的这个:

大家可以多试几次, 挑一个好看点的, 我选中的是 Java 的语法(路径为:Packages/Java/Java.tmLanguage).

下面我们要监听文件 debug.log 的打开, 监听事件需要继承sublime_plugin.EventListener, QuickxDev 已经有了 QuickxListener, 查询 api 可以得知可以监听 on_load 事件来实现.

on_load(view)
Called when the file is finished loading.

实现的代码也非常的简单:

1
2
3
4
5
6
class QuickxListener(sublime_plugin.EventListener):
...
def on_load(self, view):
print(view.file_name())
if view.file_name().find("debug.log") != -1:
view.set_syntax_file("Packages/Java/Java.tmLanguage")

让我们来张对比图:

二. 启动 Player 时打开 debug.log 文件

因为种种原因, 我的 debug.log 文件没有在 Sublime 工程中, 所以每次想看日志都得在磁盘上找到这个文件, 拖进来, 略显繁琐. 因此萌生了运行 player 时顺带打开 debug.log 文件的需求.

实现起来也十分简单, 查询 api 得知:

1
2
3
4
5
6
open_file(file_name, <flags>)	
Opens the named file, and returns the corresponding view. If the file is already opened, it will be brought to the front. Note that as file loading is asynchronous, operations on the returned view won't be possible until its is_loading() method returns False.
The optional flags parameter is a bitwise combination of:

sublime.ENCODED_POSITION. Indicates the file_name should be searched for a :row or :row:col suffix
sublime.TRANSIENT. Open the file as a preview only: it won't have a tab assigned it until modified

file_name文件名不说, 后面的 flags 比较有意思:

  1. sublime.ENCODED_POSITION 选项可以再在打开文件的同时定位到某一行/某一列,非常有用.
  2. sublime.TRANSIENT 选项意思是只是预览它, 直到手动改变内容前不创建标签 .

下面我们找到启动 Player 的地方, 因为file_name是全路径, 所以我们需要找到工程的路径. 启动 player 时需要传入 -workdir 参数, 我们用这个就行. 实现同样十分简单:

1
2
3
4
5
6
7
workdir = os.path.split(path)[0]
args.append("-workdir")
args.append(workdir)

...

view.window().open_file(workdir+"/debug.log")

三. 错误堆栈/dump 的跳转

当我查看debug.log 文件时, 经常会看到类似的语句:

1
2
3
4
5
6
7
8
1.lua 语法错误
/Users/XXXX/scripts/app/MyApp.lua:117: '=' expected near 'a'

2. lua 运行时错误
Cocos2d: [LUA ERROR] [string "/Users/XXX/app/MyApp.lua"]:118: attempt to index local 'a' (a nil value)

3. dump 信息
Cocos2d: [0.0724] dump from: [string "/Users/XXX/scripts/app/MyApp.lua"]:117: in function 'ctor'

他们之中都包含了文件名+行号信息, 联想到上面提到打开文件时可以跳转到某一行, 是不是隐隐感觉可以做些什么?

没错, 基于这个, 我们能够很容易的实现双击错误信息后跳转到错误位置.

1. 添加鼠标双击的响应

这个比较简单, 我们需要在 QuickxDev 文件夹中创建 Default.sublime-mousemap 文件, 内容如下:

1
2
3
4
5
6
7
8
[
{
"button": "button1", "count": 2,
"press_command": "drag_select",
"press_args": {"by": "words"},
"command": "my_special_doubleclick"
}
]

这样在双击左键的时候会发送 my_special_doubleclick 事件, 我们在 quickx.py 中定义它:

1
2
3
4
5
6
7
class MySpecialDoubleclickCommand(sublime_plugin.TextCommand):
def run(self, edit):
if not self.view.file_name():
return
file_path, file_name = os.path.split(self.view.file_name())
if file_name == "debug.log":
print(file_path, file_name)

我们双击 debug.log 文件的某一行就会收到MySpecialDoubleclickCommand命令, 进而打印出文件名, 路径!

2. 获取鼠标双击的所在行

因为双击会选中某个单词, 所以我们可以使用sublime.View.sel()来获取当前选中的行:

1
2
3
4
5
6
for region in self.view.sel():
if not region.empty():
line = self.view.substr(self.view.line(region.a))
break

print("line:",line)

3. 计算跳转的文件和行号

因为不会正则, 只能用一些土办法来计算文件和行号, 代码如下, 比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def parseLuaError(line):
key = ".lua:"
posKeyStart = line.find(key)
if posKeyStart == -1:
return False,""
posKeyEnd = posKeyStart + len(key)
filename = line[:posKeyStart].strip(" \t")+".lua"
fileline = line[posKeyEnd:line.find(":",posKeyEnd)]

return True, filename+":"+fileline

def parseCocosErrorAndDump(line):
filename = ""
fileline = 1
keyHead = "[string \""
posHeadStart = line.find(keyHead)
if posHeadStart == -1:
return False,""

posHeadEnd = posHeadStart + len(keyHead)

key = ".lua\"]:"
posKeyStart = line.find(key,posHeadStart)

if posHeadStart == -1:
return False,""

posKeyEnd = posKeyStart + len(key)

filename = line[posHeadEnd:posKeyStart]+".lua"
fileline = line[posKeyEnd:line.find(":",posKeyEnd)]

return True, filename+":"+fileline

想想还是正则会比较简单, 以后有机会在改吧!

3. 跳转

在上面的步骤中我们拿到了文件名和行号, 现在我们只需要跳转就OK了!

1
2
3
4
5
6
7
8
9
10
def parseLine(self, line):
re,string = self.parseCocosErrorAndDump(line)
if re:
return re,string

return self.parseLuaError(line)

re,filename = self.parseLine(line)
if re:
self.view.window().open_file(filename,sublime.ENCODED_POSITION)

是啦,就是这么简单! 意外的发现这个功能对错误堆栈的跳转十分给力! 录了几个小 GIF, 感受一下吧!

演示一,演示二,演示三,演示四

其实这些功能实现起来都不难, 但是却能令自己感觉很爽很有成就感, 这恐怕就是做游戏所不能体会到的吧! 哈哈!

(本文完)