@sailshq/language-server 0.0.3 → 0.0.4
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/completions/sails-completions.js +63 -0
- package/go-to-definitions/go-to-action.js +1 -17
- package/go-to-definitions/go-to-helper.js +47 -0
- package/go-to-definitions/go-to-inertia-page.js +53 -0
- package/go-to-definitions/go-to-policy.js +90 -0
- package/go-to-definitions/go-to-view.js +70 -0
- package/helpers/find-fn-line.js +21 -0
- package/helpers/find-project-root.js +18 -0
- package/helpers/find-sails.js +12 -0
- package/helpers/load-sails.js +38 -0
- package/index.js +53 -9
- package/package.json +1 -1
- package/validators/validate-action-exist.js +16 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
const loadSails = require('../helpers/load-sails')
|
|
3
|
+
|
|
4
|
+
module.exports = async function sailsCompletions(document, position) {
|
|
5
|
+
const text = document.getText()
|
|
6
|
+
const offset = document.offsetAt(position)
|
|
7
|
+
const line = text.substring(0, offset).split('\n').pop()
|
|
8
|
+
|
|
9
|
+
const match = line.match(/sails((?:\.[a-zA-Z_$][0-9a-zA-Z_$]*)*)\.$/)
|
|
10
|
+
if (match) {
|
|
11
|
+
try {
|
|
12
|
+
return await loadSails(document.uri, (sailsApp) => {
|
|
13
|
+
const path = match[1].split('.').filter(Boolean)
|
|
14
|
+
return getNestedCompletions(sailsApp, path)
|
|
15
|
+
})
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return []
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getNestedCompletions(obj, path) {
|
|
25
|
+
let current = obj
|
|
26
|
+
for (const key of path) {
|
|
27
|
+
if (current && typeof current === 'object' && key in current) {
|
|
28
|
+
current = current[key]
|
|
29
|
+
} else {
|
|
30
|
+
return []
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof current !== 'object' || current === null) {
|
|
35
|
+
return []
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const completions = Object.keys(current).map((key) => {
|
|
39
|
+
const value = current[key]
|
|
40
|
+
let kind = lsp.CompletionItemKind.Property
|
|
41
|
+
let detail = 'Property'
|
|
42
|
+
|
|
43
|
+
if (typeof value === 'function') {
|
|
44
|
+
kind = lsp.CompletionItemKind.Method
|
|
45
|
+
detail = 'Method'
|
|
46
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
47
|
+
detail = 'Object'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
label: key,
|
|
52
|
+
kind: kind,
|
|
53
|
+
detail: detail,
|
|
54
|
+
documentation: `Access to sails${path.length ? '.' + path.join('.') : ''}.${key}`,
|
|
55
|
+
sortText: key.startsWith('_') ? `z${key}` : key // Add this line
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Sort the completions
|
|
60
|
+
completions.sort((a, b) => a.sortText.localeCompare(b.sortText))
|
|
61
|
+
|
|
62
|
+
return completions
|
|
63
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const lsp = require('vscode-languageserver/node')
|
|
2
2
|
const path = require('path')
|
|
3
|
-
const
|
|
4
|
-
const url = require('url')
|
|
3
|
+
const findFnLine = require('../helpers/find-fn-line')
|
|
5
4
|
|
|
6
5
|
module.exports = async function goToAction(document, position) {
|
|
7
6
|
const fileName = path.basename(document.uri)
|
|
@@ -65,18 +64,3 @@ function extractActionInfo(document, position) {
|
|
|
65
64
|
function resolveActionPath(projectRoot, actionPath) {
|
|
66
65
|
return path.join(projectRoot, 'api', 'controllers', `${actionPath}.js`)
|
|
67
66
|
}
|
|
68
|
-
|
|
69
|
-
async function findFnLine(filePath) {
|
|
70
|
-
try {
|
|
71
|
-
const content = await fs.readFile(url.fileURLToPath(filePath), 'utf8')
|
|
72
|
-
const lines = content.split('\n')
|
|
73
|
-
for (let i = 0; i < lines.length; i++) {
|
|
74
|
-
if (lines[i].includes('fn:')) {
|
|
75
|
-
return i // Return the line number (0-based index)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return 0 // If 'fn:' is not found, return the first line
|
|
79
|
-
} catch (error) {
|
|
80
|
-
return 0 // Return the first line if there's an error
|
|
81
|
-
}
|
|
82
|
-
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const fs = require('fs').promises
|
|
4
|
+
const findProjectRoot = require('../helpers/find-project-root')
|
|
5
|
+
const findFnLine = require('../helpers/find-fn-line')
|
|
6
|
+
|
|
7
|
+
module.exports = async function goToHelper(document, position) {
|
|
8
|
+
const helperInfo = extractHelperInfo(document, position)
|
|
9
|
+
|
|
10
|
+
if (!helperInfo) {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const projectRoot = await findProjectRoot(document.uri)
|
|
15
|
+
const fullHelperPath =
|
|
16
|
+
path.join(projectRoot, 'api', 'helpers', ...helperInfo.helperPath) + '.js'
|
|
17
|
+
|
|
18
|
+
if (fullHelperPath) {
|
|
19
|
+
const fnLineNumber = await findFnLine(fullHelperPath)
|
|
20
|
+
return lsp.Location.create(
|
|
21
|
+
fullHelperPath,
|
|
22
|
+
lsp.Range.create(fnLineNumber, 0, fnLineNumber, 0)
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function extractHelperInfo(document, position) {
|
|
28
|
+
const text = document.getText()
|
|
29
|
+
const offset = document.offsetAt(position)
|
|
30
|
+
|
|
31
|
+
// Regular expression to match sails.helpers.exampleHelper() or sails.helpers.exampleHelper.with()
|
|
32
|
+
// Also matches nested helpers like sails.helpers.mail.send() or sails.helpers.mail.send.with()
|
|
33
|
+
const regex = /sails\.helpers\.([a-zA-Z0-9.]+)(?:\.with)?\s*\(/g
|
|
34
|
+
let match
|
|
35
|
+
|
|
36
|
+
while ((match = regex.exec(text)) !== null) {
|
|
37
|
+
const start = match.index
|
|
38
|
+
const end = start + match[0].length
|
|
39
|
+
|
|
40
|
+
if (start <= offset && offset <= end) {
|
|
41
|
+
const helperPath = match[1].split('.').filter((part) => part !== 'with')
|
|
42
|
+
return { helperPath }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const fs = require('fs').promises
|
|
4
|
+
const findProjectRoot = require('../helpers/find-project-root')
|
|
5
|
+
|
|
6
|
+
module.exports = async function goToInertiaPage(document, position) {
|
|
7
|
+
const pageInfo = extractPageInfo(document, position)
|
|
8
|
+
|
|
9
|
+
if (!pageInfo) {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const projectRoot = await findProjectRoot(document.uri)
|
|
14
|
+
const possibleExtensions = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte'] // Add or remove extensions as needed
|
|
15
|
+
|
|
16
|
+
for (const ext of possibleExtensions) {
|
|
17
|
+
const fullPagePath = path.join(
|
|
18
|
+
projectRoot,
|
|
19
|
+
'assets',
|
|
20
|
+
'js',
|
|
21
|
+
'pages',
|
|
22
|
+
`${pageInfo.page}${ext}`
|
|
23
|
+
)
|
|
24
|
+
try {
|
|
25
|
+
await fs.access(fullPagePath)
|
|
26
|
+
return lsp.Location.create(fullPagePath, lsp.Range.create(0, 0, 0, 0))
|
|
27
|
+
} catch (error) {
|
|
28
|
+
// File doesn't exist with this extension, try the next one
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function extractPageInfo(document, position) {
|
|
36
|
+
const text = document.getText()
|
|
37
|
+
const offset = document.offsetAt(position)
|
|
38
|
+
|
|
39
|
+
// Regular expression to match { page: 'example' } or { page: "example" }
|
|
40
|
+
const regex = /{\s*page\s*:\s*['"]([^'"]+)['"]\s*}/g
|
|
41
|
+
let match
|
|
42
|
+
|
|
43
|
+
while ((match = regex.exec(text)) !== null) {
|
|
44
|
+
const start = match.index
|
|
45
|
+
const end = start + match[0].length
|
|
46
|
+
|
|
47
|
+
if (start <= offset && offset <= end) {
|
|
48
|
+
return { page: match[1] }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const fs = require('fs').promises
|
|
4
|
+
|
|
5
|
+
module.exports = async function goToPolicy(document, position) {
|
|
6
|
+
const fileName = path.basename(document.uri)
|
|
7
|
+
|
|
8
|
+
if (fileName !== 'policies.js') {
|
|
9
|
+
return null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const policyInfo = extractPolicyInfo(document, position)
|
|
13
|
+
|
|
14
|
+
if (!policyInfo) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const projectRoot = path.dirname(path.dirname(document.uri))
|
|
19
|
+
const fullPolicyPath = resolvePolicyPath(projectRoot, policyInfo.policy)
|
|
20
|
+
|
|
21
|
+
if (await fileExists(fullPolicyPath)) {
|
|
22
|
+
return lsp.Location.create(fullPolicyPath, lsp.Range.create(0, 0, 0, 0))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractPolicyInfo(document, position) {
|
|
29
|
+
const text = document.getText()
|
|
30
|
+
const offset = document.offsetAt(position)
|
|
31
|
+
|
|
32
|
+
// This regex matches policy definitions, including arrays of policies and boolean values
|
|
33
|
+
const regex =
|
|
34
|
+
/(['"])((?:\*|[\w-]+(?:\/\*?)?))?\1\s*:\s*((?:\[?\s*(?:(?:['"][\w-]+['"](?:\s*,\s*)?)+)\s*\]?)|true|false)/g
|
|
35
|
+
let match
|
|
36
|
+
|
|
37
|
+
while ((match = regex.exec(text)) !== null) {
|
|
38
|
+
const [fullMatch, , route, policiesOrBoolean] = match
|
|
39
|
+
const start = match.index
|
|
40
|
+
const end = start + fullMatch.length
|
|
41
|
+
|
|
42
|
+
// Check if the cursor is anywhere within the entire match
|
|
43
|
+
if (start <= offset && offset <= end) {
|
|
44
|
+
// If policiesOrBoolean is a boolean, ignore it
|
|
45
|
+
if (policiesOrBoolean === true || policiesOrBoolean === false) {
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Remove brackets if present and split into individual policies
|
|
50
|
+
const policies = policiesOrBoolean
|
|
51
|
+
.replace(/^\[|\]$/g, '')
|
|
52
|
+
.split(',')
|
|
53
|
+
.map((p) => p.trim().replace(/^['"]|['"]$/g, ''))
|
|
54
|
+
|
|
55
|
+
// Find which policy the cursor is on
|
|
56
|
+
let currentStart = start + fullMatch.indexOf(policiesOrBoolean)
|
|
57
|
+
for (const policy of policies) {
|
|
58
|
+
const policyStart = text.indexOf(policy, currentStart)
|
|
59
|
+
const policyEnd = policyStart + policy.length
|
|
60
|
+
|
|
61
|
+
if (offset >= policyStart && offset <= policyEnd) {
|
|
62
|
+
return {
|
|
63
|
+
policy,
|
|
64
|
+
range: lsp.Range.create(
|
|
65
|
+
document.positionAt(policyStart),
|
|
66
|
+
document.positionAt(policyEnd)
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
currentStart = policyEnd
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolvePolicyPath(projectRoot, policyPath) {
|
|
80
|
+
return path.join(projectRoot, 'api', 'policies', `${policyPath}.js`)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function fileExists(filePath) {
|
|
84
|
+
try {
|
|
85
|
+
await fs.access(new URL(filePath))
|
|
86
|
+
return true
|
|
87
|
+
} catch {
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const fs = require('fs').promises
|
|
4
|
+
|
|
5
|
+
const findProjectRoot = require('../helpers/find-project-root')
|
|
6
|
+
|
|
7
|
+
module.exports = async function goToView(document, position) {
|
|
8
|
+
const viewInfo = extractViewInfo(document, position)
|
|
9
|
+
|
|
10
|
+
if (!viewInfo) {
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const projectRoot = await findProjectRoot(document.uri)
|
|
15
|
+
|
|
16
|
+
const fullViewPath = resolveViewPath(projectRoot, viewInfo.view)
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await fs.access(fullViewPath)
|
|
20
|
+
return lsp.Location.create(fullViewPath, lsp.Range.create(0, 0, 0, 0))
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveViewPath(projectRoot, viewPath) {
|
|
27
|
+
return path.join(projectRoot, 'views', `${viewPath}.ejs`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function extractViewInfo(document, position) {
|
|
31
|
+
const text = document.getText()
|
|
32
|
+
const offset = document.offsetAt(position)
|
|
33
|
+
|
|
34
|
+
// This regex matches both object notation for views and viewTemplatePath
|
|
35
|
+
const regex =
|
|
36
|
+
/(?:(['"])(.+?)\1\s*:\s*{\s*view\s*:\s*(['"])(.+?)\3\s*}|viewTemplatePath\s*:\s*(['"])(.+?)\5)/g
|
|
37
|
+
let match
|
|
38
|
+
|
|
39
|
+
while ((match = regex.exec(text)) !== null) {
|
|
40
|
+
const [fullMatch, , , , viewInObject, , viewInController] = match
|
|
41
|
+
const view = viewInObject || viewInController
|
|
42
|
+
const start = match.index
|
|
43
|
+
const end = start + fullMatch.length
|
|
44
|
+
|
|
45
|
+
// Check if the cursor is anywhere within the entire match
|
|
46
|
+
if (start <= offset && offset <= end) {
|
|
47
|
+
// Find the start and end positions of the view part, including quotes
|
|
48
|
+
const viewStartWithQuote = text.lastIndexOf(
|
|
49
|
+
"'",
|
|
50
|
+
text.indexOf(view, start)
|
|
51
|
+
) // Find the opening quote
|
|
52
|
+
const viewEndWithQuote =
|
|
53
|
+
text.indexOf("'", text.indexOf(view, start) + view.length) + 1 // Find the closing quote and include it
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
view,
|
|
57
|
+
range: lsp.Range.create(
|
|
58
|
+
document.positionAt(viewStartWithQuote),
|
|
59
|
+
document.positionAt(viewEndWithQuote)
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveViewPath(projectRoot, viewPath) {
|
|
69
|
+
return path.join(projectRoot, 'views', `${viewPath}.ejs`)
|
|
70
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const fs = require('fs').promises
|
|
2
|
+
const url = require('url')
|
|
3
|
+
|
|
4
|
+
module.exports = async function findFnLine(filePath) {
|
|
5
|
+
try {
|
|
6
|
+
const resolvedPath = filePath.startsWith('file:')
|
|
7
|
+
? url.fileURLToPath(filePath)
|
|
8
|
+
: filePath
|
|
9
|
+
|
|
10
|
+
const content = await fs.readFile(resolvedPath, 'utf8')
|
|
11
|
+
const lines = content.split('\n')
|
|
12
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13
|
+
if (lines[i].includes('fn:')) {
|
|
14
|
+
return i // Return the line number (0-based index)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return 0 // If 'fn:' is not found, return the first line
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return 0 // Return the first line if there's an error
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const url = require('url')
|
|
3
|
+
const fs = require('fs').promises
|
|
4
|
+
|
|
5
|
+
module.exports = async function findProjectRoot(uri) {
|
|
6
|
+
let currentPath = path.dirname(url.fileURLToPath(uri))
|
|
7
|
+
const root = path.parse(currentPath).root
|
|
8
|
+
|
|
9
|
+
while (currentPath !== root) {
|
|
10
|
+
try {
|
|
11
|
+
await fs.access(path.join(currentPath, 'package.json'))
|
|
12
|
+
return currentPath
|
|
13
|
+
} catch (error) {
|
|
14
|
+
currentPath = path.dirname(currentPath)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
throw new Error('Could not find project root')
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const findProjectRoot = require('./find-project-root')
|
|
4
|
+
|
|
5
|
+
module.exports = async function findSails(workspaceUri) {
|
|
6
|
+
const projectRoot = await findProjectRoot(workspaceUri)
|
|
7
|
+
const sailsPath = path.join(projectRoot, 'node_modules', 'sails')
|
|
8
|
+
if (fs.existsSync(sailsPath)) {
|
|
9
|
+
return { sailsPath, projectRoot }
|
|
10
|
+
}
|
|
11
|
+
throw new Error('Sails not found in node_modules')
|
|
12
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const findSails = require('./find-sails')
|
|
2
|
+
|
|
3
|
+
module.exports = async function loadSails(workspaceUri, operation) {
|
|
4
|
+
let Sails
|
|
5
|
+
let sailsApp
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const { sailsPath } = await findSails(workspaceUri)
|
|
9
|
+
Sails = require(sailsPath).constructor
|
|
10
|
+
|
|
11
|
+
sailsApp = await new Promise((resolve, reject) => {
|
|
12
|
+
new Sails().load(
|
|
13
|
+
{
|
|
14
|
+
hooks: { shipwright: false },
|
|
15
|
+
log: { level: 'silent' }
|
|
16
|
+
},
|
|
17
|
+
(err, sails) => {
|
|
18
|
+
if (err) {
|
|
19
|
+
console.error('Failed to load Sails app:', err)
|
|
20
|
+
return reject(err)
|
|
21
|
+
}
|
|
22
|
+
resolve(sails)
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Execute the operation with the loaded Sails app
|
|
28
|
+
return await operation(sailsApp)
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Error loading or working with Sails app:', error)
|
|
31
|
+
throw error
|
|
32
|
+
} finally {
|
|
33
|
+
// Ensure Sails is lowered even if an error occurred
|
|
34
|
+
if (sailsApp && typeof sailsApp.lower === 'function') {
|
|
35
|
+
await new Promise((resolve) => sailsApp.lower(resolve))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/index.js
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
const lsp = require('vscode-languageserver/node')
|
|
2
2
|
const TextDocument = require('vscode-languageserver-textdocument').TextDocument
|
|
3
|
+
|
|
4
|
+
// Validators
|
|
3
5
|
const validateDocument = require('./validators/validate-document')
|
|
6
|
+
|
|
7
|
+
// Go-to definitions
|
|
4
8
|
const goToAction = require('./go-to-definitions/go-to-action')
|
|
9
|
+
const goToPolicy = require('./go-to-definitions/go-to-policy')
|
|
10
|
+
const goToView = require('./go-to-definitions/go-to-view')
|
|
11
|
+
const goToInertiaPage = require('./go-to-definitions/go-to-inertia-page')
|
|
12
|
+
const goToHelper = require('./go-to-definitions/go-to-helper')
|
|
13
|
+
|
|
14
|
+
// Completions
|
|
15
|
+
const sailsCompletions = require('./completions/sails-completions')
|
|
5
16
|
|
|
6
17
|
const connection = lsp.createConnection(lsp.ProposedFeatures.all)
|
|
7
18
|
const documents = new lsp.TextDocuments(TextDocument)
|
|
@@ -10,11 +21,10 @@ connection.onInitialize((params) => {
|
|
|
10
21
|
return {
|
|
11
22
|
capabilities: {
|
|
12
23
|
textDocumentSync: lsp.TextDocumentSyncKind.Incremental,
|
|
13
|
-
definitionProvider: true
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// }
|
|
24
|
+
definitionProvider: true,
|
|
25
|
+
completionProvider: {
|
|
26
|
+
triggerCharacters: ['"', "'", '.']
|
|
27
|
+
}
|
|
18
28
|
}
|
|
19
29
|
}
|
|
20
30
|
})
|
|
@@ -32,11 +42,45 @@ connection.onDefinition(async (params) => {
|
|
|
32
42
|
if (!document) {
|
|
33
43
|
return null
|
|
34
44
|
}
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
|
|
46
|
+
const actionDefinition = await goToAction(document, params.position)
|
|
47
|
+
const policyDefinition = await goToPolicy(document, params.position)
|
|
48
|
+
const viewDefinition = await goToView(document, params.position)
|
|
49
|
+
const inertiaPageDefinition = await goToInertiaPage(document, params.position)
|
|
50
|
+
const helperDefinition = await goToHelper(document, params.position)
|
|
51
|
+
|
|
52
|
+
const definitions = [
|
|
53
|
+
actionDefinition,
|
|
54
|
+
policyDefinition,
|
|
55
|
+
viewDefinition,
|
|
56
|
+
inertiaPageDefinition,
|
|
57
|
+
helperDefinition
|
|
58
|
+
].filter(Boolean)
|
|
59
|
+
|
|
60
|
+
return definitions.length > 0 ? definitions : null
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
connection.onCompletion(async (params) => {
|
|
64
|
+
const document = documents.get(params.textDocument.uri)
|
|
65
|
+
if (!document) {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const completions = await sailsCompletions(document, params.position)
|
|
70
|
+
|
|
71
|
+
if (completions) {
|
|
72
|
+
return {
|
|
73
|
+
isIncomplete: false,
|
|
74
|
+
items: completions
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null
|
|
39
79
|
})
|
|
40
80
|
|
|
41
81
|
documents.listen(connection)
|
|
42
82
|
connection.listen()
|
|
83
|
+
|
|
84
|
+
connection.console.log = (message) => {
|
|
85
|
+
console.log(message)
|
|
86
|
+
}
|
package/package.json
CHANGED
|
@@ -18,6 +18,10 @@ module.exports = function validateActionExist(document) {
|
|
|
18
18
|
const actions = extractActionInfo(document) // Get all actions
|
|
19
19
|
|
|
20
20
|
for (const { action, range } of actions) {
|
|
21
|
+
if (isUrlOrRedirect(action)) {
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
24
|
+
|
|
21
25
|
const fullActionPath = resolveActionPath(projectRoot, action)
|
|
22
26
|
if (!fs.existsSync(url.fileURLToPath(fullActionPath))) {
|
|
23
27
|
const diagnostic = {
|
|
@@ -63,3 +67,15 @@ function extractActionInfo(document) {
|
|
|
63
67
|
function resolveActionPath(projectRoot, actionPath) {
|
|
64
68
|
return path.join(projectRoot, 'api', 'controllers', `${actionPath}.js`)
|
|
65
69
|
}
|
|
70
|
+
|
|
71
|
+
function isUrlOrRedirect(action) {
|
|
72
|
+
if (action.startsWith('http://') || action.startsWith('https://')) {
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (action.startsWith('/')) {
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return false
|
|
81
|
+
}
|