用python内置库做的html编译器(有点小bug)
import tkinter as tkfrom tkinter import scrolledtext, filedialog, messagebox
import re
import tempfile
import webbrowser
import os
class HTMLCompiler:
def __init__(self, root):
self.root = root
self.root.title("HTML 编译器")
self.root.configure(bg='black')
self.current_file = None
# 创建菜单栏
self.menu_bar = tk.Menu(root)
# 文件菜单
self.file_menu = tk.Menu(self.menu_bar, tearoff=0)
self.file_menu.add_command(label="新建", command=self.new_file, accelerator="Ctrl+N")
self.file_menu.add_command(label="打开", command=self.open_file, accelerator="Ctrl+O")
self.file_menu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
self.file_menu.add_command(label="另存为", command=self.save_as_file)
self.file_menu.add_separator()
self.file_menu.add_command(label="退出", command=self.root.quit)
self.menu_bar.add_cascade(label="文件", menu=self.file_menu)
# 运行菜单
self.run_menu = tk.Menu(self.menu_bar, tearoff=0)
self.run_menu.add_command(label="运行HTML", command=self.run_html, accelerator="F5")
self.menu_bar.add_cascade(label="运行", menu=self.run_menu)
self.root.config(menu=self.menu_bar)
# 创建工具栏
self.toolbar = tk.Frame(root, bg='#333')
self.new_btn = tk.Button(self.toolbar, text="新建", command=self.new_file, bg='#333', fg='white')
self.open_btn = tk.Button(self.toolbar, text="打开", command=self.open_file, bg='#333', fg='white')
self.save_btn = tk.Button(self.toolbar, text="保存", command=self.save_file, bg='#333', fg='white')
self.run_btn = tk.Button(self.toolbar, text="运行", command=self.run_html, bg='#333', fg='white')
self.new_btn.pack(side=tk.LEFT, padx=2, pady=2)
self.open_btn.pack(side=tk.LEFT, padx=2, pady=2)
self.save_btn.pack(side=tk.LEFT, padx=2, pady=2)
self.run_btn.pack(side=tk.LEFT, padx=2, pady=2)
self.toolbar.pack(fill=tk.X)
# 创建文本编辑器
self.text_area = scrolledtext.ScrolledText(
root, wrap=tk.WORD, bg='black', fg='white',
insertbackground='white', font=('Consolas', 12)
)
self.text_area.pack(fill=tk.BOTH, expand=True)
# 绑定事件
self.text_area.bind('<KeyRelease>', self.on_key_release)
self.text_area.bind('<Tab>', self.handle_tab)
self.text_area.bind('<Return>', self.handle_return)
self.text_area.bind('<KeyPress>', self.handle_key_press)
# 绑定快捷键
self.root.bind('<Control-n>', lambda event: self.new_file())
self.root.bind('<Control-o>', lambda event: self.open_file())
self.root.bind('<Control-s>', lambda event: self.save_file())
self.root.bind('<F5>', lambda event: self.run_html())
# 自动补全相关
self.autocomplete_window = None
self.autocomplete_listbox = None
self.autocomplete_pos = None
self.autocomplete_words = [
'<html>', '</html>', '<head>', '</head>', '<body>', '</body>',
'<title>', '</title>', '<div>', '</div>', '<p>', '</p>',
'<h1>', '</h1>', '<h2>', '</h2>', '<h3>', '</h3>',
'<a>', '</a>', '<img>', '<ul>', '</ul>', '<li>', '</li>',
'<table>', '</table>', '<tr>', '</tr>', '<td>', '</td>',
'<style>', '</style>', '<script>', '</script>', '<span>', '</span>',
'<br>', '<hr>', '<input>', '<button>', '</button>', '<form>', '</form>'
]
# 语法高亮颜色配置
self.tag_colors = {
'tag': '#569CD6',
'attribute': '#9CDCFE',
'value': '#CE9178',
'comment': '#6A9955',
'doctype': '#569CD6',
'string': '#CE9178',
'symbol': '#D4D4D4'
}
# 配置标签样式
for tag, color in self.tag_colors.items():
self.text_area.tag_config(tag, foreground=color)
# 初始高亮
self.highlight()
# 新建一个空白文件
self.new_file()
# 文件操作功能
def new_file(self, event=None):
self.text_area.delete('1.0', tk.END)
self.current_file = None
self.root.title("HTML 编译器 - 未命名")
def open_file(self, event=None):
file_path = filedialog.askopenfilename(
filetypes=[("HTML 文件", "*.html"), ("所有文件", "*.*")]
)
if file_path:
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
self.text_area.delete('1.0', tk.END)
self.text_area.insert('1.0', content)
self.current_file = file_path
self.root.title(f"HTML 编译器 - {os.path.basename(file_path)}")
self.highlight()
except Exception as e:
messagebox.showerror("错误", f"无法打开文件:\n{str(e)}")
def save_file(self, event=None):
if self.current_file:
try:
with open(self.current_file, 'w', encoding='utf-8') as file:
file.write(self.text_area.get('1.0', tk.END))
messagebox.showinfo("保存", "文件保存成功!")
except Exception as e:
messagebox.showerror("错误", f"无法保存文件:\n{str(e)}")
else:
self.save_as_file()
def save_as_file(self):
file_path = filedialog.asksaveasfilename(
defaultextension=".html",
filetypes=[("HTML 文件", "*.html"), ("所有文件", "*.*")]
)
if file_path:
try:
with open(file_path, 'w', encoding='utf-8') as file:
file.write(self.text_area.get('1.0', tk.END))
self.current_file = file_path
self.root.title(f"HTML 编译器 - {os.path.basename(file_path)}")
messagebox.showinfo("保存", "文件保存成功!")
except Exception as e:
messagebox.showerror("错误", f"无法保存文件:\n{str(e)}")
def run_html(self, event=None):
html_code = self.text_area.get("1.0", tk.END)
if not html_code.strip():
messagebox.showwarning("警告", "没有内容可运行!")
return
# 创建临时HTML文件
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.html') as f:
f.write(html_code)
temp_path = f.name
# 用默认浏览器打开
try:
webbrowser.open('file://' + os.path.abspath(temp_path))
except Exception as e:
messagebox.showerror("错误", f"无法运行HTML:\n{str(e)}")
# 以下是原有的编辑器功能(自动补全、语法高亮等)
def on_key_release(self, event):
self.highlight()
self.check_autocomplete(event)
def handle_key_press(self, event):
if event.keysym == 'Escape' and self.autocomplete_window:
self.autocomplete_window.destroy()
self.autocomplete_window = None
return 'break'
if self.autocomplete_window and event.keysym in ('Return', 'Tab', 'Down', 'Up'):
return self.handle_autocomplete_key(event)
if event.keysym == 'BackSpace':
self.handle_backspace(event)
def handle_backspace(self, event):
current_pos = self.text_area.index(tk.INSERT)
line, col = map(int, current_pos.split('.'))
line_text = self.text_area.get(f'{line}.0', f'{line}.end')
if col > 0 and line_text[:col].isspace():
prev_non_space = col - 1
while prev_non_space >= 0 and line_text.isspace():
prev_non_space -= 1
if prev_non_space == -1:
space_count = col
if space_count % 4 != 0:
new_col = (space_count // 4) * 4
self.text_area.delete(f'{line}.{new_col}', current_pos)
return 'break'
return None
def handle_tab(self, event):
current_pos = self.text_area.index(tk.INSERT)
line, col = map(int, current_pos.split('.'))
space_count = 4 - (col % 4)
self.text_area.insert(tk.INSERT, ' ' * space_count)
return 'break'
def handle_return(self, event):
current_pos = self.text_area.index(tk.INSERT)
line, col = map(int, current_pos.split('.'))
line_text = self.text_area.get(f'{line}.0', f'{line}.end')
indent = 0
while indent < len(line_text) and line_text == ' ':
indent += 1
self.text_area.insert(tk.INSERT, '\n' + ' ' * indent)
if line_text.strip().startswith('<') and not line_text.strip().startswith('</'):
match = re.match(r'^\s*<(+)[^>]*>', line_text)
if match and not line_text.strip().endswith('/>'):
tag = match.group(1)
self.text_area.insert(tk.INSERT, f'\n{" " * indent}</{tag}>')
self.text_area.mark_set(tk.INSERT, f'{line+1}.{indent+1}')
return 'break'
def check_autocomplete(self, event):
if self.autocomplete_window:
self.autocomplete_window.destroy()
self.autocomplete_window = None
current_pos = self.text_area.index(tk.INSERT)
line, col = map(int, current_pos.split('.'))
line_text = self.text_area.get(f'{line}.0', current_pos)
if line_text and (line_text[-1].isalpha() or line_text[-1] == '<' or line_text[-1] == '/'):
word_start = max(line_text.rfind(' '), line_text.rfind('<'), line_text.rfind('>'), line_text.rfind('"')) + 1
current_word = line_text
matches =
if matches:
self.show_autocomplete(matches, current_pos, word_start, col)
def show_autocomplete(self, matches, current_pos, word_start, col):
line, _ = map(int, current_pos.split('.'))
self.autocomplete_window = tk.Toplevel(self.root)
self.autocomplete_window.wm_overrideredirect(True)
self.autocomplete_window.wm_geometry("+%d+%d" % (
self.root.winfo_rootx() + self.text_area.winfo_x() +
self.text_area.tk.call(self.text_area._w, 'index', f'{line}.{word_start}',
'xpixels', 'ypixels'),
self.root.winfo_rooty() + self.text_area.winfo_y() +
self.text_area.tk.call(self.text_area._w, 'index', f'{line}.{word_start}',
'xpixels', 'ypixels') + 20
))
self.autocomplete_listbox = tk.Listbox(
self.autocomplete_window,
height=min(10, len(matches)),
bg='#2D2D2D', fg='white',
selectbackground='#3D3D3D',
font=('Consolas', 12)
)
self.autocomplete_listbox.pack()
for match in matches:
self.autocomplete_listbox.insert(tk.END, match)
self.autocomplete_listbox.bind('<Return>', self.select_autocomplete)
self.autocomplete_listbox.bind('<Tab>', self.select_autocomplete)
self.autocomplete_listbox.bind('<Escape>', lambda e: self.autocomplete_window.destroy())
self.autocomplete_listbox.bind('<Button-1>', self.select_autocomplete)
self.autocomplete_pos = (line, word_start, col)
self.autocomplete_listbox.focus_set()
self.autocomplete_listbox.selection_set(0)
def handle_autocomplete_key(self, event):
if not self.autocomplete_listbox:
return
selection = self.autocomplete_listbox.curselection()
if event.keysym == 'Return' or event.keysym == 'Tab':
if selection:
self.select_autocomplete(event)
return 'break'
elif event.keysym == 'Down':
if selection and selection < self.autocomplete_listbox.size() - 1:
self.autocomplete_listbox.selection_clear(selection)
self.autocomplete_listbox.selection_set(selection + 1)
return 'break'
elif event.keysym == 'Up':
if selection and selection > 0:
self.autocomplete_listbox.selection_clear(selection)
self.autocomplete_listbox.selection_set(selection - 1)
return 'break'
return None
def select_autocomplete(self, event):
if not self.autocomplete_listbox:
return
selection = self.autocomplete_listbox.curselection()
if selection:
selected = self.autocomplete_listbox.get(selection)
line, word_start, col = self.autocomplete_pos
self.text_area.delete(f'{line}.{word_start}', f'{line}.{col}')
self.text_area.insert(f'{line}.{word_start}', selected)
self.text_area.mark_set(tk.INSERT, f'{line}.{word_start + len(selected)}')
self.autocomplete_window.destroy()
self.autocomplete_window = None
self.text_area.focus_set()
return 'break'
def highlight(self):
for tag in self.tag_colors.keys():
self.text_area.tag_remove(tag, '1.0', tk.END)
text = self.text_area.get('1.0', tk.END)
# 高亮注释
self.highlight_pattern(r'<!--.*?-->', 'comment')
# 高亮DOCTYPE
self.highlight_pattern(r'<!DOCTYPE.*?>', 'doctype')
# 高亮标签
self.highlight_pattern(r'<\/?+', 'tag')
self.highlight_pattern(r'<\/+>', 'tag')
# 高亮属性
self.highlight_pattern(r'\s+=', 'attribute')
# 高亮属性值
self.highlight_pattern(r'"[^"]*"', 'value')
self.highlight_pattern(r"'[^']*'", 'value')
# 高亮符号
self.highlight_pattern(r'[<>\/=]', 'symbol')
def highlight_pattern(self, pattern, tag):
start = '1.0'
end = tk.END
self.text_area.mark_set('matchStart', start)
self.text_area.mark_set('matchEnd', start)
count = tk.IntVar()
while True:
index = self.text_area.search(
pattern, 'matchEnd', end,
count=count, regexp=True
)
if index == '': break
self.text_area.mark_set('matchStart', index)
self.text_area.mark_set('matchEnd', f'{index}+{count.get()}c')
self.text_area.tag_add(tag, 'matchStart', 'matchEnd')
if __name__ == '__main__':
root = tk.Tk()
root.geometry('800x600')
app = HTMLCompiler(root)
root.mainloop() 我注意到你的HTML编译器代码有几个小问题,主要涉及自动补全功能未完成和部分逻辑错误。以下是修复建议:
### 主要问题修复:
1. **自动补全列表未定义**:
需要补全 `self.autocomplete_words` 列表,例如:
self.autocomplete_words = ['<html>', '<head>', '<body>', '<div>', '<p>', '<a>', '<img>', '<script>', '<style>', 'class', 'id', 'href', 'src']
2. **文件对话框类型缺失**:
在 `open_file` 和 `save_as_file` 方法中补全文件类型:
filetypes=[('HTML 文件', '*.html'), ('所有文件', '*.*')]
3. **回车缩进逻辑错误**:
修复 `handle_return` 的缩进计算:
def handle_return(self, event):
current_pos = self.text_area.index(tk.INSERT)
line, col = map(int, current_pos.split('.'))
line_text = self.text_area.get(f'{line}.0', f'{line}.end')
indent = 0
# 修正:遍历字符而非整个字符串
while indent < len(line_text) and line_text == ' ':
indent += 1
self.text_area.insert(tk.INSERT, '\n' + ' ' * indent)
return 'break'# 阻止默认回车行为
### 其他优化建议:
1. **语法高亮增强**:
在 `highlight` 方法(未提供)中建议使用正则表达式匹配标签:
re.findall(r'<[^>]+>', content)
2. **临时文件清理**:
在 `run_html` 中添加延时删除临时文件:
import threading
import time
def delete_temp_file(path):
time.sleep(5)# 延迟5秒确保浏览器加载
os.remove(path)
# 在webbrowser.open后添加
threading.Thread(target=delete_temp_file, args=(temp_path,)).start()
3. **自动补全框架**:
补全基础自动补全方法(需自行完善匹配逻辑):
def check_autocomplete(self, event):
# 获取当前单词逻辑示例
current_pos = self.text_area.index(tk.INSERT)
words = self.text_area.get("1.0", current_pos).split()
if not words:
return
last_word = words[-1]
# 简单示例:匹配开头字符
matches =
if matches:
self.show_autocomplete(matches, current_pos)
### 使用注意事项:
1. 确保语法高亮方法 `highlight` 已实现
2. 自动补全功能需要完善位置计算和选择逻辑
3. Windows系统可能需要调整临时文件路径处理
> 提示:你可以先注释掉自动补全相关的绑定事件(self.text_area.bind('<KeyRelease>', self.on_key_release)),优先确保基础功能运行正常。
需要进一步调试某个功能可以告诉我具体问题点,我会提供更详细的解决方案!
以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。 {:10_256:} 我去,这么狠的吗? 看了一下,这段代码其实是用 tkinter 做了一个简单的 HTML 编辑器,能新建、打开、保存、运行 HTML 文件,还带了点语法高亮和自动补全,操作起来比较方便。
下次发帖配点图片那就更好了,加油~
{:13_450:}
页:
[1]