Skip to content

Commit

Permalink
feat: implement go-to view definition
Browse files Browse the repository at this point in the history
  • Loading branch information
DominusKelvin committed Sep 11, 2024
1 parent 91e15c3 commit 9c01c09
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 1 deletion.
84 changes: 84 additions & 0 deletions packages/language-server/go-to-definitions/go-to-view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const lsp = require('vscode-languageserver/node')
const path = require('path')
const fs = require('fs').promises
const url = require('url')

module.exports = async function goToView(document, position) {
const viewInfo = extractViewInfo(document, position)

if (!viewInfo) {
return null
}

const projectRoot = await findProjectRoot(document.uri)

const fullViewPath = resolveViewPath(projectRoot, viewInfo.view)

try {
await fs.access(fullViewPath)
return lsp.Location.create(fullViewPath, lsp.Range.create(0, 0, 0, 0))
} catch (error) {
return null
}
}

function resolveViewPath(projectRoot, viewPath) {
return path.join(projectRoot, 'views', `${viewPath}.ejs`)
}

function extractViewInfo(document, position) {
const text = document.getText()
const offset = document.offsetAt(position)

// This regex matches both object notation for views and viewTemplatePath
const regex =
/(?:(['"])(.+?)\1\s*:\s*{\s*view\s*:\s*(['"])(.+?)\3\s*}|viewTemplatePath\s*:\s*(['"])(.+?)\5)/g
let match

while ((match = regex.exec(text)) !== null) {
const [fullMatch, , , , viewInObject, , viewInController] = match
const view = viewInObject || viewInController
const start = match.index
const end = start + fullMatch.length

// Check if the cursor is anywhere within the entire match
if (start <= offset && offset <= end) {
// Find the start and end positions of the view part, including quotes
const viewStartWithQuote = text.lastIndexOf(
"'",
text.indexOf(view, start)
) // Find the opening quote
const viewEndWithQuote =
text.indexOf("'", text.indexOf(view, start) + view.length) + 1 // Find the closing quote and include it

return {
view,
range: lsp.Range.create(
document.positionAt(viewStartWithQuote),
document.positionAt(viewEndWithQuote)
)
}
}
}

return null
}

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')
}
12 changes: 11 additions & 1 deletion packages/language-server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const TextDocument = require('vscode-languageserver-textdocument').TextDocument
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 connection = lsp.createConnection(lsp.ProposedFeatures.all)
const documents = new lsp.TextDocuments(TextDocument)
Expand Down Expand Up @@ -36,11 +37,20 @@ connection.onDefinition(async (params) => {

const actionDefinition = await goToAction(document, params.position)
const policyDefinition = await goToPolicy(document, params.position)
const viewDefinition = await goToView(document, params.position)

const definitions = [actionDefinition, policyDefinition].filter(Boolean)
const definitions = [
actionDefinition,
policyDefinition,
viewDefinition
].filter(Boolean)

return definitions.length > 0 ? definitions : null
})

documents.listen(connection)
connection.listen()

connection.console.log = (message) => {
console.log(message)
}

0 comments on commit 9c01c09

Please sign in to comment.