|
| 1 | +<script lang="ts" setup> |
| 2 | +import { ref, watch } from "vue"; |
| 3 | +
|
| 4 | +// Define TypeScript interfaces for settings and supported languages |
| 5 | +interface NetStackOptions { |
| 6 | + frame?: string; |
| 7 | + type?: string; |
| 8 | + method?: string; |
| 9 | + paramsList?: string; |
| 10 | + paramType?: string; |
| 11 | + paramName?: string; |
| 12 | + file?: string; |
| 13 | + line?: string; |
| 14 | +} |
| 15 | +
|
| 16 | +interface Language { |
| 17 | + name: string; |
| 18 | + at: string; |
| 19 | + in: string; |
| 20 | + line: string; |
| 21 | +} |
| 22 | +
|
| 23 | +type Text = string; |
| 24 | +
|
| 25 | +interface Node { |
| 26 | + params: Array<{ name: string; type: string }>; |
| 27 | + type: string; |
| 28 | + lineNumber?: number; |
| 29 | + file?: string; |
| 30 | + method: string; |
| 31 | + spaces: string; |
| 32 | +} |
| 33 | +
|
| 34 | +type Element = Text | Node; |
| 35 | +
|
| 36 | +// Props |
| 37 | +const props = withDefaults(defineProps<{ stackTrace: string; options?: NetStackOptions }>(), { |
| 38 | + options: () => ({ |
| 39 | + frame: "st-frame", |
| 40 | + type: "st-type", |
| 41 | + method: "st-method", |
| 42 | + paramsList: "st-frame-params", |
| 43 | + paramType: "st-param-type", |
| 44 | + paramName: "st-param-name", |
| 45 | + file: "st-file", |
| 46 | + line: "st-line", |
| 47 | + }), |
| 48 | +}); |
| 49 | +
|
| 50 | +// Supported languages and their keywords |
| 51 | +const languages: Language[] = [ |
| 52 | + { name: "english", at: "at", in: "in", line: "line" }, |
| 53 | + { name: "danish", at: "ved", in: "i", line: "linje" }, |
| 54 | + { name: "german", at: "bei", in: "in", line: "Zeile" }, |
| 55 | + { name: "spanish", at: "en", in: "en", line: "línea" }, |
| 56 | + { name: "russian", at: "в", in: "в", line: "строка" }, |
| 57 | + { name: "chinese", at: "在", in: "位置", line: "行号" }, |
| 58 | +]; |
| 59 | +
|
| 60 | +// Reactive variables and setup state |
| 61 | +const formattedStack = ref<Element[]>([]); |
| 62 | +const selectedLanguage = ref<Language>(languages[0]); |
| 63 | +
|
| 64 | +// Helper function to detect languages in the stack trace |
| 65 | +const detectLanguagesInOrder = (text: string): Language[] => { |
| 66 | + const languageRegexes = { |
| 67 | + english: /\s+at .*?\)/g, |
| 68 | + danish: /\s+ved .*?\)/g, |
| 69 | + german: /\s+bei .*?\)/g, |
| 70 | + spanish: /\s+en .*?\)/g, |
| 71 | + russian: /\s+в .*?\)/g, |
| 72 | + chinese: /\s+在 .*?\)/g, |
| 73 | + }; |
| 74 | +
|
| 75 | + const detectedLanguages: Language[] = []; |
| 76 | + for (const lang in languageRegexes) { |
| 77 | + if (languageRegexes[lang as keyof typeof languageRegexes].test(text)) { |
| 78 | + const foundLang = languages.find((l) => l.name === lang); |
| 79 | + if (foundLang) { |
| 80 | + detectedLanguages.push(foundLang); |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | +
|
| 85 | + return detectedLanguages; |
| 86 | +}; |
| 87 | +
|
| 88 | +// Core formatting logic |
| 89 | +const formatStackTrace = (stackTrace: string, selectedLang: Language): Element[] => { |
| 90 | + const lines = stackTrace.split("\n"); |
| 91 | + const fileAndLineNumberRegEx = new RegExp(`${selectedLang.in} (.+):${selectedLang.line} (\\d+)`); |
| 92 | + const atRegex = new RegExp(`(\\s*)(${selectedLang.at}) (.+?)\\((.*?)\\)`); |
| 93 | +
|
| 94 | + return lines.map((line) => { |
| 95 | + const match = line.match(atRegex); |
| 96 | + if (match) { |
| 97 | + const [, spaces, , methodWithType, paramsWithFile] = match; |
| 98 | +
|
| 99 | + const [type, method] = (() => { |
| 100 | + const parts = methodWithType.split("."); |
| 101 | + const method = parts.pop() ?? ""; |
| 102 | + const type = parts.join("."); |
| 103 | + return [type, method]; |
| 104 | + })(); |
| 105 | +
|
| 106 | + const params = paramsWithFile.split(", ").map((param) => { |
| 107 | + const [paramType, paramName] = param.split(" "); |
| 108 | + return { name: paramName, type: paramType }; |
| 109 | + }); |
| 110 | +
|
| 111 | + const matchFile = line.match(fileAndLineNumberRegEx); |
| 112 | + let file, lineNumber; |
| 113 | + if (matchFile) { |
| 114 | + [, file, lineNumber] = matchFile; |
| 115 | + } |
| 116 | +
|
| 117 | + return <Node>{ method, type, params, file, lineNumber, spaces }; |
| 118 | + } else { |
| 119 | + return line; |
| 120 | + } |
| 121 | + }); |
| 122 | +}; |
| 123 | +
|
| 124 | +// Process the provided stack trace |
| 125 | +const processStackTrace = (): void => { |
| 126 | + const rawContent = props.stackTrace; |
| 127 | + const detectedLanguages = detectLanguagesInOrder(rawContent); |
| 128 | +
|
| 129 | + if (!detectedLanguages.length) { |
| 130 | + formattedStack.value = [rawContent]; // If no language detected, output raw content |
| 131 | + return; |
| 132 | + } |
| 133 | +
|
| 134 | + selectedLanguage.value = detectedLanguages[0]; // Use the first detected language |
| 135 | + formattedStack.value = formatStackTrace(rawContent, selectedLanguage.value); |
| 136 | +}; |
| 137 | +
|
| 138 | +watch( |
| 139 | + () => props.stackTrace, |
| 140 | + () => { |
| 141 | + processStackTrace(); |
| 142 | + }, |
| 143 | + { immediate: true } |
| 144 | +); |
| 145 | +</script> |
| 146 | + |
| 147 | +<template> |
| 148 | + <div class="stack-trace-container"> |
| 149 | + <template v-for="line in formattedStack" :key="line"> |
| 150 | + <template v-if="typeof line === 'string'"> |
| 151 | + <span>{{ line }}</span> |
| 152 | + </template> |
| 153 | + <div v-else> |
| 154 | + {{ line.spaces }}{{ selectedLanguage.at }} |
| 155 | + <span :class="props.options.frame"> |
| 156 | + <span :class="props.options.type">{{ line.type }}</span |
| 157 | + >.<span :class="props.options.method">{{ line.method }}</span |
| 158 | + >(<span :class="props.options.paramsList"> |
| 159 | + <template v-for="(param, index) in line.params" :key="param.name"> |
| 160 | + <span :class="props.options.paramType"> {{ param.type }}</span> <span :class="props.options.paramName">{{ param.name }}</span> |
| 161 | + <span v-if="index !== line.params.length - 1">, </span> |
| 162 | + </template> </span |
| 163 | + >) |
| 164 | + </span> |
| 165 | + <template v-if="line.file"> |
| 166 | + {{ selectedLanguage.in }} <span :class="props.options.file">{{ line.file }}</span |
| 167 | + >:{{ selectedLanguage.line }} <span :class="props.options.line">{{ line.lineNumber }}</span> |
| 168 | + </template> |
| 169 | + </div> |
| 170 | + </template> |
| 171 | + </div> |
| 172 | +</template> |
| 173 | + |
| 174 | +<style scoped> |
| 175 | +.stack-trace-container { |
| 176 | + font-family: monospace; |
| 177 | + white-space: pre-wrap; |
| 178 | +} |
| 179 | +
|
| 180 | +.st-frame { |
| 181 | + color: #00729c; |
| 182 | +} |
| 183 | +
|
| 184 | +.st-type { |
| 185 | + color: #a11; |
| 186 | +} |
| 187 | +
|
| 188 | +.st-method { |
| 189 | + color: #164; |
| 190 | +} |
| 191 | +
|
| 192 | +.st-file { |
| 193 | + color: #c67b3d; |
| 194 | +} |
| 195 | +
|
| 196 | +.st-line { |
| 197 | + color: #6c757d; |
| 198 | +} |
| 199 | +
|
| 200 | +.st-param-type { |
| 201 | + font-style: italic; |
| 202 | + color: #6b82ce; |
| 203 | +} |
| 204 | +
|
| 205 | +.st-param-name { |
| 206 | + font-weight: bold; |
| 207 | + color: #343a40; |
| 208 | +} |
| 209 | +
|
| 210 | +.st-frame-params { |
| 211 | + color: #495057; |
| 212 | +} |
| 213 | +</style> |
0 commit comments