Skip to content

Commit

Permalink
feat: allow issuing commands in the presence of errors (#433)
Browse files Browse the repository at this point in the history
  • Loading branch information
sockmaster27 authored Aug 20, 2024
1 parent 41342e6 commit 8e47879
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 83 deletions.
40 changes: 10 additions & 30 deletions server/src/handlers/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import * as engine from '../engine'
import * as socket from '../engine/socket'

import { clearDiagnostics, sendDiagnostics, sendNotification } from '../server'
import { makePositionalHandler, makeEnqueuePromise, enqueueUnlessHasErrors, makeDefaultResponseHandler } from './util'
import { makePositionalHandler, makeEnqueuePromise, makeDefaultResponseHandler } from './util'
import { USER_MESSAGE } from '../util/userMessages'
import { StatusCode } from '../util/statusCodes'

Expand Down Expand Up @@ -129,11 +129,7 @@ export function handleRemJar({ uri }: UriInput) {
engine.remJar(uri)
}

export const handleShowAst = enqueueUnlessHasErrors(
makeShowAstJob,
makeShowAstResponseHandler,
hasErrorsHandlerForCommands,
)
export const handleShowAst = makeEnqueuePromise(makeShowAstJob, makeShowAstResponseHandler)

/**
* Request a response to be sent when all jobs are finished.
Expand Down Expand Up @@ -179,11 +175,7 @@ export function handleChangeContent(params: TextDocumentChangeEvent<TextDocument
/**
* @function
*/
export const handleGotoDefinition = makePositionalHandler(
jobs.Request.lspGoto,
undefined,
makeGotoDefinitionResponseHandler,
)
export const handleGotoDefinition = makePositionalHandler(jobs.Request.lspGoto, makeGotoDefinitionResponseHandler)

function makeGotoDefinitionResponseHandler(promiseResolver: (result?: socket.FlixResult) => void) {
return function responseHandler({ status, result }: socket.FlixResponse) {
Expand Down Expand Up @@ -232,11 +224,9 @@ export const handleCodelens = makePositionalHandler(jobs.Request.lspCodelens)
/**
* @function
*/
export const handleRename = enqueueUnlessHasErrors(
makeRenameJob,
makeDefaultResponseHandler,
hasErrorsHandlerForCommands,
)
export const handleRename = makeEnqueuePromise(makeRenameJob, makeDefaultResponseHandler) as (
params: any,
) => Promise<any>

function makeRenameJob(params: any) {
return {
Expand Down Expand Up @@ -266,11 +256,9 @@ export function handleCodeAction(params: any): Promise<any> {
/**
* @function
*/
export const handleWorkspaceSymbols = enqueueUnlessHasErrors(
makeWorkspaceSymbolsJob,
makeDefaultResponseHandler,
hasErrorsHandlerForCommands,
)
export const handleWorkspaceSymbols = makeEnqueuePromise(makeWorkspaceSymbolsJob, makeDefaultResponseHandler) as (
params: any,
) => Promise<any>

function makeWorkspaceSymbolsJob(params: any) {
return {
Expand All @@ -294,18 +282,10 @@ export const handleInlayHints = (params: InlayHintParams): Thenable<any> =>
socket.eventEmitter.once(job.id, makeDefaultResponseHandler(resolve))
})

function hasErrorsHandlerForCommands() {
sendNotification(jobs.Request.internalError, {
message: 'Cannot run commands when errors are present.',
actions: [],
})
sendNotification(jobs.Request.internalFinishedJob)
}

/**
* @function
*/
export const handleVersion = makeEnqueuePromise(jobs.Request.apiVersion, makeVersionResponseHandler)
export const handleVersion = makeEnqueuePromise({ request: jobs.Request.apiVersion }, makeVersionResponseHandler)

function makeVersionResponseHandler(promiseResolver: () => void) {
return function responseHandler({ status, result }: any) {
Expand Down
51 changes: 10 additions & 41 deletions server/src/handlers/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import * as jobs from '../engine/jobs'
import * as engine from '../engine'
import * as socket from '../engine/socket'
import { hasErrors } from '../server'
import { StatusCode } from '../util/statusCodes'

type ResponseHandler = ({ status, result }: socket.FlixResponse) => void
Expand All @@ -32,45 +31,20 @@ export function makeDefaultResponseHandler(promiseResolver: (result?: socket.Fli
}
}

export function makeEnqueuePromise(
type: jobs.Request,
makeResponseHandler?: (promiseResolver: (result?: socket.FlixResult) => void) => ResponseHandler,
uri?: string,
position?: any,
) {
return function enqueuePromise() {
return new Promise(function (resolve) {
const job = engine.enqueueJobWithFlattenedParams(type, { uri, position })
const handler = makeResponseHandler || makeDefaultResponseHandler
socket.eventEmitter.once(job.id, handler(resolve))
})
}
}

/**
* Function to enqueue a job unless errors are present.
* If errors are present the hasErrorsHandler is called.
* Otherwise a promise is returned that is finally resolved with the result of running the command.
* Function to enqueue a job.
* A promise is returned that is finally resolved with the result of running the command.
*
* @param jobOrGetJob - Either a Job with request and optional params or a function that returns a Job
* @param makeResponseHandler
* @param hasErrorsHandler
*/
export function enqueueUnlessHasErrors(
jobOrGetJob: jobs.Job | ((params: any) => jobs.Job),
makeResponseHandler?: (promiseResolver: (result?: socket.FlixResult | undefined) => void) => ResponseHandler,
hasErrorsHandler?: () => void,
): (params: any) => any {
if (typeof hasErrorsHandler !== 'function') {
// development check (remove later)
throw '`enqueueUnlessHasErrors` must have `hasErrorsHandler` when called with errors'
}
return function enqueuePromise(params: any) {
if (hasErrors() && hasErrorsHandler) {
return hasErrorsHandler()
}
export function makeEnqueuePromise<JobParams extends unknown[]>(
jobOrGetJob: jobs.Job | ((...params: JobParams) => jobs.Job),
makeResponseHandler?: (promiseResolver: (result?: socket.FlixResult) => void) => ResponseHandler,
) {
return function enqueuePromise(...params: JobParams) {
return new Promise(function (resolve) {
const { request, ...jobData } = typeof jobOrGetJob === 'function' ? jobOrGetJob(params) : jobOrGetJob
const { request, ...jobData } = typeof jobOrGetJob === 'function' ? jobOrGetJob(...params) : jobOrGetJob
const job = engine.enqueueJobWithFlattenedParams(request, jobData)
const handler = makeResponseHandler || makeDefaultResponseHandler
socket.eventEmitter.once(job.id, handler(resolve))
Expand All @@ -80,16 +54,11 @@ export function enqueueUnlessHasErrors(

export function makePositionalHandler(
type: jobs.Request,
handlerWhenErrorsExist?: () => Thenable<any>,
makeResponseHandler?: (promiseResolver: (result?: socket.FlixResult) => void) => ResponseHandler,
) {
return function positionalHandler(params: any): Thenable<any> {
if (hasErrors() && handlerWhenErrorsExist) {
// NOTE: At present this isn't used by anyone (neither is makeResponseHandler)
return handlerWhenErrorsExist()
}
const uri = params.textDocument ? params.textDocument.uri : undefined
const uri = params.textDocument?.uri
const position = params.position
return makeEnqueuePromise(type, makeResponseHandler, uri, position)()
return makeEnqueuePromise({ request: type, uri, position }, makeResponseHandler)()
}
}
13 changes: 1 addition & 12 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,30 +119,19 @@ export function sendNotification(notificationType: string, payload?: any) {
// VS Code remembers files with errors and won't clear them itself.
const fileUrisWithErrors: Set<string> = new Set()

// A Boolean of whether the program contains errors.
let programHasError: boolean = false

export function hasErrors() {
return programHasError
}

/**
* Clear `fileUrisWithErrors` after removing error flags for all `uri`s.
*/
export function clearDiagnostics() {
fileUrisWithErrors.forEach((uri: string) => sendDiagnostics({ uri, diagnostics: [] }))
fileUrisWithErrors.clear()
programHasError = false
}

/**
* Proxy for `connection.sendDiagnostics` that also adds the `uri` to `fileUrisWithErrors`.
*/
export function sendDiagnostics(params: PublishDiagnosticsParams) {
params.diagnostics.forEach(diagnostic => {
if (diagnostic.severity && diagnostic.severity < 3) {
programHasError = true
}
params.diagnostics.forEach(() => {
fileUrisWithErrors.add(params.uri)
})
connection.sendDiagnostics(params)
Expand Down

0 comments on commit 8e47879

Please sign in to comment.