Enhance RsCodeMirror Component with Quick Dev Features and Improved Formatting
- Added quick development features including toggles for line numbers, word wrap, and active line highlighting to enhance the coding experience. - Implemented search functionality with keyboard shortcuts for improved code navigation and editing. - Enhanced JSON formatting capabilities to handle JSON code without relying on Prettier, ensuring better performance for JSON files. - Updated the component's template and styles to accommodate new features, including a responsive layout for quick dev tools. - Improved cursor position tracking and display for better user feedback during code editing.
This commit is contained in:
parent
4b23da5239
commit
8d6184fd8b
@ -11,6 +11,9 @@ import { amy, ayuLight, barf, clouds, cobalt, dracula } from "thememirror";
|
||||
import { autocompletion } from "@codemirror/autocomplete";
|
||||
import { indentUnit } from "@codemirror/language";
|
||||
import { indentOnInput } from "@codemirror/language";
|
||||
import { EditorView } from "@codemirror/view";
|
||||
import { searchKeymap, search } from "@codemirror/search";
|
||||
import { keymap } from "@codemirror/view";
|
||||
import { useDebounceFn } from "@vueuse/core";
|
||||
|
||||
// Dynamically import Prettier and its plugins
|
||||
@ -90,6 +93,12 @@ const dropdownThemes = ref([
|
||||
const value = ref(props.modelValue);
|
||||
const extensions = ref([]);
|
||||
|
||||
// Quick dev features
|
||||
const showLineNumbers = ref(true);
|
||||
const enableWordWrap = ref(false);
|
||||
const highlightActiveLine = ref(true);
|
||||
const enableSearch = ref(true);
|
||||
|
||||
// Helper function to get theme extension
|
||||
const getThemeExtension = (themeVal) => {
|
||||
switch (themeVal) {
|
||||
@ -121,6 +130,8 @@ const getLanguageExtension = () => {
|
||||
return css();
|
||||
case "html":
|
||||
return css(); // Use CSS for HTML highlighting
|
||||
case "json":
|
||||
return javascript();
|
||||
case "javascript":
|
||||
case "js":
|
||||
default:
|
||||
@ -134,13 +145,49 @@ const initializeExtensions = () => {
|
||||
const themeExtension = getThemeExtension(currentTheme);
|
||||
const languageExtension = getLanguageExtension();
|
||||
|
||||
extensions.value = [
|
||||
const baseExtensions = [
|
||||
languageExtension,
|
||||
themeExtension,
|
||||
autocompletion(),
|
||||
indentUnit.of(" "),
|
||||
indentOnInput(),
|
||||
];
|
||||
|
||||
// Don't add lineNumbers() extension - vue-codemirror already has them
|
||||
// We'll control visibility via CSS instead
|
||||
|
||||
if (enableWordWrap.value) {
|
||||
baseExtensions.push(EditorView.lineWrapping);
|
||||
}
|
||||
|
||||
if (highlightActiveLine.value) {
|
||||
baseExtensions.push(EditorView.theme({
|
||||
'.cm-activeLine': { backgroundColor: 'rgba(255, 255, 255, 0.1)' },
|
||||
'.cm-focused .cm-activeLine': { backgroundColor: 'rgba(255, 255, 255, 0.1)' }
|
||||
}));
|
||||
}
|
||||
|
||||
if (enableSearch.value) {
|
||||
baseExtensions.push(search());
|
||||
baseExtensions.push(keymap.of(searchKeymap));
|
||||
}
|
||||
|
||||
extensions.value = baseExtensions;
|
||||
};
|
||||
|
||||
// Toggle functions
|
||||
const toggleLineNumbers = () => {
|
||||
showLineNumbers.value = !showLineNumbers.value;
|
||||
};
|
||||
|
||||
const toggleWordWrap = () => {
|
||||
enableWordWrap.value = !enableWordWrap.value;
|
||||
initializeExtensions();
|
||||
};
|
||||
|
||||
const toggleActiveLine = () => {
|
||||
highlightActiveLine.value = !highlightActiveLine.value;
|
||||
initializeExtensions();
|
||||
};
|
||||
|
||||
// Initialize extensions on component creation
|
||||
@ -148,6 +195,7 @@ initializeExtensions();
|
||||
|
||||
const totalLines = ref(0);
|
||||
const totalLength = ref(0);
|
||||
const cursorPosition = ref({ line: 1, column: 1 });
|
||||
|
||||
// Codemirror EditorView instance ref
|
||||
const view = shallowRef();
|
||||
@ -155,6 +203,23 @@ const handleReady = (payload) => {
|
||||
view.value = payload.view;
|
||||
totalLines.value = view.value.state.doc.lines;
|
||||
totalLength.value = view.value.state.doc.length;
|
||||
|
||||
// Track cursor position
|
||||
const updateCursor = () => {
|
||||
if (view.value) {
|
||||
const state = view.value.state;
|
||||
const pos = state.selection.main.head;
|
||||
const line = state.doc.lineAt(pos);
|
||||
cursorPosition.value = {
|
||||
line: line.number,
|
||||
column: pos - line.from + 1
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
view.value.dom.addEventListener('click', updateCursor);
|
||||
view.value.dom.addEventListener('keyup', updateCursor);
|
||||
updateCursor();
|
||||
};
|
||||
|
||||
// Watch for theme changes
|
||||
@ -162,16 +227,7 @@ watch(
|
||||
() => props.theme || editorTheme.value,
|
||||
(newTheme) => {
|
||||
if (newTheme) {
|
||||
const themeExtension = getThemeExtension(newTheme);
|
||||
const languageExtension = getLanguageExtension();
|
||||
|
||||
extensions.value = [
|
||||
languageExtension,
|
||||
themeExtension,
|
||||
autocompletion(),
|
||||
indentUnit.of(" "),
|
||||
indentOnInput(),
|
||||
];
|
||||
initializeExtensions();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
@ -190,17 +246,7 @@ watch(
|
||||
() => editorTheme.value,
|
||||
(newTheme) => {
|
||||
if (!props.theme && newTheme) {
|
||||
// Only update if no explicit theme prop is provided
|
||||
const themeExtension = getThemeExtension(newTheme);
|
||||
const languageExtension = getLanguageExtension();
|
||||
|
||||
extensions.value = [
|
||||
languageExtension,
|
||||
themeExtension,
|
||||
autocompletion(),
|
||||
indentUnit.of(" "),
|
||||
indentOnInput(),
|
||||
];
|
||||
initializeExtensions();
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -253,19 +299,53 @@ const loadPrettier = async () => {
|
||||
|
||||
// Function Format Code
|
||||
const formatCode = async (code) => {
|
||||
// Handle JSON formatting without Prettier
|
||||
if (props.language === 'json') {
|
||||
try {
|
||||
const parsed = JSON.parse(code);
|
||||
return JSON.stringify(parsed, null, 2);
|
||||
} catch (error) {
|
||||
console.error('JSON formatting error:', error);
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
// Use Prettier for other languages
|
||||
await loadPrettier();
|
||||
try {
|
||||
let parser = "babel";
|
||||
let plugins = [parserBabel.value];
|
||||
|
||||
switch (props.language) {
|
||||
case "vue":
|
||||
parser = "vue";
|
||||
plugins = [parserHTML.value, parserBabel.value, parserPostCSS.value, pluginVue.value];
|
||||
break;
|
||||
case "css":
|
||||
case "scss":
|
||||
parser = "css";
|
||||
plugins = [parserPostCSS.value];
|
||||
break;
|
||||
case "html":
|
||||
parser = "html";
|
||||
plugins = [parserHTML.value];
|
||||
break;
|
||||
case "javascript":
|
||||
case "js":
|
||||
default:
|
||||
parser = "babel";
|
||||
plugins = [parserBabel.value];
|
||||
break;
|
||||
}
|
||||
|
||||
const formattedCode = await prettier.value.format(code, {
|
||||
parser: "vue",
|
||||
plugins: [
|
||||
parserHTML.value,
|
||||
parserBabel.value,
|
||||
parserPostCSS.value,
|
||||
pluginVue.value,
|
||||
],
|
||||
parser: parser,
|
||||
plugins: plugins,
|
||||
semi: false,
|
||||
singleQuote: true,
|
||||
trailingComma: "es5",
|
||||
printWidth: 80,
|
||||
tabWidth: 2,
|
||||
});
|
||||
return formattedCode;
|
||||
} catch (error) {
|
||||
@ -280,8 +360,12 @@ const formatCurrentCode = async () => {
|
||||
value.value = formattedCode;
|
||||
emits("update:modelValue", formattedCode);
|
||||
emits("format-code");
|
||||
|
||||
// Show success message based on language
|
||||
const langName = props.language === 'json' ? 'JSON' : props.language.toUpperCase();
|
||||
console.log(`${langName} formatted successfully`);
|
||||
} catch (error) {
|
||||
console.log("Error formatting code:", error);
|
||||
console.error("Error formatting code:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -364,7 +448,8 @@ watch(
|
||||
props.class,
|
||||
{
|
||||
'fullscreen-editor': isFullscreen,
|
||||
'fixed inset-0 z-[9999] bg-[#282C34]': isFullscreen
|
||||
'fixed inset-0 z-[9999] bg-[#282C34]': isFullscreen,
|
||||
'hide-line-numbers': !showLineNumbers
|
||||
}
|
||||
]"
|
||||
>
|
||||
@ -387,6 +472,35 @@ watch(
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Quick dev tools -->
|
||||
<div class="flex items-center gap-1 ml-3 border-l border-gray-600 pl-3">
|
||||
<button
|
||||
@click="toggleLineNumbers"
|
||||
:title="showLineNumbers ? 'Hide line numbers' : 'Show line numbers'"
|
||||
class="dev-toggle"
|
||||
:class="showLineNumbers ? 'active' : 'inactive'"
|
||||
>
|
||||
#
|
||||
</button>
|
||||
<button
|
||||
@click="toggleWordWrap"
|
||||
:title="enableWordWrap ? 'Disable word wrap' : 'Enable word wrap'"
|
||||
class="dev-toggle"
|
||||
:class="enableWordWrap ? 'active' : 'inactive'"
|
||||
>
|
||||
↩
|
||||
</button>
|
||||
<button
|
||||
@click="toggleActiveLine"
|
||||
:title="highlightActiveLine ? 'Disable line highlight' : 'Enable line highlight'"
|
||||
class="dev-toggle"
|
||||
:class="highlightActiveLine ? 'active' : 'inactive'"
|
||||
>
|
||||
▬
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="isFullscreen" class="text-sm text-gray-300 ml-4">
|
||||
<Icon name="material-symbols:info-outline" class="w-4 h-4 inline mr-1" />
|
||||
Press <kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded">Esc</kbd> or
|
||||
@ -408,9 +522,10 @@ watch(
|
||||
<rs-button
|
||||
@click="formatCurrentCode"
|
||||
class="px-3 py-1 bg-blue-600 hover:bg-blue-500 text-sm rounded transition-colors"
|
||||
:title="getFormatButtonTitle()"
|
||||
>
|
||||
<Icon name="vscode-icons:file-type-prettier" class="!w-5 !h-5 mr-2" />
|
||||
Format Code (Shift + Alt + F)
|
||||
<Icon :name="getFormatButtonIcon()" class="!w-5 !h-5 mr-2" />
|
||||
{{ getFormatButtonText() }}
|
||||
</rs-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -438,16 +553,19 @@ watch(
|
||||
class="footer flex justify-between items-center gap-2 p-2 bg-[#282C34] text-[#abb2bf] border-t border-gray-600"
|
||||
>
|
||||
<div class="flex items-center gap-4 text-sm">
|
||||
<span v-if="isFullscreen" class="text-gray-300">
|
||||
<div class="text-gray-300 text-xs">
|
||||
<Icon name="material-symbols:keyboard" class="w-4 h-4 inline mr-1" />
|
||||
Shortcuts:
|
||||
<kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded mx-1">Ctrl+Shift+F</kbd> Format,
|
||||
<kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded mx-1">F11</kbd> Toggle Fullscreen
|
||||
</span>
|
||||
<kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded mx-0.5">Ctrl+F</kbd> Search,
|
||||
<kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded mx-0.5">Ctrl+H</kbd> Replace,
|
||||
<kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded mx-0.5">Shift+Alt+F</kbd> Format
|
||||
<span v-if="isFullscreen">, <kbd class="px-1 py-0.5 text-xs bg-gray-700 rounded mx-0.5">F11</kbd> Exit Fullscreen</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="">Lines: {{ numberComma(totalLines) }}</span>
|
||||
<span class="">Length: {{ numberComma(totalLength) }}</span>
|
||||
<div class="flex items-center gap-4 text-sm">
|
||||
<span>Lines: {{ numberComma(totalLines) }}</span>
|
||||
<span>Length: {{ numberComma(totalLength) }}</span>
|
||||
<span class="text-gray-400">Ln {{ cursorPosition.line }}, Col {{ cursorPosition.column }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -536,6 +654,37 @@ button:focus {
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* Hide line numbers when toggle is off */
|
||||
.hide-line-numbers :deep(.cm-lineNumbers) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide-line-numbers :deep(.cm-gutters) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Adjust content when line numbers are hidden */
|
||||
.hide-line-numbers :deep(.cm-content) {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
/* Quick dev tools styling */
|
||||
.dev-toggle {
|
||||
@apply px-2 py-1 text-xs rounded transition-colors;
|
||||
}
|
||||
|
||||
.dev-toggle.active {
|
||||
@apply bg-blue-600 text-white;
|
||||
}
|
||||
|
||||
.dev-toggle.inactive {
|
||||
@apply bg-gray-600 text-gray-300;
|
||||
}
|
||||
|
||||
.dev-toggle.inactive:hover {
|
||||
@apply bg-gray-500;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user