autosnippet 1.3.4 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -95,7 +95,7 @@ asd ui
95
95
 
96
96
  ## 术语
97
97
 
98
- - **Snippet**:写入 Xcode 的代码片段,通过 trigger(补全键)或库面板使用。
98
+ - **Snippet**:写入 Xcode 的代码片段,通过 trigger(补全键)或库面板使用;trigger 以配置的触发符开头(默认 **@**,可通过环境变量 `ASD_TRIGGER_SYMBOL` 更换)。
99
99
  - **Recipe(配方)**:存放在 `Knowledge/recipes/` 下的 Markdown 知识,供 AI 检索、Guard 审查与搜索。
100
100
  - **项目根**:含 `AutoSnippetRoot.boxspec.json` 的目录。
101
101
 
package/bin/create.js CHANGED
@@ -22,6 +22,7 @@ const fs = require('fs');
22
22
  const path = require('path');
23
23
  const readline = require('readline');
24
24
  const cache = require('../lib/infra/cacheStore.js');
25
+ const triggerSymbol = require('../lib/infra/triggerSymbol.js');
25
26
  const findPath = require('./findPath.js');
26
27
  const specRepository = require('../lib/snippet/specRepository.js');
27
28
  const snippetFactory = require('../lib/snippet/snippetFactory.js');
