@lvce-editor/shared-process 0.0.40 → 0.1.1

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.
Files changed (29) hide show
  1. package/config/defaultKeyBindings.json +5 -0
  2. package/extensions/builtin.language-basics-docker/extension.json +13 -3
  3. package/extensions/builtin.language-basics-json/extension.json +2 -1
  4. package/extensions/builtin.language-basics-json/src/tokenizeJson.js +6 -2
  5. package/extensions/builtin.language-basics-markdown/src/tokenizeMarkdown.js +288 -11
  6. package/extensions/builtin.language-basics-perl/README.md +14 -0
  7. package/extensions/builtin.language-basics-perl/extension.json +12 -0
  8. package/extensions/builtin.language-basics-perl/src/tokenizePerl.js +31 -0
  9. package/extensions/builtin.language-basics-toml/extension.json +10 -2
  10. package/extensions/builtin.theme-slime/color-theme.json +4 -0
  11. package/package.json +6 -6
  12. package/src/parts/Command/Command.js +5 -18
  13. package/src/parts/DirentType/DirentType.js +13 -0
  14. package/src/parts/Download/Download.js +6 -8
  15. package/src/parts/ExtensionManagement/ExtensionManagement.js +16 -41
  16. package/src/parts/ExtensionManagement/ExtensionManagementColorTheme.js +4 -6
  17. package/src/parts/ExtensionManagement/ExtensionManagementIconTheme.js +4 -6
  18. package/src/parts/ExtensionManifestStatus/ExtensionManifestStatus.js +3 -0
  19. package/src/parts/FileSystem/FileSystem.js +33 -74
  20. package/src/parts/Json/Json.js +7 -55
  21. package/src/parts/JsonError/JsonError.js +70 -0
  22. package/src/parts/JsonFile/JsonFile.js +5 -5
  23. package/src/parts/Native/Native.js +2 -6
  24. package/src/parts/Preferences/Preferences.js +1 -1
  25. package/src/parts/RecentlyOpened/RecentlyOpened.ipc.js +5 -0
  26. package/src/parts/RecentlyOpened/RecentlyOpened.js +52 -0
  27. package/src/parts/Search/Search.js +9 -3
  28. package/src/parts/Electron/Electron.ipc.js +0 -18
  29. package/src/parts/Electron/Electron.js +0 -110
@@ -1,14 +1,14 @@
1
+ import isObject from 'is-object'
1
2
  import { mkdir, readdir, rename, rm } from 'node:fs/promises'
2
- import { performance } from 'node:perf_hooks'
3
3
  import path from 'node:path'
4
- import isObject from 'is-object'
4
+ import { performance } from 'node:perf_hooks'
5
5
  import VError from 'verror'
6
6
  import * as Debug from '../Debug/Debug.js'
7
- import * as Error from '../Error/Error.js'
8
7
  import * as ReadJson from '../JsonFile/JsonFile.js'
9
8
  import * as Path from '../Path/Path.js'
10
9
  import * as Platform from '../Platform/Platform.js'
11
10
  import * as Queue from '../Queue/Queue.js'
11
+ import * as ExtensionManifestStatus from '../ExtensionManifestStatus/ExtensionManifestStatus.js'
12
12
 
13
13
  const isLanguageBasics = (extension) => {
14
14
  if (extension && extension.id) {
@@ -61,11 +61,7 @@ export const install = async (id) => {
61
61
  // type: 'marketplace',
62
62
  // })
63
63
  } catch (error) {
64
- throw new Error.OperationalError({
65
- cause: error,
66
- code: 'E_EXT_INSTALL_FAILED',
67
- message: `Failed to install extension "${id}"`,
68
- })
64
+ throw new VError(error, `Failed to install extension "${id}"`)
69
65
  }
70
66
  }
71
67
 
@@ -78,11 +74,7 @@ export const uninstall = async (id) => {
78
74
  // if (error.code === 'ENOENT') {
79
75
  // return
80
76
  // }
81
- throw new Error.OperationalError({
82
- cause: error,
83
- code: 'E_EXT_UNINSTALL_FAILED',
84
- message: `Failed to uninstall extension "${id}"`,
85
- })
77
+ throw new VError(error, `Failed to uninstall extension "${id}"`)
86
78
  }
87
79
  }
