Skip to content

Commit

Permalink
🚧 Add code actions
Browse files Browse the repository at this point in the history
  • Loading branch information
misode committed Dec 30, 2024
1 parent 076a83d commit 3c6da15
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 7 deletions.
14 changes: 14 additions & 0 deletions packages/core/src/processor/codeActions/CodeAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { AstNode } from '../../node/index.js'
import type { CodeActionProviderContext } from '../../service/index.js'
import type { LanguageError } from '../../source/index.js'

export interface CodeAction {
title: string
isPreferred?: boolean
errors?: LanguageError[]
}

export type CodeActionProvider<N extends AstNode = AstNode> = (
node: N,
ctx: CodeActionProviderContext,
) => readonly CodeAction[]
40 changes: 40 additions & 0 deletions packages/core/src/processor/codeActions/builtin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { AstNode } from '../../node/index.js'
import { FileNode } from '../../node/index.js'
import type { MetaRegistry } from '../../service/index.js'
import { Range } from '../../source/index.js'
import { traversePreOrder } from '../util.js'
import type { CodeAction, CodeActionProvider } from './CodeAction.js'

export const fallback: CodeActionProvider<AstNode> = (node, ctx) => {
const ans: CodeAction[] = []
traversePreOrder(
node,
(node) => Range.containsRange(node.range, ctx.range, true),
(node) => ctx.meta.hasCodeActionProvider(node.type),
(node) => ans.push(...ctx.meta.getCodeActionProvider(node.type)(node, ctx)),
)
return ans
}

export const file: CodeActionProvider<FileNode<AstNode>> = (node, ctx) => {
const ans: CodeAction[] = []
for (const error of FileNode.getErrors(node)) {
const action = error.info?.codeAction
if (!action) {
continue
}
if (!Range.containsRange(error.range, ctx.range, true)) {
continue
}
ans.push({
title: action.title,
isPreferred: action.isPreferred,
errors: [error],
})
}
return ans
}

export function registerProviders(meta: MetaRegistry) {
meta.registerCodeActionProvider<FileNode<AstNode>>('file', file)
}
2 changes: 2 additions & 0 deletions packages/core/src/processor/codeActions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * as codeActions from './builtin.js'
export * from './CodeAction.js'
1 change: 1 addition & 0 deletions packages/core/src/processor/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './binder/index.js'
export * from './checker/index.js'
export * from './codeActions/index.js'
export * from './ColorInfoProvider.js'
export * from './colorizer/index.js'
export * from './completer/index.js'
Expand Down
18 changes: 15 additions & 3 deletions packages/core/src/processor/linter/builtin/undeclaredSymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { Arrayable, ResourceLocation } from '../../../common/index.js'
import type { AstNode } from '../../../node/index.js'
import type { LinterContext } from '../../../service/index.js'
import { LinterSeverity, SymbolLinterConfig as Config } from '../../../service/index.js'
import type { Symbol } from '../../../symbol/index.js'
import { SymbolUtil, SymbolVisibility } from '../../../symbol/index.js'
import type { LanguageErrorInfo } from '../../../source/LanguageError.js'
import type { FileCategory, Symbol } from '../../../symbol/index.js'
import { FileCategories, SymbolUtil, SymbolVisibility } from '../../../symbol/index.js'
import type { Linter } from '../Linter.js'

export const undeclaredSymbol: Linter<AstNode> = (node, ctx) => {
Expand All @@ -20,6 +21,17 @@ export const undeclaredSymbol: Linter<AstNode> = (node, ctx) => {
})
}
if (Config.Action.isReport(action)) {
const info = FileCategories.includes(node.symbol.category as FileCategory)
? {
codeAction: {
title: localize(
'code-action.create-undeclared-file',
node.symbol.category,
localeQuote(node.symbol.identifier),
),
},
} satisfies LanguageErrorInfo
: undefined
const severityOverride = action.report === 'inherit'
? undefined
: LinterSeverity.toErrorSeverity(action.report)
Expand All @@ -30,7 +42,7 @@ export const undeclaredSymbol: Linter<AstNode> = (node, ctx) => {
localeQuote(node.symbol.identifier),
),
node,
undefined,
info,
severityOverride,
)
}
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/service/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ export namespace FormatterContext {
}
}

