Skip to content

Commit

Permalink
Merge pull request #15 from sailshq/feat/get-sails-completion
Browse files Browse the repository at this point in the history
[feat] get sails completion
  • Loading branch information
DominusKelvin authored Sep 13, 2024
2 parents 91cc95d + cd7df23 commit 2bae818
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 21 deletions.
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

0 comments on commit 2bae818

Please sign in to comment.