88
80
 
@@ -97,11 +89,7 @@ export const enable = async (id) => {
97
89
  Path.join(extensionsPath, id)
98
90
  )
99
91
  } catch (error) {
100
- throw new Error.OperationalError({
101
- cause: error,
102
- message: `Failed to enable extension "${id}"`,
103
- code: 'E_EXTENSION_ENABLING_FAILED',
104
- })
92
+ throw new VError(error, `Failed to enable extension "${id}"`)
105
93
  }
106
94
  }
107
95
 
@@ -116,11 +104,7 @@ export const disable = async (id) => {
116
104
  Path.join(disabledExtensionsPath, id)
117
105
  )
118
106
  } catch (error) {
119
- throw new Error.OperationalError({
120
- cause: error,
121
- code: 'E_EXTENSION_DISABLING_FAILED',
122
- message: `Failed to disable extension ${id}`,
123
- })
107
+ throw new VError(error, `Failed to disable extension ${id}`)
124
108
  }
125
109
  }
126
110
 
@@ -160,6 +144,8 @@ const isIncludedBuiltinExtension = (dirent) => {
160
144
  return !SKIPPED.includes(dirent)
161
145
  }
162
146
 
147
+ // TODO pass builtin extensions path from renderer-worker
148
+ // only negative: more messages sent between renderer-worker and shared process on startup
163
149
  const getBuiltinExtensionPaths = async () => {
164
150
  try {
165
151
  const builtinExtensionsPath = Platform.getBuiltinExtensionsPath()
@@ -192,11 +178,7 @@ const getInstalledExtensionPaths = async () => {
192
178
  await mkdir(Platform.getExtensionsPath(), { recursive: true })
193
179
  return []
194
180
  }
195
- throw new Error.OperationalError({
196
- cause: error,
197
- code: 'E_FAILED_TO_LOAD_INSTALLED_EXTENSIONS',
198
- message: 'Failed to get installed extensions',
199
- })
181
+ throw new VError(error, 'Failed to get installed extensions')
200
182
  }
201
183
  }
202
184
 
@@ -222,28 +204,21 @@ const getExtensionManifest = async (path) => {
222
204
  return {
223
205
  ...json,
224
206
  path,
225
- status: 'fulfilled',
207
+ status: ExtensionManifestStatus.Resolved,
226
208
  }
227
209
  } catch (error) {
228
210
  const id = inferExtensionId(path)
229
211
  return {
230
212
  path,
231
- status: 'rejected',
232
- reason: new Error.OperationalError({
233
- cause: error,
234
- code: 'E_LOADING_EXTENSION_MANIFEST_FAilED',
235
- message: `Failed to load extension "${id}": Failed to load extension manifest`,
236
- // @ts-ignore
237
- stack: error.stack,
238
- }),
213
+ status: ExtensionManifestStatus.Rejected,
214
+ reason: new VError(
215
+ error,
216
+ `Failed to load extension "${id}": Failed to load extension manifest`
217
+ ),
239
218
  }
240
219
  }
241
220
  }
242
221
 
243
- const isRejected = (promise) => {
244
- return promise.status === 'rejected'
245
- }
246
-
247
222
  /**
248
223
  * @param {string[]} paths
249
224
  */
@@ -1,5 +1,7 @@
1
+ import VError from 'verror'
1
2
  import * as Error from '../Error/Error.js'
2
3
  import * as ReadJson from '../JsonFile/JsonFile.js'
4
+ import * as Path from '../Path/Path.js'
3
5
  import * as ExtensionManagement from './ExtensionManagement.js'
4
6
 
5
7
  // TODO test this function
@@ -14,16 +16,12 @@ export const getColorThemeJson = async (colorThemeId) => {
14
16
  if (colorTheme.id !== colorThemeId) {
15
17
  continue
16
18
  }
17
- const absolutePath = `${extension.path}/${colorTheme.path}`
19
+ const absolutePath = Path.join(extension.path, colorTheme.path)
18
20
  try {
19
21
  const json = await ReadJson.readJson(absolutePath)
20
22
  return json
21
23
  } catch (error) {
22
- throw new Error.OperationalError({
23
- cause: error,
24
- code: 'E_COLOR_THEME_COULD_NOT_BE_LOADED',
25
- message: `Failed to load color theme "${colorThemeId}"`,
26
- })
24
+ throw new VError(error, `Failed to load color theme "${colorThemeId}"`)
27
25
  }
28
26
  }
