Skip to content

Commit

Permalink
Allow renaming when diagnostics exist
Browse files Browse the repository at this point in the history
  • Loading branch information
nighca committed Jan 8, 2025
1 parent d8156a5 commit e28bfa2
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 24 deletions.
63 changes: 50 additions & 13 deletions spx-gui/src/components/asset/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMessage, useModal } from '@/components/ui'
import { useConfirmDialog, useMessage, useModal } from '@/components/ui'
import { AssetType } from '@/apis/asset'
import { selectAudio, selectFile, selectImg, selectImgs } from '@/utils/file'
import { parseScratchFileAssets } from '@/utils/scratch'
Expand Down Expand Up @@ -195,12 +195,33 @@ export function useAddMonitor(autoSelect = true) {
}
}

// There may be errors in the project code when renaming assets, which may cause some references to be updated incorrectly.
// This function is used to handle errors in the project code when renaming assets: we notify the user to check the code and update the references manually.
// TODO: Recomfirm details here with UX
export function useCodeErrorHandler() {
const i18n = useI18n()
const confirm = useConfirmDialog()
return function handleError(hasError: boolean) {
if (hasError) {
return confirm({
title: i18n.t({ en: 'Name updated', zh: '名字已更新' }),
content: i18n.t({
en: 'There are errors in the project code. Some references may not be updated automatically. You can check the code and update them manually.',
zh: '当前项目代码中存在错误,可能仍有部分引用未能自动更新,你可以检查代码并手动修改。'
})
})
}
}
}

