Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] get auto completion for the sails object #15

Merged
merged 6 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions packages/language-server/completions/sails-completions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const lsp = require('vscode-languageserver/node')
const loadSails = require('../helpers/load-sails')

module.exports = async function sailsCompletions(document, position) {
const text = document.getText()
const offset = document.offsetAt(position)
const line = text.substring(0, offset).split('\n').pop()

const match = line.match(/sails((?:\.[a-zA-Z_$][0-9a-zA-Z_$]*)*)\.$/)
if (match) {
try {
return await loadSails(document.uri, (sailsApp) => {
const path = match[1].split('.').filter(Boolean)
return getNestedCompletions(sailsApp, path)
})
} catch (error) {
return []
}
}

return null
}

function getNestedCompletions(obj, path) {
let current = obj
for (const key of path) {
if (current && typeof current === 'object' && key in current) {
current = current[key]
} else {
return []
}
}

if (typeof current !== 'object' || current === null) {
return []
}

const completions = Object.keys(current).map((key) => {
const value = current[key]
let kind = lsp.CompletionItemKind.Property
let detail = 'Property'

if (typeof value === 'function') {
kind = lsp.CompletionItemKind.Method
detail = 'Method'
} else if (typeof value === 'object' && value !== null) {
detail = 'Object'
}

return {
label: key,
kind: kind,
detail: detail,
documentation: `Access to sails${path.length ? '.' + path.join('.') : ''}.${key}`,
sortText: key.startsWith('_') ? `z${key}` : key // Add this line
}
})

// Sort the completions
completions.sort((a, b) => a.sortText.localeCompare(b.sortText))

return completions
}
18 changes: 2 additions & 16 deletions packages/language-server/go-to-definitions/go-to-view.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const lsp = require('vscode-languageserver/node')
const path = require('path')
const fs = require('fs').promises
const url = require('url')

const findProjectRoot = require('../helpers/find-project-root')

module.exports = async function goToView(document, position) {
const viewInfo = extractViewInfo(document, position)
Expand Down Expand Up @@ -67,18 +68,3 @@ function extractViewInfo(document, position) {
function resolveViewPath(projectRoot, viewPath) {
return path.join(projectRoot, 'views', `${viewPath}.ejs`)
}

async function findProjectRoot(uri) {
let currentPath = path.dirname(url.fileURLToPath(uri))
const root = path.parse(currentPath).root

while (currentPath !== root) {
try {
await fs.access(path.join(currentPath, 'package.json'))
return currentPath
} catch (error) {
currentPath = path.dirname(currentPath)
}
}
throw new Error('Could not find project root')
}
18 changes: 18 additions & 0 deletions packages/language-server/helpers/find-project-root.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const path = require('path')
const url = require('url')
const fs = require('fs').promises

module.exports = async function findProjectRoot(uri) {
let currentPath = path.dirname(url.fileURLToPath(uri))
const root = path.parse(currentPath).root

while (currentPath !== root) {
try {
await fs.access(path.join(currentPath, 'package.json'))
return currentPath
} catch (error) {
currentPath = path.dirname(currentPath)
}
}
throw new Error('Could not find project root')
}
12 changes: 12 additions & 0 deletions packages/language-server/helpers/find-sails.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const path = require('path')
const fs = require('fs')
const findProjectRoot = require('./find-project-root')

module.exports = async function findSails(workspaceUri) {
const projectRoot = await findProjectRoot(workspaceUri)
const sailsPath = path.join(projectRoot, 'node_modules', 'sails')
if (fs.existsSync(sailsPath)) {
return { sailsPath, projectRoot }
}
throw new Error('Sails not found in node_modules')
}
38 changes: 38 additions & 0 deletions packages/language-server/helpers/load-sails.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const findSails = require('./find-sails')

module.exports = async function loadSails(workspaceUri, operation) {
let Sails
let sailsApp

try {
const { sailsPath } = await findSails(workspaceUri)
Sails = require(sailsPath).constructor

sailsApp = await new Promise((resolve, reject) => {
new Sails().load(
{
hooks: { shipwright: false },
log: { level: 'silent' }
},
(err, sails) => {
if (err) {
console.error('Failed to load Sails app:', err)
return reject(err)
}
resolve(sails)
}
)
})

// Execute the operation with the loaded Sails app
return await operation(sailsApp)
} catch (error) {
console.error('Error loading or working with Sails app:', error)
throw error
} finally {
// Ensure Sails is lowered even if an error occurred
if (sailsApp && typeof sailsApp.lower === 'function') {
await new Promise((resolve) => sailsApp.lower(resolve))
}
}
}
28 changes: 23 additions & 5 deletions packages/language-server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const validateDocument = require('./validators/validate-document')
const goToAction = require('./go-to-definitions/go-to-action')
const goToPolicy = require('./go-to-definitions/go-to-policy')
const goToView = require('./go-to-definitions/go-to-view')
const sailsCompletions = require('./completions/sails-completions')

const connection = lsp.createConnection(lsp.ProposedFeatures.all)
const documents = new lsp.TextDocuments(TextDocument)
Expand All @@ -12,11 +13,10 @@ connection.onInitialize((params) => {
return {
capabilities: {
textDocumentSync: lsp.TextDocumentSyncKind.Incremental,
definitionProvider: true
// completionProvider: {
// resolveProvider: true,
// triggerCharacters: ['"', "'", '.']
// }
definitionProvider: true,
completionProvider: {
triggerCharacters: ['"', "'", '.']
}
}
}
})
Expand Down Expand Up @@ -48,6 +48,24 @@ connection.onDefinition(async (params) => {
return definitions.length > 0 ? definitions : null
})

connection.onCompletion(async (params) => {
const document = documents.get(params.textDocument.uri)
if (!document) {
return null
}

const completions = await sailsCompletions(document, params.position)

if (completions) {
return {
isIncomplete: false,
items: completions
}
}

return null
})

documents.listen(connection)
connection.listen()

Expand Down
Loading