29
27
  }
@@ -1,5 +1,7 @@
1
+ import VError from 'verror'
1
2
  import * as Error from '../Error/Error.js'
2
3
  import * as ReadJson from '../JsonFile/JsonFile.js'
4
+ import * as Path from '../Path/Path.js'
3
5
  import * as ExtensionManagement from './ExtensionManagement.js'
4
6
 
5
7
  export const getIconTheme = async (iconThemeId) => {
@@ -12,7 +14,7 @@ export const getIconTheme = async (iconThemeId) => {
12
14
  if (iconTheme.id !== iconThemeId) {
13
15
  continue
14
16
  }
15
- const absolutePath = `${extension.path}/${iconTheme.path}`
17
+ const absolutePath = Path.join(extension.path, iconTheme.path)
16
18
  try {
17
19
  const json = await ReadJson.readJson(absolutePath)
18
20
  return {
@@ -20,11 +22,7 @@ export const getIconTheme = async (iconThemeId) => {
20
22
  json,
21
23
  }
22
24
  } catch (error) {
23
- throw new Error.OperationalError({
24
- cause: error,
25
- code: 'E_ICON_THEME_COULD_NOT_BE_LOADED',
26
- message: `Failed to load icon theme "${iconThemeId}"`,
27
- })
25
+ throw new VError(error, `Failed to load icon theme "${iconThemeId}"`)
28
26
  }
29
27
  }
30
28
  }
@@ -0,0 +1,3 @@
1
+ export const Resolved = 'resolved'
2
+
3
+ export const Rejected = 'rejected'
@@ -1,12 +1,11 @@
1
1
  // TODO lazyload chokidar and trash (but doesn't work currently because of bug with jest)
2
2
  import * as fs from 'node:fs/promises'
3
3
  import * as os from 'node:os'
4
- import chokidar from 'chokidar'
5
- import * as Error from '../Error/Error.js'
4
+ import VError from 'verror'
5
+ import * as DirentType from '../DirentType/DirentType.js'
6
6
  import * as Path from '../Path/Path.js'
7
- import * as Trash from '../Trash/Trash.js'
8
7
  import * as Platform from '../Platform/Platform.js'
9
- import VError from 'verror'
8
+ import * as Trash from '../Trash/Trash.js'
10
9
 
11
10
  export const state = {
12
11
  watcherMap: Object.create(null),
@@ -27,26 +26,26 @@ export const copy = async (source, target) => {
27
26
  `Failed to copy "${source}" to "${target}": src and dest cannot be the same`
28
27
  )
29
28
  }
30
- throw new Error.OperationalError({
31
- cause: error,
32
- code: 'E_FILE_SYSTEM_ERROR',
33
- message: `Failed to copy "${source}" to "${target}"`,
34
- category: 'File System Error',
35
- })
29
+ throw new VError(error, `Failed to copy "${source}" to "${target}"`)
36
30
  }
37
31
  }
38
32
 
33
+ // TODO extensions should only be accessed once on startup
39
34
  export const readFile = async (path) => {
35
+ // console.info('[shared-process] read file', path)
40
36
  try {
37
+ // const start = performance.now()
38
+ // console.time(`read ${path}`)
41
39
  const content = await fs.readFile(path, 'utf8')
40
+ // const end = performance.now()
41
+ // console.log('read', path, 'took', (end - start).toFixed(2), 'ms')
42
+ // console.timeEnd(`read ${path}`)
42
43
  return content
43
44
  } catch (error) {
44
- throw new Error.OperationalError({
45
- cause: error,
46
- code: 'E_FILE_SYSTEM_ERROR',
47
- message: `Failed to read file "${path}"`,
48
- category: 'File System Error',
49
- })
45
+ if (error && error.code === 'ENOENT') {
46
+ throw new VError(`File not found '${path}'`)
47
+ }
48
+ throw new VError(error, `Failed to read file "${path}"`)
50
49
  }
51
50
  }
52
51
 