@@ -92,8 +93,8 @@ function updateCodeSnippets(specFile, word, key, value) {
92
93
  let placeItem = placeholder.list[index];
93
94
 
94
95
  const t = placeItem && placeItem.trigger ? String(placeItem.trigger) : '';
95
- const raw = t.startsWith('@') ? t.slice(1) : t;
96
- if (raw === word || t === word || t === ('@' + word)) {
96
+ const raw = triggerSymbol.stripTriggerPrefix(t);
97
+ if (raw === word || t === word || t === (triggerSymbol.TRIGGER_SYMBOL + word)) {
97
98
  snippet = placeItem;
98
99
  if (key) {
99
100
  snippet[key] = value;
@@ -174,8 +175,8 @@ function createCodeSnippets(specFile, answers, updateSnippet, selectedFilePath)
174
175
  snippet = {
175
176
  identifier: identifier,
176
177
  title: answers.title,
177
- trigger: '#' + answers.completion_first,
178
- completion: '#' + answers.completion_first + completionMoreStr,
178
+ trigger: triggerSymbol.TRIGGER_SYMBOL + answers.completion_first,
179
+ completion: triggerSymbol.TRIGGER_SYMBOL + answers.completion_first + completionMoreStr,
179
180
  summary: answers.summary,
180
181
  languageShort: 'objc',
181
182
  };
@@ -418,11 +419,11 @@ async function createFromExtracted(projectRoot, rootSpecPath, extracted) {
418
419
  console.error('❌ AI 结果不完整:需要 title / code');
419
420
  return;
420
421
  }
421
- const trigger = (extracted.trigger || '').trim() || ('#' + extracted.title.replace(/\s+/g, ''));
422
- const prefix = trigger.startsWith('@') || trigger.startsWith('#') ? '' : '#';
423
- const normalizedTrigger = trigger.startsWith('@') || trigger.startsWith('#') ? trigger : prefix + trigger;
422
+ const trigger = (extracted.trigger || '').trim() || (triggerSymbol.TRIGGER_SYMBOL + extracted.title.replace(/\s+/g, ''));
423
+ const prefix = triggerSymbol.hasTriggerPrefix(trigger) ? '' : triggerSymbol.TRIGGER_SYMBOL;
424
+ const normalizedTrigger = triggerSymbol.hasTriggerPrefix(trigger) ? trigger : prefix + trigger;
424
425
  const category = extracted.category || 'Utility';
425
- const categoryPart = category ? (normalizedTrigger.startsWith('@') ? '@' : '#') + category : '';
426
+ const categoryPart = category ? triggerSymbol.getPrefixFromTrigger(normalizedTrigger) + category : '';
426
427
  const isSwift = (extracted.language || '').toLowerCase() === 'swift';
427
428
  const codeLines = extracted.code.split('\n').map(s => s.replace(/\r$/, ''));
428
429
  let body = codeLines;
package/bin/share.js CHANGED
@@ -22,6 +22,7 @@ const inquirer = require('inquirer');
22
22
 
23
23
  const specRepository = require('../lib/snippet/specRepository.js');
24
24
  const cache = require('../lib/infra/cacheStore.js');
25
+ const triggerSymbol = require('../lib/infra/triggerSymbol.js');
25
26
  const config = require('../lib/infra/paths.js');
26
27
 
27
28
  function shareCodeSnippets(specFile) {
@@ -246,7 +247,7 @@ function shareTheSnippet(specFile, filePath, answers) {
246
247
  array[0] = completion_first;
247
248
  createFromLocal(specFile, filedir, array, answers.completion_more);
248
249
  });
249
- } else if (array[0].startsWith('#') || array[0].endsWith('@Moudle')) {
250
+ } else if (triggerSymbol.hasTriggerPrefix(array[0]) || array[0].endsWith('@Moudle')) {
250
251
  console.log('这个文件已经是共享版本,不需要再处理。');
251
252
  } else {
252
253
  createFromLocal(specFile, filedir, array, answers.completion_more);
@@ -262,7 +263,7 @@ function shareTheSnippet(specFile, filePath, answers) {
262
263
  function createFromLocal(specFile, filedir, array, completion_more) {
263
264
  const xcodeLang = array[3];
264
265
  const languageShort = xcodeLang === 'Xcode.SourceCodeLanguage.Swift' ? 'swift' : 'objc';
265
- const prefix = '#';
266
+ const prefix = triggerSymbol.TRIGGER_SYMBOL;
266
267
  const snippet = {
267
268
  identifier: 'AutoSnip_' + array[2],
268
269
  title: array[5],
package/bin/ui.js CHANGED
@@ -14,6 +14,7 @@ const targetScanner = require('../lib/spm/targetScanner');
14
14
  const candidateService = require('../lib/ai/candidateService');
15
15
  const headerResolution = require('../lib/ai/headerResolution');
16
16
  const markerLine = require('../lib/snippet/markerLine');
17
+ const triggerSymbol = require('../lib/infra/triggerSymbol');
17
18
 
18
19
  /**
19
20
  * 检测当前进程是否已有控制 Chromium 系浏览器的权限(与 openChrome.applescript 所需一致)
@@ -177,7 +178,7 @@ function launch(projectRoot, port = 3000, options = {}) {
177
178
  const searchMark = /\/\/\s*(?:autosnippet|as):search(\s|$)/;
178
179
  let found = -1;
179
180
  for (let i = 0; i < lines.length; i++) {
180
- const t = lines[i].replace(/^[\s@#]+/, '').trim();
181
+ const t = triggerSymbol.stripTriggerPrefix(lines[i].trim()).trim();
181
182
  if (searchMark.test(t) || t === '// as:search' || t.startsWith('// as:search ') || t.startsWith('// autosnippet:search')) {
182
183
  found = i;
183
184
  break;
@@ -608,12 +609,11 @@ function launch(projectRoot, port = 3000, options = {}) {
608
609
  const { snippet } = req.body;
609
610
  const rootSpecPath = path.join(projectRoot, 'AutoSnippetRoot.boxspec.json');
610
611
 
611
- // ✅ 映射 Dashboard Snippet 格式到内部 specRepository 格式
612
- const triggerBase = snippet.trigger || snippet.completionKey;
613
- // 支持 # 作为新触发标识,但也兼容旧的 @
614
- const triggerPrefix = triggerBase.startsWith('@') ? '@' : '#';
615
- const normalizedTrigger = triggerBase.startsWith(triggerPrefix) ? triggerBase : triggerPrefix + triggerBase;
616
- const categoryPart = snippet.category ? `${triggerPrefix}${snippet.category}` : '';
612
+ // ✅ 映射 Dashboard Snippet 格式到内部 specRepository 格式(Trigger 输入框绑定的是 completionKey,保存时优先用其值以同步用户编辑)
613
+ const triggerBase = snippet.completionKey ?? snippet.trigger ?? '';
614
+ const sym = triggerSymbol.TRIGGER_SYMBOL;
615
+ const normalizedTrigger = triggerSymbol.ensureTriggerPrefix(triggerBase);
616
+ const categoryPart = snippet.category ? `${sym}${snippet.category}` : '';
617
617
 
618
618
  // 处理 body:确保是数组;若前端误传了已转义内容则先还原,再清理触发符,最后只转义一次写入
619
619
  const rawBody = snippet.body || snippet.content || [];
@@ -624,10 +624,11 @@ function launch(projectRoot, port = 3000, options = {}) {
624
624
  if (firstLine === normalizedTrigger || firstLine === triggerBase || firstLine === normalizedTrigger.slice(1)) {
625
625
  cleanedBody.shift();
626
626
  }
627
- while (cleanedBody.length && String(cleanedBody[0]).trim() === '#') cleanedBody.shift();
627
+ const symEsc = sym.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
628
+ while (cleanedBody.length && new RegExp('^' + symEsc + '$').test(String(cleanedBody[0]).trim())) cleanedBody.shift();
628
629
  if (cleanedBody.length) {
629
630
  firstLine = String(cleanedBody[0]).trim();
630
- if (/^#\s*\/\/\s*as:(include|import)\s+/.test(firstLine)) cleanedBody[0] = firstLine.replace(/^#\s*/, '');
631
+ if (new RegExp('^' + symEsc + '\\s*\\/\\/\\s*as:(include|import)\\s+').test(firstLine)) cleanedBody[0] = firstLine.replace(new RegExp('^' + symEsc + '\\s*'), '');
631
632
  }
632
633
  }
633
634
 
@@ -88,7 +88,7 @@ class ClaudeProvider extends AiProvider {
88
88
  - title_cn: Concise name (Chinese).
89
89
  - summary_cn: Description (Chinese).
90
90
  - summary_en: Description (English).
91
- - trigger: Short shortcut (starts with #).
91
+ - trigger: Short shortcut (starts with @).
92
92
  - category: One of the above.
93
93
  - language: "swift" or "objectivec".
94
94
  - tags: Array of tags.
@@ -128,7 +128,7 @@ class ClaudeProvider extends AiProvider {
128
128
  - title: Concise name (English).
129
129
  - summary_cn: Description in Chinese.
130
130
  - summary_en: Description in English.
131
- - trigger: Shortcut (starts with #).
131
+ - trigger: Shortcut (starts with @).
132
132
  - category: [View, Service, Tool, Model, Network, Storage, UI, Utility].
133
133
  - language: "swift" or "objectivec".
134
134
  - code: Reusable usage example (with placeholders).
@@ -93,7 +93,7 @@ class GoogleGeminiProvider extends AiProvider {
93
93
  - title_cn: Concise name (Chinese).
94
94
  - summary_cn: Description (Chinese).
95
95
  - summary_en: Description (English).
96
- - trigger: Short shortcut (starts with #).
96
+ - trigger: Short shortcut (starts with @).
97
97
  - category: One of the above.
98
98
  - language: "swift" or "objectivec".
99
99
  - tags: Array of tags.
@@ -139,7 +139,7 @@ class GoogleGeminiProvider extends AiProvider {
139
139
  - title: Concise name (English).
140
140
  - summary_cn: Description in Chinese.
141
141
  - summary_en: Description in English.
142
- - trigger: Shortcut (starts with #).
142
+ - trigger: Shortcut (starts with @).
143
143
  - category: [View, Service, Tool, Model, Network, Storage, UI, Utility].
144
144
  - language: "swift" or "objectivec".
145
145
  - code: Reusable usage example (with placeholders).
@@ -21,7 +21,7 @@ class MockProvider extends AiProvider {
21
21
  title_cn: '模拟代码片段标题',
22
22
  summary_cn: '这是一个模拟的代码片段摘要。',
23
23
  summary_en: 'This is a mock code snippet summary.',
24
- trigger: '#mock',
24
+ trigger: '@mock',
25
25
  category: 'Utility',
26
26
  language: language,
27
27
  tags: ['mock', 'test'],
@@ -36,7 +36,7 @@ class MockProvider extends AiProvider {
36
36
  title: 'Mock Extracted Recipe',
37
37
  summary_cn: '从文件中提取的模拟配方',
38
38
  summary_en: 'Mock recipe extracted from files',
39
- trigger: '#mock_skill',
39
+ trigger: '@mock_skill',
40
40
  category: 'Tool',
41
41
  language: 'swift',
42
42
  code: '// Mock code',
@@ -104,7 +104,7 @@ class OpenAiProvider extends AiProvider {
104
104
  - title_cn: Concise name (Chinese).
105
105
  - summary_cn: Description (Chinese).
106
106
  - summary_en: Description (English).
107
- - trigger: Short shortcut (starts with #).
107
+ - trigger: Short shortcut (starts with @).
108
108
  - category: One of the above.
109
109
  - language: "swift" or "objectivec".
110
110
  - tags: Array of tags.
@@ -144,7 +144,7 @@ class OpenAiProvider extends AiProvider {
144
144
  - title: Concise name (English).
145
145
  - summary_cn: Description in Chinese.
146
146
  - summary_en: Description in English.
147
- - trigger: Shortcut (starts with #).
147
+ - trigger: Shortcut (starts with @).
148
148
  - category: [View, Service, Tool, Model, Network, Storage, UI, Utility].
149
149
  - language: "swift" or "objectivec".
150
150
  - code: Reusable usage example (with placeholders).
@@ -11,6 +11,7 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
+ const triggerSymbol = require('./triggerSymbol.js');
14
15
  const paths = require('./paths.js');
15
16
 
16
17
  const SpecCache = 'SpecCache_';
@@ -37,7 +38,7 @@ async function updateCache(specFile, content) {
37
38
 
38
39
  cache.list.forEach(element => {
39
40
  const trigger = element && element.trigger ? String(element.trigger) : '';
40
- const rawKey = trigger.startsWith('@') ? trigger.slice(1) : trigger;
41
+ const rawKey = triggerSymbol.stripTriggerPrefix(trigger);
41
42
  if (element && rawKey) {
42
43
  let key = rawKey;
43
44
  keysCache.list.push(key);
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Snippet 触发符配置:支持灵活更换默认符号(如 @)。
5
+ * - 默认使用 @,可通过环境变量 ASD_TRIGGER_SYMBOL 覆盖(单字符)。
6
+ * - 仅支持配置的触发符,统一由此模块提供,不做其它符号的差别处理。
7
+ */
8
+
9
+ const DEFAULT_SYMBOL = '@';
10
+
11
+ function fromEnv() {
12
+ const raw = process.env.ASD_TRIGGER_SYMBOL;
13
+ if (raw != null && String(raw).length === 1) return String(raw);
14
+ return DEFAULT_SYMBOL;
15
+ }
16
+
17
+ /** 当前触发符(可配置) */
18
+ const TRIGGER_SYMBOL = fromEnv();
19
+
20
+ /** 用于拆分的触发符集合(仅当前配置的符号) */
21
+ const TRIGGER_SYMBOLS = [TRIGGER_SYMBOL];
22
+
23
+ /** 用于按触发符拆分的正则(如 completion 拆 category) */
24
+ const TRIGGER_SPLIT_REGEX = new RegExp('[' + TRIGGER_SYMBOLS.map(escapeRegExp).join('') + ']');
25
+
26
+ function escapeRegExp(s) {
27
+ return s.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
28
+ }
29
+
30
+ /** str 是否以任一触发符开头 */
31
+ function hasTriggerPrefix(str) {
32
+ if (!str || typeof str !== 'string') return false;
33
+ const s = String(str).trim();
34
+ return TRIGGER_SYMBOLS.some((sym) => s.startsWith(sym));
35
+ }
36
+
37
+ /** 去掉 str 开头的连续触发符 */
38
+ function stripTriggerPrefix(str) {
39
+ if (!str || typeof str !== 'string') return String(str);
40
+ let s = String(str).trim();
41
+ while (s.length && TRIGGER_SYMBOLS.some((sym) => s.startsWith(sym))) {
42
+ s = s.slice(1).trimStart();
43
+ }
44
+ return s;
45
+ }
46
+
47
+ /** 若 str 不以任一触发符开头,则加上默认触发符 */
48
+ function ensureTriggerPrefix(str) {
49
+ if (!str || typeof str !== 'string') return str;
50
+ const s = String(str).trim();
51
+ if (!s) return s;
52
+ return hasTriggerPrefix(s) ? s : TRIGGER_SYMBOL + s;
53
+ }
54
+
55
+ /** 若 str 已带触发符则返回该符号,否则返回默认触发符(用于 category 等) */
56
+ function getPrefixFromTrigger(str) {
57
+ if (!str || typeof str !== 'string') return TRIGGER_SYMBOL;
58
+ const s = String(str).trim();
59
+ for (const sym of TRIGGER_SYMBOLS) {
60
+ if (s.startsWith(sym)) return sym;
61
+ }
62
+ return TRIGGER_SYMBOL;
63
+ }
64
+
65
+ module.exports = {
66
+ TRIGGER_SYMBOL,
67
+ TRIGGER_SYMBOLS,
68
+ TRIGGER_SPLIT_REGEX,
69
+ hasTriggerPrefix,
70
+ stripTriggerPrefix,
71
+ ensureTriggerPrefix,
72
+ getPrefixFromTrigger,
73
+ };
@@ -7,15 +7,16 @@
7
7
  * - 提供“是否为指令行”的判断(用于移除标记行)
8
8
  */
9
9
 
10
+ const triggerSymbol = require('../infra/triggerSymbol.js');
10
11
  const HEADER_MARK_INCLUDE = '// autosnippet:include ';
11
12
  const HEADER_MARK_IMPORT = '// autosnippet:import ';
12
13
  const HEADER_MARK_INCLUDE_SHORT = '// as:include ';
13
14
  const HEADER_MARK_IMPORT_SHORT = '// as:import ';
14
15
 
15
16
  function normalizeDirectiveLine(line) {
16
- // 兼容前导 @ 或 #(Xcode 插入 snippet 时可能带触发符前缀),去掉后便于匹配 as:include / as:import
17
+ // 去掉前导触发符,便于匹配 as:include / as:import
17
18
  const s = String(line || '').trim();
18
- return s.replace(/^@+/, '').replace(/^#\s*/, '').trimStart();
19
+ return triggerSymbol.stripTriggerPrefix(s);
19
20
  }
20
21
 
21
22
  function parseDirectiveLine(line) {
@@ -23,6 +23,8 @@ function buildIdentifier(title, completionFirst, completionMoreStr) {
23
23
  return 'AutoSnip_' + answersIdBuff.toString('base64').replace(/\//g, '');
24
24
  }
25
25
 
26
+ const triggerSymbol = require('../infra/triggerSymbol.js');
27
+
26
28
  function fromText(answers, text, options = {}) {
27
29
  if (!text || !String(text).trim()) return null;
28
30
 
@@ -33,7 +35,7 @@ function fromText(answers, text, options = {}) {
33
35
  // 如果有 explicit category 且不在 completionMoreStr 中,则添加
34
36
  let categoryPart = '';
35
37
  if (answers.category) {
36
- const cat = answers.category.startsWith('@') ? answers.category : '@' + answers.category;
38
+ const cat = triggerSymbol.hasTriggerPrefix(answers.category) ? answers.category : triggerSymbol.TRIGGER_SYMBOL + answers.category;
37
39
  if (!completionMoreStr.includes(cat)) {
38
40
  categoryPart = cat;
39
41
  }
@@ -42,8 +44,7 @@ function fromText(answers, text, options = {}) {
42
44
  const identifier = buildIdentifier(answers.title, answers.completion_first, completionMoreStr + categoryPart);
43
45
  const isSwift = options && options.language === 'swift';
44
46
 
45
- // 默认使用 # 替代 @,更像是一个 Tag/知识标识
46
- const prefix = '#';
47
+ const prefix = triggerSymbol.TRIGGER_SYMBOL;
47
48
  const snippet = {
48
49
  identifier: identifier,
49
50
  title: answers.title,
@@ -12,6 +12,7 @@
12
12
 
13
13
  const fs = require('fs');
14
14
  const path = require('path');
15
+ const triggerSymbol = require('../infra/triggerSymbol.js');
15
16
  const cacheStore = require('../infra/cacheStore.js');
16
17
  const paths = require('../infra/paths.js');
17
18
  const findPath = require('../../bin/findPath.js'); // Phase 1 先复用现有实现,后续再迁移
@@ -43,14 +44,11 @@ function applySpecDefaults(specObj, specFile) {
43
44
  }
44
45
 
45
46
  function parseCategoriesFromCompletion(completion) {
46
- // completion 形如:#key#View#Tool 或 @key@View@Tool@Moudle
47
+ // completion 形如:@key@View@Tool(按配置的触发符拆分)
47
48
  const s = String(completion || '');
48
- if (!s.includes('@') && !s.includes('#')) return [];
49
-
50
- // 同时支持 @ # 拆分
51
- const parts = s.split(/[@#]/).map(p => p.trim()).filter(Boolean);
52
-
53
- // 去掉最后的 Moudle 标记(兼容旧版本)
49
+ if (!s.includes(triggerSymbol.TRIGGER_SYMBOL)) return [];
50
+
51
+ const parts = s.split(triggerSymbol.TRIGGER_SPLIT_REGEX).map(p => p.trim()).filter(Boolean);
54
52
  return parts.filter(p => p !== 'Moudle');
55
53
  }
56
54
 
@@ -64,7 +62,7 @@ function normalizeTrigger(raw) {
64
62
 
65
63
  function rawKeyFromTrigger(trigger) {
66
64
  const t = normalizeTrigger(trigger);
67
- return (t.startsWith('@') || t.startsWith('#')) ? t.slice(1) : t;
65
+ return triggerSymbol.stripTriggerPrefix(t);
68
66
  }
69
67
 
70
68
  /** 去除 title 中多余的 category 前缀(如 [Service]),避免与 category 字段重复 */
@@ -170,7 +168,7 @@ async function augmentSnippetForAi(snippet, specFile) {
170
168
  // ✅ skill 视角(不改变运行时:仅增强语义)
171
169
  const categories = parseCategoriesFromCompletion(completion);
172
170
  const tags = categories
173
- .map((c) => c.startsWith('@') ? c.slice(1) : c)
171
+ .map((c) => triggerSymbol.stripTriggerPrefix(c))
174
172
  .filter((c) => c && c !== rawKeyFromTrigger(snippet.trigger));
175
173
 
176
174
  snippet.skill = snippet.skill && typeof snippet.skill === 'object' ? snippet.skill : {};
@@ -11,6 +11,7 @@ const path = require('path');
11
11
  const open = require('open');
12
12
  const injection = require('../injection/injectionService.js');
13
13
  const cache = require('../infra/cacheStore.js');
14
+ const triggerSymbol = require('../infra/triggerSymbol.js');
14
15
 
15
16
  const CMD_PATH = process.cwd();
16
17
 
@@ -23,8 +24,8 @@ const guardMarkShort = '// as:guard';
23
24
  const searchMarkShort = '// as:search';
24
25
  const searchMarkLong = '// autosnippet:search';
25
26
  const alinkMark = 'alink';
26
- const wellMark = '#';
27
- const atMark = '@';
27
+ const wellMark = triggerSymbol.TRIGGER_SYMBOL;
28
+ const atMark = triggerSymbol.TRIGGER_SYMBOL;
28
29
 
29
30
  // ObjC 头文件名常见包含 `+`(Category)、`-`、`.` 等字符
30
31
  const headerReg = /^@?\/\/\s*(?:autosnippet|as):include\s+<([A-Za-z0-9_]+)\/([A-Za-z0-9_+.-]+\.h)>(\s+.+)?$/;
@@ -181,8 +182,7 @@ function processFileChange(specFile, updateFile, relativePath, options) {
181
182
  const lineArray = data.split('\n');
182
183
  lineArray.forEach(element => {
183
184
  const lineVal = element.trim();
184
- let normalizedLineVal = lineVal.startsWith(atMark) ? lineVal.slice(1).trimStart() : lineVal;
185
- if (normalizedLineVal.startsWith('#')) normalizedLineVal = normalizedLineVal.slice(1).trimStart();
185
+ let normalizedLineVal = triggerSymbol.stripTriggerPrefix(lineVal);
186
186
  if (currImportReg.test(lineVal)) {
187
187
  importArray.push(lineVal);
188
188
  }
@@ -281,14 +281,18 @@ function checkAnotherFile(specFile, updateFile, headerLine, importArray, isSwift
281
281
  }
282
282
 
283
283
  function openLink(specFile, inputWord) {
284
- if (inputWord.includes(wellMark)) {
285
- const wellKey = inputWord.split(wellMark);
286
-
287
- if (wellKey.length > 1 && wellKey[1] === alinkMark) {
288
- cache.getLinkCache(specFile).then(function (linkCache) {
289
- if (linkCache) {
290
- const completionKey = wellKey[0].replace(atMark, '');
291
- let link = decodeURI(linkCache[completionKey]);
284
+ const sym = triggerSymbol.TRIGGER_SYMBOL;
285
+ let completionKey = null;
286
+ if (inputWord.includes(sym)) {
287
+ const parts = inputWord.split(sym).map(p => p.trim()).filter(Boolean);
288
+ if (parts.length >= 2 && parts[parts.length - 1] === alinkMark) {
289
+ completionKey = parts[parts.length - 2];
290
+ }
291
+ }
292
+ if (completionKey != null) {
293
+ cache.getLinkCache(specFile).then(function (linkCache) {
294
+ if (linkCache) {
295
+ let link = decodeURI(linkCache[completionKey]);
292
296
 
293
297
  if (!link.startsWith('http')) {
294
298
  const specSlashIndex = specFile.lastIndexOf('/');
@@ -301,7 +305,6 @@ function openLink(specFile, inputWord) {
301
305
  }
302
306
  }
303
307
  });
304
- }
305
308
  }
306
309
  }
307
310
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "A iOS module management tool.",
5
5
  "main": "index.js",
6
6
  "scripts": {