export interface CodeActionProviderContext extends ProcessorContext {
range: Range
}
export interface CodeActionProviderContextOptions extends ProcessorContextOptions {
range: Range
}
export namespace CodeActionProviderContext {
export function create(
project: ProjectData,
opts: CodeActionProviderContextOptions,
): CodeActionProviderContext {
return { ...ProcessorContext.create(project, opts), range: opts.range }
}
}

export interface ColorizerContext extends ProcessorWithRangeContext {}
export interface ColorizerContextOptions extends ProcessorWithRangeContextOptions {}
export namespace ColorizerContext {
Expand Down
26 changes: 25 additions & 1 deletion packages/core/src/service/MetaRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,20 @@ import type { Formatter } from '../processor/formatter/index.js'
import type {
Binder,
Checker,
CodeActionProvider,
Colorizer,
Completer,
InlayHintProvider,
} from '../processor/index.js'
import { binder, checker, colorizer, completer, formatter, linter } from '../processor/index.js'
import {
binder,
checker,
codeActions,
colorizer,
completer,
formatter,
linter,
} from '../processor/index.js'
import type { Linter } from '../processor/linter/Linter.js'
import type { SignatureHelpProvider } from '../processor/SignatureHelpProvider.js'
import type { DependencyKey, DependencyProvider } from './Dependency.js'
Expand Down Expand Up @@ -56,6 +65,7 @@ export class MetaRegistry {
readonly #binders = new Map<string, Binder<any>>()
readonly #checkers = new Map<string, Checker<any>>()
readonly #colorizers = new Map<string, Colorizer<any>>()
readonly #codeActionProviders = new Map<string, CodeActionProvider<any>>()
readonly #completers = new Map<string, Completer<any>>()
readonly #dependencyProviders = new Map<DependencyKey, DependencyProvider>()
readonly #formatters = new Map<string, Formatter<any>>()
Expand All @@ -71,6 +81,7 @@ export class MetaRegistry {
constructor() {
binder.registerBinders(this)
checker.registerCheckers(this)
codeActions.registerProviders(this)
colorizer.registerColorizers(this)
completer.registerCompleters(this)
formatter.registerFormatters(this)
Expand Down Expand Up @@ -144,6 +155,19 @@ export class MetaRegistry {
this.#checkers.set(type, checker)
}

public hasCodeActionProvider<N extends AstNode>(type: N['type']): boolean {
return this.#codeActionProviders.has(type)
}
public getCodeActionProvider<N extends AstNode>(type: N['type']): CodeActionProvider<N> {
return this.#codeActionProviders.get(type) ?? codeActions.fallback
}
public registerCodeActionProvider<N extends AstNode>(
type: N['type'],
codeActionProvider: CodeActionProvider<N>,
): void {
this.#codeActionProviders.set(type, codeActionProvider)
}

public hasColorizer<N extends AstNode>(type: N['type']): boolean {
return this.#colorizers.has(type)
}
Expand Down
23 changes: 22 additions & 1 deletion packages/core/src/service/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@ import type { TextDocument } from 'vscode-languageserver-textdocument'
import type { Logger } from '../common/index.js'
import type { FileNode } from '../node/index.js'
import { AstNode } from '../node/index.js'
import type { Color, ColorInfo, ColorToken, InlayHint, SignatureHelp } from '../processor/index.js'
import type {
CodeAction,
Color,
ColorInfo,
ColorToken,
InlayHint,
SignatureHelp,
} from '../processor/index.js'
import { ColorPresentation, completer, traversePreOrder } from '../processor/index.js'
import { Range } from '../source/index.js'
import type { SymbolLocation, SymbolUsageType } from '../symbol/index.js'
import { SymbolUsageTypes } from '../symbol/index.js'
import {
CodeActionProviderContext,
ColorizerContext,
CompleterContext,
ContextBase,
FormatterContext,
ProcessorContext,
SignatureHelpProviderContext,
Expand Down Expand Up @@ -61,6 +70,18 @@ export class Service {
return []
}

getCodeActions(node: FileNode<AstNode>, doc: TextDocument, range: Range): readonly CodeAction[] {
try {
this.debug(`Getting code actions ${doc.uri} # ${doc.version} @ ${Range.toString(range)}`)
const codeActionProvider = this.project.meta.getCodeActionProvider(node.type)
const ctx = CodeActionProviderContext.create(this.project, { doc, range })
return codeActionProvider(node, ctx)
} catch (e) {
this.logger.error(`[Service] [getCodeActions] Failed for ${doc.uri} # ${doc.version}`, e)
}
return []
}

getColorInfo(node: FileNode<AstNode>, doc: TextDocument): ColorInfo[] {
try {
this.debug(`Getting color info for ${doc.uri} # ${doc.version}`)
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/source/LanguageError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,14 @@ export const enum ErrorSeverity {
}

export interface LanguageErrorInfo {
codeAction?: string
codeAction?: LanguageErrorAction
deprecated?: boolean
unnecessary?: boolean
related?: { location: Location; message: string }[]
}

export interface LanguageErrorAction {
title: string
isPreferred?: boolean
// TODO
}
11 changes: 11 additions & 0 deletions packages/language-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ connection.onInitialize(async (params) => {
const ans: ls.InitializeResult = {
serverInfo: { name: 'Spyglass Language Server' },
capabilities: {
codeActionProvider: {},
colorProvider: {},
completionProvider: { triggerCharacters: service.project.meta.getTriggerCharacters() },
declarationProvider: {},
Expand Down Expand Up @@ -173,6 +174,16 @@ connection.onDidCloseTextDocument(({ textDocument: { uri } }) => {

connection.workspace.onDidRenameFiles(({}) => {})

connection.onCodeAction(async ({ textDocument: { uri }, range }) => {
const docAndNode = await service.project.ensureClientManagedChecked(uri)
if (!docAndNode) {
return undefined
}
const { doc, node } = docAndNode
const codeActions = service.getCodeActions(node, doc, toCore.range(range, doc))
return codeActions.map(a => toLS.codeAction(a, doc))
})

connection.onColorPresentation(async ({ textDocument: { uri }, color, range }) => {
const docAndNode = await service.project.ensureClientManagedChecked(uri)
if (!docAndNode) {
Expand Down
9 changes: 9 additions & 0 deletions packages/language-server/src/util/toLS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,15 @@ export function inlayHints(hints: core.InlayHint[], doc: TextDocument): ls.Inlay
return hints.map((h) => inlayHint(h, doc))
}

export function codeAction(codeAction: core.CodeAction, doc: TextDocument): ls.CodeAction {
return {
title: codeAction.title,
kind: ls.CodeActionKind.QuickFix,
isPreferred: codeAction.isPreferred,
diagnostics: codeAction.errors?.map(e => diagnostic(core.LanguageError.withPosRange(e, doc))),
}
}

export function completionItem(
completion: core.CompletionItem,
doc: TextDocument,
Expand Down
2 changes: 1 addition & 1 deletion packages/locales/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"code-action.fix-workspace": "Fix all auto-fixable problems in the workspace",
"code-action.id-attribute-datafix": "Update this attribute name to 1.16",
"code-action.id-complete-default-namespace": "Complete default namespace",
"code-action.id-create-file": "Create %0% in the same data pack",
"code-action.create-undeclared-file": "Create %0% %1% in the same pack",
"code-action.id-omit-default-namespace": "Omit default namespace",
"code-action.id-zombified-piglin-datafix": "Change this ID to Zombified Piglin's",
"code-action.nbt-compound-sort-keys": "Sort NBT compound tag",
Expand Down

0 comments on commit 3c6da15

Please sign in to comment.