@@ -56,12 +55,7 @@ export const writeFile = async (path, content) => {
56
55
  // Queue.add(`writeFile/${path}`, () =>
57
56
  await fs.writeFile(path, content)
58
57
  } catch (error) {
59
- throw new Error.OperationalError({
60
- cause: error,
61
- code: 'E_FILE_SYSTEM_ERROR',
62
- message: `Failed to write to file "${path}"`,
63
- category: 'File System Error',
64
- })
58
+ throw new VError(error, `Failed to write to file "${path}"`)
65
59
  }
66
60
  }
67
61
 
@@ -70,12 +64,7 @@ export const ensureFile = async (path, content) => {
70
64
  await fs.mkdir(Path.dirname(path), { recursive: true })
71
65
  await fs.writeFile(path, content)
72
66
  } catch (error) {
73
- throw new Error.OperationalError({
74
- cause: error,
75
- code: 'E_FILE_SYSTEM_ERROR',
76
- message: `Failed to write to file "${path}"`,
77
- category: 'File System Error',
78
- })
67
+ throw new VError(error, `Failed to write to file "${path}"`)
79
68
  }
80
69
  }
81
70
 
@@ -83,12 +72,7 @@ export const createFile = async (path) => {
83
72
  try {
84
73
  await fs.writeFile(path, '', { flag: 'wx' })
85
74
  } catch (error) {
86
- throw new Error.OperationalError({
87
- cause: error,
88
- code: 'E_FILE_SYSTEM_ERROR',
89
- message: `Failed to create file "${path}"`,
90
- category: 'File System Error',
91
- })
75
+ throw new VError(error, `Failed to create file "${path}"`)
92
76
  }
93
77
  }
94
78
 
@@ -96,12 +80,7 @@ export const createFolder = async (path, options) => {
96
80
  try {
97
81
  await fs.mkdir(path, options)
98
82
  } catch (error) {
99
- throw new Error.OperationalError({
100
- cause: error,
101
- code: 'E_FILE_SYSTEM_ERROR',
102
- message: `Failed to create folder "${path}"`,
103
- category: 'File System Error',
104
- })
83
+ throw new VError(error, `Failed to create folder "${path}"`)
105
84
  }
106
85
  }
107
86
 
@@ -129,12 +108,7 @@ export const remove = async (path) => {
129
108
  try {
130
109
  await Trash.trash(path)
131
110
  } catch (error) {
132
- throw new Error.OperationalError({
133
- cause: error,
134
- code: 'E_FILE_REMOVE_SYSTEM_ERROR',
135
- message: `Failed to remove "${path}"`,
136
- category: 'File System Error',
137
- })
111
+ throw new VError(error, `Failed to remove "${path}"`)
138
112
  }
139
113
  }
140
114
 