export function useRenameSprite() {
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const handleCodeError = useCodeErrorHandler()
const invokeRenameModal = useModal(RenameModal)
return async function renameSprite(sprite: Sprite) {
return invokeRenameModal({
let hasCodeError = false
await invokeRenameModal({
target: {
name: sprite.name,
validateName(name) {
Expand All @@ -209,22 +230,25 @@ export function useRenameSprite() {
async applyName(newName) {
const action = { name: { en: 'Rename sprite', zh: '重命名精灵' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.renameResource(getResourceIdentifier(sprite), newName)
hasCodeError = (await codeEditorCtx.renameResource(getResourceIdentifier(sprite), newName)).hasError
sprite.setName(newName)
})
},
inputTip: assetName.spriteNameTip
}
})
await handleCodeError(hasCodeError)
}
}

export function useRenameSound() {
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const handleCodeError = useCodeErrorHandler()
const invokeRenameModal = useModal(RenameModal)
return async function renameSound(sound: Sound) {
return invokeRenameModal({
let hasCodeError = false
await invokeRenameModal({
target: {
name: sound.name,
validateName(name) {
Expand All @@ -233,22 +257,25 @@ export function useRenameSound() {
async applyName(newName) {
const action = { name: { en: 'Rename sound', zh: '重命名声音' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.renameResource(getResourceIdentifier(sound), newName)
hasCodeError = (await codeEditorCtx.renameResource(getResourceIdentifier(sound), newName)).hasError
sound.setName(newName)
})
},
inputTip: assetName.soundNameTip
}
})
await handleCodeError(hasCodeError)
}
}

export function useRenameCostume() {
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const handleCodeError = useCodeErrorHandler()
const invokeRenameModal = useModal(RenameModal)
return async function renameCostume(costume: Costume) {
return invokeRenameModal({
let hasCodeError = false
await invokeRenameModal({
target: {
name: costume.name,
validateName(name) {
Expand All @@ -257,22 +284,25 @@ export function useRenameCostume() {
async applyName(newName) {
const action = { name: { en: 'Rename costume', zh: '重命名造型' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.renameResource(getResourceIdentifier(costume), newName)
hasCodeError = (await codeEditorCtx.renameResource(getResourceIdentifier(costume), newName)).hasError
costume.setName(newName)
})
},
inputTip: assetName.costumeNameTip
}
})
await handleCodeError(hasCodeError)
}
}

export function useRenameBackdrop() {
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const handleCodeError = useCodeErrorHandler()
const invokeRenameModal = useModal(RenameModal)
return async function renameBackdrop(backdrop: Backdrop) {
return invokeRenameModal({
let hasCodeError = false
await invokeRenameModal({
target: {
name: backdrop.name,
validateName(name) {
Expand All @@ -281,22 +311,25 @@ export function useRenameBackdrop() {
async applyName(newName) {
const action = { name: { en: 'Rename backdrop', zh: '重命名背景' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.renameResource(getResourceIdentifier(backdrop), newName)
hasCodeError = (await codeEditorCtx.renameResource(getResourceIdentifier(backdrop), newName)).hasError
backdrop.setName(newName)
})
},
inputTip: assetName.backdropNameTip
}
})
await handleCodeError(hasCodeError)
}
}

export function useRenameAnimation() {
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const handleCodeError = useCodeErrorHandler()
const invokeRenameModal = useModal(RenameModal)
return async function renameAnimation(animation: Animation) {
return invokeRenameModal({
let hasCodeError = false
await invokeRenameModal({
target: {
name: animation.name,
validateName(name) {
Expand All @@ -305,22 +338,25 @@ export function useRenameAnimation() {
async applyName(newName) {
const action = { name: { en: 'Rename animation', zh: '重命名动画' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.renameResource(getResourceIdentifier(animation), newName)
hasCodeError = (await codeEditorCtx.renameResource(getResourceIdentifier(animation), newName)).hasError
animation.setName(newName)
})
},
inputTip: assetName.animationNameTip
}
})
await handleCodeError(hasCodeError)
}
}

export function useRenameWidget() {
const editorCtx = useEditorCtx()
const codeEditorCtx = useCodeEditorCtx()
const handleCodeError = useCodeErrorHandler()
const invokeRenameModal = useModal(RenameModal)
return async function renameWidget(widget: Widget) {
return invokeRenameModal({
let hasCodeError = false
await invokeRenameModal({
target: {
name: widget.name,
validateName(name) {
Expand All @@ -329,12 +365,13 @@ export function useRenameWidget() {
async applyName(newName) {
const action = { name: { en: 'Rename widget', zh: '重命名控件' } }
await editorCtx.project.history.doAction(action, async () => {
await codeEditorCtx.renameResource(getResourceIdentifier(widget), newName)
hasCodeError = (await codeEditorCtx.renameResource(getResourceIdentifier(widget), newName)).hasError
widget.setName(newName)
})
},
inputTip: assetName.widgetNameTip
}
})
await handleCodeError(hasCodeError)
}
}
2 changes: 1 addition & 1 deletion spx-gui/src/components/asset/library/AssetAddModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ const handleSubmit = useMessageHandle(
}),
content: t({
en: `The ${assetTypeName} you uploaded [${form.value.name}] is the same as the existing ${assetTypeName} [${assets[0].displayName}] in the asset library. Are you sure you want to add this ${assetTypeName} to the asset library?`,
zh: `您上传的${assetTypeName}「${form.value.name}」与已存在于素材库中的${assetTypeName}「${assets[0].displayName}」内容相同。是否确认需要将此${assetTypeName}添加到素材库中?`
zh: `你上传的${assetTypeName}「${form.value.name}」与已存在于素材库中的${assetTypeName}「${assets[0].displayName}」内容相同。是否确认需要将此${assetTypeName}添加到素材库中?`
})
})
}
Expand Down
10 changes: 8 additions & 2 deletions spx-gui/src/components/editor/code-editor/code-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,10 +481,16 @@ export class CodeEditor extends Disposable {
}

/** Update code for resource renaming, should be called before model name update */
async renameResource(resource: ResourceIdentifier, newName: string) {
async renameResource(resource: ResourceIdentifier, newName: string): Promise<{ hasError: boolean }> {
const diagnosticReport = await this.lspClient.workspaceDiagnostic({ previousResultIds: [] })
const hasError = diagnosticReport.items.some((r) => {
if (r.kind === 'unchanged') return false // For now, we support 'full' reports only
return r.items.some((i) => i.severity === lsp.DiagnosticSeverity.Error)
})
const edit = await this.lspClient.workspaceExecuteCommandSpxRenameResources({ resource, newName })
if (edit == null) return
if (edit == null) return { hasError }
this.applyWorkspaceEdit(edit)
return { hasError }
}

private uis: ICodeEditorUI[] = []
Expand Down
7 changes: 5 additions & 2 deletions spx-gui/src/components/editor/code-editor/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ export type CodeEditorCtx = {
formatWorkspace(): Promise<void>
/** Update code for renaming */
rename(id: TextDocumentIdentifier, position: Position, newName: string): Promise<void>
/** Update code for resource renaming, should be called before model name update */
renameResource(resource: ResourceIdentifier, newName: string): Promise<void>
/**
* Update code for resource renaming, should be called before model name update.
* @returns `hasError` indicates if there is any (diagnostic) error in workspace before renaming.
*/
renameResource(resource: ResourceIdentifier, newName: string): Promise<{ hasError: boolean }>
}

const codeEditorCtxInjectionKey: InjectionKey<CodeEditorCtx> = Symbol('code-editor-ctx')
Expand Down
5 changes: 5 additions & 0 deletions spx-gui/src/components/editor/code-editor/lsp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ export class SpxLSPClient extends Disposable {
return spxlc.request<lsp.DocumentDiagnosticReport>(lsp.DocumentDiagnosticRequest.method, params)
}

async workspaceDiagnostic(params: lsp.WorkspaceDiagnosticParams): Promise<lsp.WorkspaceDiagnosticReport> {
const spxlc = await this.prepareRequest()
return spxlc.request<lsp.WorkspaceDiagnosticReport>(lsp.WorkspaceDiagnosticRequest.method, params)
}

async textDocumentHover(params: lsp.HoverParams): Promise<lsp.Hover | null> {
const spxlc = await this.prepareRequest()
return spxlc.request<lsp.Hover | null>(lsp.HoverRequest.method, params)
Expand Down
10 changes: 9 additions & 1 deletion spx-gui/src/components/ui/UIButton.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<button
ref="btnRef"
class="ui-button"
:class="[`type-${type}`, `size-${size}`, loading && 'loading']"
:disabled="disabled"
Expand All @@ -14,7 +15,7 @@
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { computed, ref } from 'vue'
import UIIcon, { type Type as IconType } from './icons/UIIcon.vue'
export type ButtonType = 'primary' | 'secondary' | 'boring' | 'danger' | 'success'
export type ButtonSize = 'small' | 'medium' | 'large'
Expand All @@ -41,6 +42,13 @@ const props = withDefaults(
const disabled = computed(() => props.disabled || props.loading)
const icon = computed(() => (props.loading ? 'loading' : props.icon))
const btnRef = ref<HTMLButtonElement>()
defineExpose({
focus() {
btnRef.value?.focus()
}
})
</script>

<style lang="scss" scoped>
Expand Down
15 changes: 13 additions & 2 deletions spx-gui/src/components/ui/dialog/UIConfirmDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@
<UIButton type="boring" @click="emit('cancelled')">
{{ cancelText ?? config?.cancelText ?? 'Cancel' }}
</UIButton>
<UIButton type="primary" :loading="isConfirmLoading" @click="handleConfirm">
<UIButton ref="confirmBtnRef" type="primary" :loading="isConfirmLoading" @click="handleConfirm">
{{ confirmText ?? config?.confirmText ?? 'Confirm' }}
</UIButton>
</footer>
</UIDialog>
</template>

<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { onMounted, ref, watch } from 'vue'
import { untilNotNull } from '@/utils/utils'
import { useConfig } from '../UIConfigProvider.vue'
import UIButton from '../UIButton.vue'
import UIDialog from './UIDialog.vue'
Expand Down Expand Up @@ -51,6 +52,16 @@ const emit = defineEmits<{
resolved: []
}>()
const confirmBtnRef = ref<InstanceType<typeof UIButton>>()
watch(
() => props.visible,
async (visible) => {
if (!visible) return
const confirmBtn = await untilNotNull(confirmBtnRef)
confirmBtn.focus()
}
)
const isConfirmLoading = ref(false)
async function handleConfirm() {
Expand Down
3 changes: 0 additions & 3 deletions tools/spxls/internal/server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ func (s *Server) spxRenameResources(params []SpxRenameResourceParams) (*Workspac
}
return nil, fmt.Errorf("failed to compile: %w", err)
}
if result.hasErrorSeverityDiagnostic {
return nil, errors.New("cannot rename spx resources when there are unresolved error severity diagnostics")
}

return s.spxRenameResourcesWithCompileResult(result, params)
}
Expand Down

0 comments on commit e28bfa2

Please sign in to comment.