Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ dist-ssr
*.sln
*.sw?
pnpm-lock.yaml
*.csv
*.json
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# CodeForge
<div align="center">
<img src="public/codeforge.svg" width=100 />

<h1 style="margin-top: -20px;">CodeForge</h1>

CodeForge 是一款轻量级、高性能的桌面代码执行器,专为开发者、学生和编程爱好者设计。
</div>

## 特性

Expand Down
7 changes: 5 additions & 2 deletions src-tauri/src/plugins/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ impl PluginManager {
}

pub fn get_supported_languages(&self) -> Vec<serde_json::Value> {
self.plugins
.iter()
let mut plugins: Vec<_> = self.plugins.iter().collect();
plugins.sort_by_key(|(_, plugin)| plugin.get_order());

plugins
.into_iter()
.map(|(key, plugin)| {
serde_json::json!({
"name": plugin.get_language_name(),
Expand Down
3 changes: 3 additions & 0 deletions src-tauri/src/plugins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ pub struct LanguageInfo {

// 语言插件接口
pub trait LanguagePlugin: Send + Sync {
fn get_order(&self) -> i32 {
0
}
fn get_language_name(&self) -> &'static str;
fn get_file_extension(&self) -> &'static str;
fn get_commands(&self) -> Vec<&'static str>;
Expand Down
12 changes: 5 additions & 7 deletions src-tauri/src/plugins/python2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use super::{ExecutionResult, LanguagePlugin};
pub struct Python2Plugin;

impl LanguagePlugin for Python2Plugin {
fn get_order(&self) -> i32 {
1
}

fn get_language_name(&self) -> &'static str {
"Python 2"
}
Expand All @@ -28,13 +32,7 @@ impl LanguagePlugin for Python2Plugin {
}

fn pre_execute_hook(&self, code: &str) -> Result<String, String> {
// 添加一些 Python 特定的预处理
let processed_code = format!(
"# CodeForge Python Execution\n# Generated at: {}\n\n{}",
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"),
code
);
Ok(processed_code)
Ok(code.to_string())
}

fn post_execute_hook(&self, result: &mut ExecutionResult) -> Result<(), String> {
Expand Down
12 changes: 5 additions & 7 deletions src-tauri/src/plugins/python3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use super::{ExecutionResult, LanguagePlugin};
pub struct Python3Plugin;

impl LanguagePlugin for Python3Plugin {
fn get_order(&self) -> i32 {
2
}

fn get_language_name(&self) -> &'static str {
"Python 3"
}
Expand All @@ -28,13 +32,7 @@ impl LanguagePlugin for Python3Plugin {
}

fn pre_execute_hook(&self, code: &str) -> Result<String, String> {
// 添加一些 Python 特定的预处理
let processed_code = format!(
"# CodeForge Python Execution\n# Generated at: {}\n\n{}",
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"),
code
);
Ok(processed_code)
Ok(code.to_string())
}

fn post_execute_hook(&self, result: &mut ExecutionResult) -> Result<(), String> {
Expand Down
8 changes: 5 additions & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@

<div class="flex-1 flex overflow-hidden">
<!-- 代码编辑器 -->
<div class="flex-1 flex flex-col">
<div class="bg-gray-100 px-4 py-2 border-b border-gray-200 flex items-center justify-between">
<div class="flex-1 flex flex-col overflow-hidden">
<div class="bg-gray-100 px-4 py-2 border-b border-gray-200 flex items-center justify-between flex-shrink-0">
<h2 class="text-sm font-medium text-gray-700">{{ getLanguageDisplayName(currentLanguage) }} 代码编辑器</h2>
<div class="text-xs text-gray-500">
<strong>{{ code.length }}</strong> 字符, <strong>{{ code.split('\n').length }}</strong> 行
</div>
</div>
<CodeEditor v-model="code" class="flex-1" :language="currentLanguage"/>
<div class="flex-1 overflow-hidden">
<CodeEditor v-model="code" class="h-full" :language="currentLanguage"/>
</div>
</div>

<!-- 输出 -->
Expand Down
45 changes: 33 additions & 12 deletions src/components/CodeEditor.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
<template>
<div class="flex bg-white h-full">
<div class="flex bg-white h-full relative overflow-hidden">
<!-- 行号 -->
<div ref="lineNumbersRef"
class="bg-gray-50 text-gray-400 text-sm font-mono py-4 px-3 select-none border-r border-gray-200 overflow-hidden">
class="bg-gray-50 text-gray-400 text-sm font-mono px-3 pb-4 select-none border-r border-gray-200 overflow-hidden flex-shrink-0 z-10"
style="padding-top: 0;">
<div v-for="(num, index) in lineNumbers" :key="index" class="h-6 leading-6 text-right">
{{ num }}
</div>
</div>

<!-- 代码输入框 -->
<textarea ref="textareaRef"
:value="modelValue"
@input="handleInput"
@keydown="handleKeyDown"
@scroll="handleScroll"
class="flex-1 p-4 font-mono text-sm leading-6 resize-none outline-none bg-white"
placeholder="在此输入代码..."
spellcheck="false">
</textarea>
<!-- 语法高亮容器 -->
<div class="flex-1 relative overflow-hidden">
<!-- 高亮显示层 -->
<pre ref="highlightRef"
class="absolute inset-0 px-4 pb-4 font-mono text-sm leading-6 bg-transparent pointer-events-none overflow-auto whitespace-pre-wrap z-0"
style="margin: 0; border: 0; padding-top: 0; word-break: break-word; white-space: pre-wrap;"
v-html="highlightedCode"></pre>

<!-- 代码输入框 -->
<textarea ref="textareaRef"
:value="modelValue"
@input="handleInput"
@keydown="handleKeyDown"
@scroll="handleScroll"
class="absolute inset-0 px-4 pb-4 font-mono text-sm leading-6 resize-none outline-none bg-transparent z-10 overflow-auto"
style="color: transparent; caret-color: #374151; margin: 0; border: 0; padding-top: 0; word-break: break-word; white-space: pre-wrap;"
placeholder="在此输入代码..."
spellcheck="false">
</textarea>
</div>
</div>
</template>

<script setup lang="ts">
import { computed, nextTick, ref } from 'vue'
import { highlightCode } from '../utils/highlighter'

const props = defineProps<{
modelValue: string
Expand All @@ -35,12 +47,17 @@ const emit = defineEmits<{

const textareaRef = ref<HTMLTextAreaElement>()
const lineNumbersRef = ref<HTMLElement>()
const highlightRef = ref<HTMLPreElement>()

const lineNumbers = computed(() => {
const lines = props.modelValue.split('\n')
return lines.map((_, index) => String(index + 1))
})

const highlightedCode = computed(() => {
return highlightCode(props.modelValue, props.language || 'python3')
})

const handleInput = (e: Event) => {
const target = e.target as HTMLTextAreaElement
emit('update:modelValue', target.value)
Expand All @@ -65,5 +82,9 @@ const handleScroll = (e: Event) => {
if (lineNumbersRef.value) {
lineNumbersRef.value.scrollTop = target.scrollTop
}
if (highlightRef.value) {
highlightRef.value.scrollTop = target.scrollTop
highlightRef.value.scrollLeft = target.scrollLeft
}
}
</script>
69 changes: 62 additions & 7 deletions src/components/OutputPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,23 @@
<div class="p-2 border-b border-gray-700 flex items-center justify-between">
<div class="flex items-center space-x-2">
<Terminal class="w-4 h-4"/>
<span class="text-sm font-medium">输出</span>
<span class="text-sm font-medium">控制台</span>
</div>
<div v-if="executionTime > 0" class="text-xs text-gray-400 flex items-center space-x-1">
<Clock class="w-3 h-3"/>
<span>{{ executionTime }} 毫秒</span>
<div class="flex items-center space-x-3">
<span v-if="isCopied" class="text-xs text-gray-400">{{ isCopied ? '已复制' : '复制失败' }}</span>

<!-- 复制按钮 -->
<button v-if="output && !isRunning"
@click="copyOutput"
class="text-gray-400 hover:text-white transition-colors duration-200 p-1 rounded hover:bg-gray-700 cursor-pointer"
title="复制输出内容">
<component :is="copyIcon" class="w-3 h-3"/>
</button>

<div v-if="executionTime > 0" class="text-xs text-gray-400 flex items-center space-x-1">
<Clock class="w-3 h-3"/>
<span>{{ executionTime }} 毫秒</span>
</div>
</div>
</div>

Expand All @@ -21,7 +33,7 @@
<pre :class="['whitespace-pre-wrap text-sm leading-relaxed', isSuccess ? 'text-green-300' : 'text-red-300']">{{ output }}</pre>
</div>

<div v-else class="p-4 text-gray-500 flex flex-col items-center justify-center h-full space-y-2">
<div v-else class="p-4 text-gray-500 flex flex-col items-center justify-center h-full space-y-2 select-none">
<Terminal class="w-8 h-8"/>
<p class="text-sm">没有输出</p>
<p class="text-xs">可以尝试运行一些代码</p>
Expand All @@ -31,12 +43,55 @@
</template>

<script setup lang="ts">
import { Clock, Loader, Terminal } from 'lucide-vue-next'
import { computed, ref } from 'vue'
import { Check, Clock, Copy, Loader, Terminal } from 'lucide-vue-next'

defineProps<{
const props = defineProps<{
output: string
isRunning: boolean
isSuccess: boolean
executionTime: number
}>()

const isCopied = ref(false)

// 动态切换图标
const copyIcon = computed(() => isCopied.value ? Check : Copy)

const copyOutput = async () => {
if (!props.output) {
return
}

try {
await navigator.clipboard.writeText(props.output)
isCopied.value = true

// 2秒后恢复复制图标
setTimeout(() => {
isCopied.value = false
}, 2000)
}
catch (error) {
console.error('复制失败:', error)

// 降级方案:使用传统方法复制
try {
const textArea = document.createElement('textarea')
textArea.value = props.output
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)

isCopied.value = true
setTimeout(() => {
isCopied.value = false
}, 2000)
}
catch (fallbackError) {
console.error('降级复制也失败了:', fallbackError)
}
}
}
</script>
2 changes: 1 addition & 1 deletion src/components/StatusBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<component :is="envInfo.installed ? CheckCircle : XCircle"
:class="envInfo.installed ? 'text-green-300' : 'text-white'"
class="w-4 h-4"/>
<span>{{ envInfo.installed ? `${ envInfo.language }: ${ envInfo.version }` : `${ envInfo.language } 环境未安装` }}</span>
<span>{{ envInfo.installed ? `${ envInfo.language }: ${ envInfo.version }` : `${ envInfo.language }: 环境未安装` }}</span>
</div>

<div v-if="executionTime > 0" class="flex items-center space-x-2">
Expand Down
37 changes: 37 additions & 0 deletions src/utils/highlighter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export * from './types'
export * from './manager'
export { Python2Highlighter } from './languages/python2'
export { Python3Highlighter } from './languages/python3'

// 导入并重新导出管理器实例
import { HighlightManager } from './manager'

// 创建单例实例
export const highlightManager = new HighlightManager()

/**
* 高亮代码的便捷函数
* @param code 源代码
* @param language 语言名称
* @returns 高亮后的 HTML
*/
export function highlightCode(code: string, language: string): string
{
return highlightManager.highlight(code, language)
}

/**
* 获取支持的语言列表
*/
export function getSupportedLanguages()
{
return highlightManager.getSupportedLanguages()
}

/**
* 注册自定义高亮器
*/
export function registerHighlighter(highlighter: import('./types').LanguageHighlighter)
{
highlightManager.register(highlighter)
}
Loading
Loading