@@ -152,24 +126,24 @@ export const exists = async (path) => {
152
126
  */
153
127
  const getType = (dirent) => {
154
128
  if (dirent.isFile()) {
155
- return 'file'
129
+ return DirentType.File
156
130
  }
157
131
  if (dirent.isDirectory()) {
158
- return 'directory'
132
+ return DirentType.Direcory
159
133
  }
160
134
  if (dirent.isSymbolicLink()) {
161
- return 'symlink'
135
+ return DirentType.Symlink
162
136
  }
163
137
  if (dirent.isSocket()) {
164
- return 'socket'
138
+ return DirentType.Socket
165
139
  }
166
140
  if (dirent.isBlockDevice()) {
167
- return 'block-device'
141
+ return DirentType.BlockDevice
168
142
  }
169
143
  if (dirent.isCharacterDevice()) {
170
- return 'character-device'
144
+ return DirentType.CharacterDevice
171
145
  }
172
- return 'unknown'
146
+ return DirentType.Unknown
173
147
  }
174
148
 
175
149
  /**
@@ -188,25 +162,15 @@ export const readDirWithFileTypes = async (path) => {
188
162
  const prettyDirents = dirents.map(toPrettyDirent)
189
163
  return prettyDirents
190
164
  } catch (error) {
191
- throw new Error.OperationalError({
192
- cause: error,
193
- code: 'E_FILE_READ_DIR_SYSTEM_ERROR',
194
- message: `Failed to read directory "${path}"`,
195
- category: 'File System Error',
196
- })
165
+ throw new VError(error, `Failed to read directory "${path}"`)
197
166
  }
198
167
  }
199
168
 
200
169
  export const mkdir = async (path) => {
201
170
  try {
202
- await fs.mkdir(path)
171
+ await fs.mkdir(path, { recursive: true })
203
172
  } catch (error) {
204
- throw new Error.OperationalError({
205
- cause: error,
206
- code: 'E_CREATE_DIRECTORY_FAILED',
207
- message: `Failed to create directory "${path}"`,
208
- category: 'File System Error',
209
- })
173
+ throw new VError(error, `Failed to create directory "${path}"`)
210
174
  }
211
175
  }
212
176
 
@@ -214,18 +178,13 @@ export const rename = async (oldPath, newPath) => {
214
178
  try {
215
179
  await fs.rename(oldPath, newPath)
216
180
  } catch (error) {
217
- throw new Error.OperationalError({
218
- cause: error,
219
- code: 'E_RENAME_FAILED',
220
- message: `Failed to rename "${oldPath}" to "${newPath}"`,
221
- category: 'File System Error',
222
- })
181
+ throw new VError(error, `Failed to rename "${oldPath}" to "${newPath}"`)
223
182
  }
224
183
  }
225
184
 
226
- export const watch = (path, options) => {
185
+ export const watch = async (path, options) => {
227
186
  // let state = 'loading'
228
-
187
+ const chokidar = await import('chokidar')
229
188
  const watcher = chokidar.watch(`${path}`, {
230
189
  ignoreInitial: true,
231
190
  })
@@ -1,65 +1,17 @@
1
- import fallback from 'json-parse-even-better-errors'
2
- import { codeFrameColumns } from '@babel/code-frame'
3
- import { LinesAndColumns } from 'lines-and-columns'
4
-
5
1
  // parsing error handling based on https://github.com/sindresorhus/parse-json/blob/main/index.js
6
2
 
7
3
  export const parse = async (string, filePath) => {
8
4
  try {
9
- try {
10
- return JSON.parse(string)
11
- } catch (error) {
12
- fallback(string)
13
- throw error
14
- }
5
+ return JSON.parse(string)
15
6
  } catch (error) {
16
- error.message = error.message.replace(/\n/g, '')
17
- const indexMatch = error.message.match(
18
- /in JSON at position (\d+) while parsing/
19
- )
20
- let topMessage = error.message
21
- let bottomMessage = error.message
22
- if (topMessage.startsWith('Unexpected token')) {
23
- topMessage = 'JSON parsing error:'
24
- }
25
- if (bottomMessage.includes('while parsing "{')) {
26
- bottomMessage = bottomMessage.slice(
27
- 0,
28
- bottomMessage.indexOf('while parsing "{')
29
- )
30
- }
31
- if (bottomMessage.includes(' (0x7D) ')) {
32
- bottomMessage = bottomMessage.replace(' (0x7D) ', ' ')
33
- }
34
- bottomMessage = bottomMessage.trim()
35
- const jsonError = new Error(topMessage)
36
- if (indexMatch && indexMatch.length > 0) {
37
- const lines = new LinesAndColumns(string)
38
- const index = Number(indexMatch[1])
39
- const location = lines.locationForIndex(index)
40
- const codeFrame = codeFrameColumns(
41
- string,
42
- { start: { line: location.line + 1, column: location.column + 1 } },
43
- { highlightCode: true }
44
- )
45
- jsonError.codeFrame = codeFrame
46
- }
47
- // console.log({ bottomMessage, message: error.message })
48
- jsonError.stack = `${bottomMessage}\n at ${filePath}`
7
+ const JsonError = await import('../JsonError/JsonError.js')
8
+ const errorProps = JsonError.getErrorPropsFromError(error, string, filePath)
9
+ const jsonError = new Error(errorProps.message)
10
+ jsonError.stack = errorProps.stack
11
+ // @ts-ignore
12
+ jsonError.codeFrame = errorProps.codeFrame
49
13
  throw jsonError
50
14
  }
51
-
52
- // try {
53
- // return JSON.parse(json)
54
- // } catch (error) {
55
- // const parseJsonBetterErrors = await import('parse-json')
56
- // try {
57
- // return parseJsonBetterErrors.default(json, filePath)
58
- // } catch (error) {
59
- // console.log({ frame: error.codeFrame })
60
- // throw error
61
- // }
62
- // }
63
15
  }
64
16
 
65
17
  export const stringify = (value) => {
@@ -0,0 +1,70 @@
1
+ import { codeFrameColumns } from '@babel/code-frame'
2
+ import { LinesAndColumns } from 'lines-and-columns'
3
+
4
+ // parsing error handling based on https://github.com/sindresorhus/parse-json/blob/main/index.js
5
+
6
+ const emptyError = {
7
+ message: '',
8
+ stack: '',
9
+ codeFrame: '',
10
+ }
11
+
12
+ export const getErrorPropsFromError = (error, string, filePath) => {
13
+ const indexMatch = error.message.match(/in JSON at position (\d+)/)
14
+ if (indexMatch && indexMatch.length > 0) {
15
+ const lines = new LinesAndColumns(string)
16
+ const index = Number(indexMatch[1])
17
+ const location = lines.locationForIndex(index)
18
+ if (location) {
19
+ const codeFrame = codeFrameColumns(
20
+ string,
21
+ { start: { line: location.line + 1, column: location.column + 1 } },
22
+ { highlightCode: false }
23
+ )
24
+ return {
25
+ codeFrame,
26
+ message: 'Json Parsing Error',
27
+ stack: `at ${filePath}`,
28
+ }
29
+ }
30
+ }
31
+ if (string.length === 0) {
32
+ return {
33
+ codeFrame: ``,
34
+ message: 'Json Parsing Error: Cannot parse empty string',
35
+ stack: `at ${filePath}`,
36
+ }
37
+ }
38
+
39
+ if (error.message === 'Unexpected end of JSON input') {
40
+ const lines = new LinesAndColumns(string)
41
+ const index = string.length - 1
42
+ const location = lines.locationForIndex(index)
43
+ if (location) {
44
+ const codeFrame = codeFrameColumns(
45
+ string,
46
+ { start: { line: location.line + 1, column: location.column + 1 } },
47
+ { highlightCode: false }
48
+ )
49
+ return {
50
+ codeFrame,
51
+ message: 'Json Parsing Error',
52
+ stack: `at ${filePath}`,
53
+ }
54
+ }
55
+ }
56
+ return {
57
+ codeFrame: ``,
58
+ message: 'Json Parsing Error',
59
+ stack: `at ${filePath}`,
60
+ }
61
+ }
62
+
63
+ export const getErrorProps = (string, filePath) => {
64
+ try {
65
+ JSON.parse(string)
66
+ return emptyError
67
+ } catch (error) {
68
+ return getErrorPropsFromError(error, string, filePath)
69
+ }
70
+ }
@@ -1,14 +1,14 @@
1
- import { mkdir, readFile, writeFile } from 'node:fs/promises'
2
- import * as Path from '../Path/Path.js'
1
+ import * as FileSystem from '../FileSystem/FileSystem.js'
3
2
  import * as Json from '../Json/Json.js'
3
+ import * as Path from '../Path/Path.js'
4
4
 
5
5
  export const readJson = async (absolutePath) => {
6
- const content = await readFile(absolutePath, 'utf-8')
6
+ const content = await FileSystem.readFile(absolutePath)
7
7
  const json = await Json.parse(content, absolutePath)
8
8
  return json
9
9
  }
10
10
 
11
11
  export const writeJson = async (absolutePath, value) => {
12
- await mkdir(Path.dirname(absolutePath), { recursive: true })
13
- await writeFile(absolutePath, Json.stringify(value))
12
+ await FileSystem.mkdir(Path.dirname(absolutePath))
13
+ await FileSystem.writeFile(absolutePath, Json.stringify(value))
14
14
  }
@@ -1,14 +1,10 @@
1
1
  import open from 'open'
2
- import * as Error from '../Error/Error.js'
2
+ import VError from 'verror'
3
3
 
4
4
  export const openFolder = async (path) => {
5
5
  try {
6
6
  await open(path)
7
7
  } catch (error) {
8
- throw new Error.OperationalError({
9
- cause: error,
10
- code: 'E_OPEN_SYSTEM_ERROR',
11
- message: `Failed to open ${path}`,
12
- })
8
+ throw new VError(error, `Failed to open ${path}`)
13
9
  }
14
10
  }
@@ -11,7 +11,7 @@ export const getUserPreferences = async () => {
11
11
  try {
12
12
  json = await JsonFile.readJson(userSettingsPath)
13
13
  } catch (error) {
14
- if (error && error.code === 'ENOENT') {
14
+ if (error && error.message.includes('File not found')) {
15
15
  return {}
16
16
  }
17
17
  throw error
@@ -0,0 +1,5 @@
1
+ import * as RecentlyOpened from './RecentlyOpened.js'
2
+
3
+ export const Commands = {
4
+ 'RecentlyOpened.addPath': RecentlyOpened.addPath,
5
+ }
@@ -0,0 +1,52 @@
1
+ import VError from 'verror'
2
+ import * as Assert from '../Assert/Assert.js'
3
+ import * as FileSystem from '../FileSystem/FileSystem.js'
4
+ import * as Json from '../Json/Json.js'
5
+ import * as Platform from '../Platform/Platform.js'
6
+
7
+ const isValid = (recentlyOpened) => {
8
+ return recentlyOpened && Array.isArray(recentlyOpened)
9
+ }
10
+
11
+ const addToArrayUnique = (recentlyOpened, path) => {
12
+ const index = recentlyOpened.indexOf(path)
13
+ if (index === -1) {
14
+ return [path, ...recentlyOpened]
15
+ }
16
+ return [
17
+ path,
18
+ ...recentlyOpened.slice(0, index),
19
+ ...recentlyOpened.slice(index + 1),
20
+ ]
21
+ }
22
+
23
+ const getRecentlyOpened = async (recentlyOpenedPath) => {
24
+ try {
25
+ const content = await FileSystem.readFile(recentlyOpenedPath)
26
+ const parsed = await Json.parse(content, recentlyOpenedPath)
27
+ return parsed
28
+ } catch (error) {
29
+ // TODO should check for error.code
30
+ if (error.message.includes('File not found')) {
31
+ // ignore
32
+ } else if (error.message.includes('Json Parsing Error')) {
33
+ // ignore
34
+ } else {
35
+ throw new VError(error, `Failed to read recently opened`)
36
+ }
37
+ return []
38
+ }
39
+ }
40
+
41
+ const setRecentlyOpened = async (recentlyOpenedPath, newRecentlyOpened) => {
42
+ const stringified = Json.stringify(newRecentlyOpened)
43
+ await FileSystem.writeFile(recentlyOpenedPath, stringified)
44
+ }
45
+
46
+ export const addPath = async (path) => {
47
+ Assert.string(path)
48
+ const recentlyOpenedPath = Platform.getRecentlyOpenedPath()
49
+ const parsed = await getRecentlyOpened(recentlyOpenedPath)
50
+ const newRecentlyOpened = addToArrayUnique(parsed, path)
51
+ await setRecentlyOpened(recentlyOpenedPath, newRecentlyOpened)
52
+ }
@@ -22,6 +22,12 @@ const useNice = !Platform.isWindows()
22
22
  // TODO update client
23
23
  // TODO not always run nice, maybe configure nice via flag/options
24
24
 
25
+ const ParsedLineType = {
26
+ Begin: 'begin',
27
+ Match: 'match',
28
+ Summary: 'summary',
29
+ }
30
+
25
31
  export const search = async (searchDir, searchString) => {
26
32
  // TODO reject promise when ripgrep search fails
27
33
  return new Promise((resolve, reject) => {
@@ -61,18 +67,18 @@ export const search = async (searchDir, searchString) => {
61
67
  for (const line of lines) {
62
68
  const parsedLine = JSON.parse(line)
63
69
  switch (parsedLine.type) {
64
- case 'begin': {
70
+ case ParsedLineType.Begin: {
65
71
  allSearchResults[parsedLine.data.path.text] = []
66
72
  break
67
73
  }
68
- case 'match': {
74
+ case ParsedLineType.Match: {
69
75
  numberOfResults++
70
76
  allSearchResults[parsedLine.data.path.text].push(
71
77
  toSearchResult(parsedLine)
72
78
  )
73
79
  break
74
80
  }
75
- case 'summary':
81
+ case ParsedLineType.Summary:
76
82
  stats = parsedLine.data
77
83
  break
78
84
  default: