# unity-puerts-stracktrace
**Repository Path**: abcdzxcv/unity-puerts-stracktrace
## Basic Information
- **Project Name**: unity-puerts-stracktrace
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 1
- **Created**: 2021-11-04
- **Last Updated**: 2022-01-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 【unity】如何让puerts打印出来的堆栈支持vscode跳转
unity的console窗口有个特性,当某行日志符合下方格式时,将转为链接
```html
```
当用鼠标单击该链接后,将在c#中触发`OnOpenAsset`函数
```c#
[UnityEditor.Callbacks.OnOpenAssetAttribute(-1)]
static bool OnOpenAsset(int instanceID, int line)
```
而这个函数的内容是可以自定义的
借用这个特性,对日志稍加处理,就可以做到,在unity的console窗口中,直接点击typescript堆栈中的ts文件链接,通过`vscode`自动打开.ts文件并跳转到指定行
最终的效果截图如下:
1. 这是不作任何处理打印出来的堆栈

2. 这是经过改写后的堆栈,支持点击链接后自动打开vscode,并跳转到对应的.ts文件的指定行(需要安装vscode)

3. 这是点击后的效果,点击链接后,将打开vscode,并自动跳转到对应文件的指定行

## 源码
1. c#文件`PuertsStacktraceUtil.cs`(放在`Assets\Editor`路径下),用于处理点击链接的逻辑
```c#
using UnityEditor;
using UnityEngine;
using System.Runtime.InteropServices;
public static class PuertsStacktraceUtil
{
public enum VirtualKeyCode
{
Ctrl = 17,
Enter = 13,
F9 = 120,
Shift = 16,
End = 35,
LSHIFT = 0xA0,
RSHIFT = 0xA1,
}
public enum VirtualKeyFlag
{
KeyDown = 0,
Pressing = 1,
KeyUp = 2,
}
public static string ExecuteSystemCmd(string cmdline)
{
using (var process = new System.Diagnostics.Process())
{
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.StandardInput.AutoFlush = true;
process.StandardInput.WriteLine(cmdline + "&exit");
//获取cmd窗口的输出信息
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
process.Close();
return output;
}
}
[DllImport("user32.dll", EntryPoint = "keybd_event")]
static extern void keybd_event(
byte bVk, //虚拟键值 对应按键的ascll码十进制值
byte bScan, //0
int dwFlags, //0 为按下,1按住,2为释放
int dwExtraInfo //0
);
// 处理asset打开的callback函数
[UnityEditor.Callbacks.OnOpenAssetAttribute(-1)]
static bool OnOpenAsset(int instanceID, int line)
{
string fileFullPath = System.IO.Directory.GetParent(Application.dataPath) + "/" + AssetDatabase.GetAssetPath(EditorUtility.InstanceIDToObject(instanceID));
// Log("打开路径=" + fileFullPath + ",行=" + line);
if (fileFullPath.EndsWith(".ts") || fileFullPath.EndsWith(".js")) //文件扩展名类型
{
string tsFilePath = fileFullPath;
bool isDiff = (line == 999999);
bool isOpen = !isDiff;
if (isOpen)
{
int row = line / 100000;
int col = line % 100000;
// 打开vscode,跳转到指定文件的指定行
ExecuteSystemCmd($"Code --goto {tsFilePath}:{row}:{col}");
// Shift+End快捷键选中到行尾,便于高亮显示在哪一行
keybd_event((byte)VirtualKeyCode.LSHIFT, 0, (int)VirtualKeyFlag.Pressing, 0);
keybd_event((byte)VirtualKeyCode.End, 0, (int)VirtualKeyFlag.KeyDown, 0);
keybd_event((byte)VirtualKeyCode.End, 0, (int)VirtualKeyFlag.KeyUp, 0);
keybd_event((byte)VirtualKeyCode.LSHIFT, 0, (int)VirtualKeyFlag.KeyUp, 0);
// 释放shift键
keybd_event((byte)VirtualKeyCode.LSHIFT, 0, (int)VirtualKeyFlag.KeyDown, 0);
keybd_event((byte)VirtualKeyCode.LSHIFT, 0, (int)VirtualKeyFlag.KeyUp, 0);
keybd_event((byte)VirtualKeyCode.RSHIFT, 0, (int)VirtualKeyFlag.KeyDown, 0);
keybd_event((byte)VirtualKeyCode.RSHIFT, 0, (int)VirtualKeyFlag.KeyUp, 0);
}
else if (isDiff)
{
ExecuteSystemCmd($@"START """" D:\TortoiseGit\bin\TortoiseGitProc.exe /command:diff /path:""{ fileFullPath }""");
}
return true;
}
else if(fileFullPath.EndsWith(".cs"))
{
return false;
}
else if(fileFullPath.EndsWith(".prefab"))
{
GameObject prefab = EditorUtil.LoadAssetGameObject(fileFullPath);
UnityEditor.Selection.activeGameObject = prefab;
EditorGUIUtility.PingObject(prefab);
}
return false;
}
}
```
2. c#文件`LogUtil.cs`(放在`Assets\Script`路径下),用于将转换堆栈文本的格式
```c#
using UnityEngine;
using System.Text.RegularExpressions;
public class LogUtil
{
/*
将原始的ts堆栈文本改写成unity可识别的带链接的文本
传入参数:
Error:
at CheatUI.InitUI (Assets\Resources\Ts\UI\CheatUI.ts:246:16)
at CheatUI.Awake (Assets\Resources\Ts\UI\CheatUI.ts:129:8)
at chunk:1:3
at Assets\Resources\Ts\UI\FpsUI.ts:117:13
返回值:
Error:
at CheatUI.InitUI (Assets/Resources/Ts/UI/CheatUI.ts:行246:列16)
at CheatUI.Awake (Assets/Resources/Ts/UI/CheatUI.ts:行129:列8)
at chunk:1:3
at Assets/Resources/Ts/UI/FpsUI.ts:行117:列13
*/
public static string StacktraceWithHyperlinks(string stacktraceText)
{
Regex reg1 = new Regex(@"^(?[\s]+at .+ \()(?.+\..+):(?[\d]+):(?[\d]+)(?\))$");
Regex reg2 = new Regex(@"^(?[\s]+at )(?.+\..+):(?[\d]+):(?[\d]+)(?)$");
string stringBuilder = "";
string[] lines = stacktraceText.Replace("\r\n", "\n").Split('\n');
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
if (line.IndexOf("= 0)
{
file = file.Substring(index);
}
// string json = $"{{ \"file\":\"{file }\", \"row\":\"{row}\", \"col\":\"{col}\" }}";
int lineNumber = (System.Convert.ToInt32(row)) * 100000 + (System.Convert.ToInt32(col));
string convertLine = $"{begin}{file}:行{row}:列{col}{end}";
string diff = $" 预览差异";
stringBuilder += convertLine + diff;
}
else
{
stringBuilder += line;
}
if (i + 1 < lines.Length)
{
stringBuilder += "\n";
}
}
return stringBuilder;
}
}
```
3. typescript源码示例
```typescript
import { LogUtil } from "csharp"
function Example()
{
const stack: string = (new Error()).stack
console.log("原始堆栈内容=" + stack)
console.log("支持vscode跳转的堆栈=" + LogUtil.StacktraceWithHyperlinks(stack))
}
```