Comparing version 0.0.2 to 1.0.0
@@ -5,107 +5,129 @@ "use strict"; | ||
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument"); | ||
const defaultSettings = { projectPrefixes: [] }; | ||
let globalSettings = defaultSettings; | ||
const linear_1 = require("./linear"); | ||
const connection = (0, node_1.createConnection)(node_1.ProposedFeatures.all); | ||
const documents = new node_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument); | ||
const documentSettings = new Map(); | ||
let supportsConfiguration = false; | ||
let supportsWorkspaceFolder = false; | ||
connection.onInitialize((params) => { | ||
const capabilities = params.capabilities; | ||
const workspace = capabilities.workspace; | ||
supportsConfiguration = !!workspace?.configuration; | ||
supportsWorkspaceFolder = !!workspace?.workspaceFolders; | ||
const teamKeys = new Set(); | ||
const issues = new Map(); | ||
const issuePositions = new Map(); | ||
// [MOB-201](https://linear.app/eucalyptus/issue/MOB-201/devices-screen-not-paired-state) | ||
connection.onInitialize(async () => { | ||
await (0, linear_1.getTeamKeys)().then((keys) => { | ||
keys.map((k) => { | ||
teamKeys.add(k); | ||
}); | ||
}); | ||
const result = { | ||
capabilities: { | ||
textDocumentSync: node_1.TextDocumentSyncKind.Incremental, | ||
completionProvider: { | ||
resolveProvider: true, | ||
}, | ||
codeActionProvider: {}, | ||
hoverProvider: {}, | ||
completionProvider: { resolveProvider: true }, | ||
}, | ||
}; | ||
if (supportsWorkspaceFolder) { | ||
result.capabilities.workspace = { | ||
workspaceFolders: { | ||
supported: true, | ||
}, | ||
}; | ||
} | ||
return result; | ||
}); | ||
connection.onInitialized(() => { | ||
if (supportsConfiguration) { | ||
connection.client.register(node_1.DidChangeConfigurationNotification.type, undefined); | ||
connection.onCodeAction((params) => { | ||
if (params.context.triggerKind === node_1.CodeActionTriggerKind.Invoked) { | ||
const textDocument = documents.get(params.textDocument.uri); | ||
if (!textDocument) { | ||
return; | ||
} | ||
const text = textDocument.getText(params.range); | ||
if (!text) { | ||
return; | ||
} | ||
// TODO: Implement command to create ticket | ||
return [{ title: "Create Ticket" }]; | ||
} | ||
}); | ||
connection.onDidChangeConfiguration((change) => { | ||
if (supportsConfiguration) { | ||
documentSettings.clear(); | ||
documents.onDidChangeContent((change) => { | ||
const text = change.document.getText(); | ||
const documentPositions = []; | ||
Array.from(teamKeys) | ||
.flatMap((prefix) => { | ||
return Array.from(text.matchAll(new RegExp(`${prefix}-[0-9]*`, "g"))); | ||
}) | ||
.forEach((m) => { | ||
const issueKey = m[0]; | ||
const positionStart = change.document.positionAt(m.index ?? 0); | ||
const positionEnd = change.document.positionAt(issueKey.length + (m.index ?? 0)); | ||
// Write down that we've seen the issue, but don't | ||
// fetch the definition just yet. | ||
if (!issues.has(issueKey)) { | ||
issues.set(issueKey, undefined); | ||
} | ||
documentPositions.push({ | ||
issueKey, | ||
positionStart, | ||
positionEnd, | ||
offsetStart: change.document.offsetAt(positionStart), | ||
offsetEnd: change.document.offsetAt(positionEnd), | ||
}); | ||
}); | ||
issuePositions.set(change.document.uri, documentPositions); | ||
}); | ||
connection.onHover(async (params) => { | ||
const documentPositions = issuePositions.get(params.textDocument.uri); | ||
if (!documentPositions) { | ||
return; | ||
} | ||
else { | ||
// TODO: Validate this and send notification if cooked. | ||
globalSettings = (change.settings.lls || defaultSettings); | ||
const textDocument = documents.get(params.textDocument.uri); | ||
if (!textDocument) { | ||
return; | ||
} | ||
documents.all().forEach(identifyTickets); | ||
}); | ||
function getDocumentSettings(resource) { | ||
if (!supportsConfiguration) { | ||
return Promise.resolve(globalSettings); | ||
const cursorOffset = textDocument.offsetAt(params.position); | ||
const targetIssue = documentPositions.find((dp) => dp.offsetStart <= cursorOffset && dp.offsetEnd > cursorOffset); | ||
if (!targetIssue) { | ||
return; | ||
} | ||
let result = documentSettings.get(resource); | ||
if (!result) { | ||
result = connection.workspace.getConfiguration({ | ||
scopeUri: resource, | ||
section: "lls", | ||
}); | ||
documentSettings.set(resource, result); | ||
const issue = await (0, linear_1.getIssueByKey)(targetIssue.issueKey); | ||
if (!issue) { | ||
return; | ||
} | ||
return result; | ||
} | ||
documents.onDidClose((e) => { | ||
documentSettings.delete(e.document.uri); | ||
return { | ||
contents: issue.description ?? "Not available", | ||
}; | ||
}); | ||
documents.onDidChangeContent((change) => { | ||
identifyTickets(change.document); | ||
}); | ||
async function identifyTickets(textDocument) { | ||
const settings = await getDocumentSettings(textDocument.uri); | ||
if (settings.projectPrefixes.length === 0) { | ||
return; | ||
connection.onCompletion(async (params) => { | ||
if (teamKeys.size === 0) { | ||
return []; | ||
} | ||
const text = textDocument.getText(); | ||
const diagnostics = []; | ||
settings.projectPrefixes.forEach((prefix) => { | ||
Array.from(text.matchAll(new RegExp(`${prefix}-[0-9]*`, "g"))).forEach((m) => { | ||
diagnostics.push({ | ||
source: "Linear", | ||
message: m[0], | ||
severity: node_1.DiagnosticSeverity.Hint, | ||
range: { | ||
start: textDocument.positionAt(m.index ?? 0), | ||
end: textDocument.positionAt((m.index ?? 0) + m[0].length), | ||
}, | ||
}); | ||
}); | ||
const document = documents.get(params.textDocument.uri); | ||
if (!document) { | ||
return []; | ||
} | ||
const lineToPosition = document?.getText({ | ||
start: { line: params.position.line, character: 0 }, | ||
end: { line: params.position.line, character: params.position.character }, | ||
}); | ||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); | ||
} | ||
connection.onCompletion( | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
(_textDocumentPosition) => { | ||
return [ | ||
{ | ||
label: "MOB-1", | ||
data: "MOB-1", | ||
const lastMatch = Array.from(teamKeys) | ||
.flatMap((prefix) => Array.from(lineToPosition.matchAll(new RegExp(`(${prefix})-(.*)`, "g")))) | ||
.at(-1); | ||
if (!lastMatch) { | ||
return []; | ||
} | ||
const [, teamKey, searchString] = lastMatch; | ||
if (!teamKey || !searchString) { | ||
return []; | ||
} | ||
const issues = await (0, linear_1.findIssuesByTitle)([teamKey], searchString); | ||
if (issues === undefined) { | ||
return []; | ||
} | ||
return issues.map((issue) => { | ||
let label = `${issue.identifier}: ${issue.title}`; | ||
if (label.length > 60) { | ||
label = label.substring(0, 60) + "..."; | ||
} | ||
return { | ||
data: issue, | ||
detail: issue.description ?? "Not available", | ||
insertText: `[${issue.identifier}](${issue.url})`, | ||
filterText: `${issue.team.key}-${issue.title}`, | ||
label, | ||
kind: node_1.CompletionItemKind.Reference, | ||
}, | ||
]; | ||
}; | ||
}); | ||
}); | ||
connection.onCompletionResolve((item) => { | ||
if (item.data === "MOB-1") { | ||
item.detail = "Install the application"; | ||
item.documentation = "Details details"; | ||
} | ||
return item; | ||
}); | ||
documents.listen(connection); | ||
connection.listen(); |
{ | ||
"name": "linear-ls", | ||
"version": "0.0.2", | ||
"version": "1.0.0", | ||
"description": "Linear Language Server", | ||
@@ -15,2 +15,5 @@ "bin": "index.js", | ||
"dependencies": { | ||
"@urql/core": "^3.1.1", | ||
"graphql": "^16.6.0", | ||
"node-fetch": "^2.6.7", | ||
"vscode-languageserver": "^8.0.2", | ||
@@ -20,2 +23,6 @@ "vscode-languageserver-textdocument": "^1.0.8" | ||
"devDependencies": { | ||
"@graphql-codegen/cli": "^3.0.0", | ||
"@graphql-codegen/typescript": "^3.0.0", | ||
"@graphql-codegen/typescript-operations": "^3.0.0", | ||
"@types/node-fetch": "^2.6.2", | ||
"@typescript-eslint/eslint-plugin": "^5.50.0", | ||
@@ -39,3 +46,2 @@ "@typescript-eslint/parser": "^5.50.0", | ||
"@typescript-eslint/no-explicit-any": "error", | ||
"@typescript-eslint/explicit-module-boundary-types": "error", | ||
"@typescript-eslint/no-non-null-assertion": "error" | ||
@@ -42,0 +48,0 @@ } |
# lls | ||
Linear Language Server | ||
## Installation | ||
``` | ||
npm i -g linear-ls | ||
``` | ||
## Features | ||
### Hover on Issues | ||
Hovering on ticket identifier shows the ticket description. | ||
<img width="800" alt="image" src="https://user-images.githubusercontent.com/609452/218294991-ef0dfe07-832d-418b-9e7e-8b630f4c2c49.png"> | ||
### Issue Completion | ||
Typing team key, hyphen and search term (e.g. `EUC-thing`) triggers issue search. Selecting a result puts in a link to your issue. | ||
<img width="800" alt="image" src="https://user-images.githubusercontent.com/609452/218295062-4a0bbd6c-bb92-44c6-9301-4ab7b1522978.png"> | ||
### Create Ticket from Text Selection | ||
TODO |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
12090
8
296
1
28
5
9
3
+ Added@urql/core@^3.1.1
+ Addedgraphql@^16.6.0
+ Addednode-fetch@^2.6.7
+ Added@urql/core@3.2.2(transitive)
+ Addedgraphql@16.9.0(transitive)
+ Addednode-fetch@2.7.0(transitive)
+ Addedtr46@0.0.3(transitive)
+ Addedwebidl-conversions@3.0.1(transitive)
+ Addedwhatwg-url@5.0.0(transitive)
+ Addedwonka@6.3.4(transitive)