class CodeMasterIDE {
constructor() {
this.files = new Map();
this.openTabs = [];
this.activeTab = null;
this.editor = null;
this.fileCounter = 1;
this.aiLearning = new AILearning();
this.primaryFile = null;
this.sidebarWidth = 256;
this.aiPanelWidth = 0;
this.isDirty = new Set();
this.currentModalCallback = null;
this.contextMenuTarget = null;
// Preserve user's original content as initial files
this.initialFiles = {
'index.html': `
My static Space
Welcome to your static Space!
You can modify this app directly by editing index.html in the Files and versions tab.
Also don't forget to check the
Spaces documentation.
`,
'style.css': `body {
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
}
h1 {
font-size: 16px;
margin-top: 0;
}
p {
color: rgb(107, 114, 128);
font-size: 15px;
margin-bottom: 10px;
margin-top: 5px;
}
.card {
max-width: 620px;
margin: 0 auto;
padding: 16px;
border: 1px solid lightgray;
border-radius: 16px;
}
.card p:last-child {
margin-bottom: 0;
}`
};
this.init();
}
init() {
this.loadFiles();
this.initEditor();
this.setupEventListeners();
this.setupAI();
this.setupConsoleCapture();
this.broadcastChannel = this.setupBroadcastChannel();
// Open initial files
if (this.files.size === 0) {
Object.entries(this.initialFiles).forEach(([name, content]) => {
this.files.set(name, {
name,
content,
type: this.getFileType(name),
path: name
});
});
this.saveFiles();
}
this.renderFileTree();
// Open first HTML file or first file
const firstHtml = Array.from(this.files.keys()).find(f => f.endsWith('.html'));
const firstFile = firstHtml || this.files.keys().next().value;
if (firstFile) {
this.openFile(firstFile);
if (firstHtml) this.setPrimaryFile(firstHtml);
}
this.updatePrimaryFileSelect();
lucide.createIcons();
}
setupBroadcastChannel() {
if ('BroadcastChannel' in window) {
try {
const bc = new BroadcastChannel('codemaster_sync');
bc.onmessage = (event) => {
if (event.data.type === 'pong') {
console.log('Viewer connected');
}
};
return bc;
} catch (e) {
return null;
}
}
return null;
}
loadFiles() {
try {
const saved = localStorage.getItem('codemaster_files');
if (saved) {
const parsed = JSON.parse(saved);
this.files = new Map(Object.entries(parsed));
}
} catch (e) {
console.error('Failed to load files:', e);
}
}
saveFiles() {
try {
const obj = Object.fromEntries(this.files);
localStorage.setItem('codemaster_files', JSON.stringify(obj));
} catch (e) {
console.error('Failed to save files:', e);
}
}
initEditor() {
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs' }});
require(['vs/editor/editor.main'], () => {
monaco.editor.defineTheme('codemaster-dark', {
base: 'vs-dark',
inherit: true,
rules: [],
colors: {
'editor.background': '#020617',
'editor.lineHighlightBackground': '#1e293b',
'editorLineNumber.foreground': '#64748b',
'editorLineNumber.activeForeground': '#94a3b8',
}
});
this.editor = monaco.editor.create(document.getElementById('editor-container'), {
value: '',
language: 'html',
theme: 'codemaster-dark',
automaticLayout: true,
minimap: { enabled: true },
fontSize: 14,
fontFamily: 'JetBrains Mono, Consolas, Monaco, monospace',
wordWrap: 'on',
lineNumbers: 'on',
roundedSelection: false,
scrollBeyondLastLine: false,
renderLineHighlight: 'line',
padding: { top: 16 },
bracketPairColorization: { enabled: true },
autoIndent: 'advanced',
formatOnPaste: true,
formatOnType: true,
suggestOnTriggerCharacters: true,
quickSuggestions: true,
});
// AI-powered suggestions
this.editor.onDidType((e) => {
this.handleTyping(e);
});
// Track changes
this.editor.onDidChangeModelContent(() => {
if (this.activeTab) {
this.isDirty.add(this.activeTab);
this.updateTabStatus(this.activeTab);
this.aiLearning.recordEdit(this.activeTab, this.editor.getValue());
this.debouncedPreviewUpdate();
}
});
// Load initial content if exists
if (this.activeTab && this.files.has(this.activeTab)) {
const file = this.files.get(this.activeTab);
this.editor.setValue(file.content);
monaco.editor.setModelLanguage(this.editor.getModel(), file.type);
}
});
}
handleTyping(text) {
const position = this.editor.getPosition();
const model = this.editor.getModel();
const lineContent = model.getLineContent(position.lineNumber);
const beforeCursor = lineContent.substring(0, position.column - 1);
// Get AI suggestion
const suggestion = this.aiLearning.getSuggestion(beforeCursor, text, this.activeTab);
if (suggestion) {
this.showAISuggestion(suggestion, position);
} else {
this.hideAISuggestion();
}
}
showAISuggestion(suggestion, position) {
const coords = this.editor.getScrolledVisiblePosition(position);
const editorDom = document.getElementById('editor-container');
const suggestionEl = document.getElementById('ai-suggestion');
suggestionEl.style.left = (editorDom.offsetLeft + coords.left) + 'px';
suggestionEl.style.top = (editorDom.offsetTop + coords.top + 20) + 'px';
suggestionEl.textContent = 'AI: ' + suggestion.text;
suggestionEl.classList.remove('hidden');
// Auto-insert on Tab if high confidence
if (suggestion.confidence > 0.8) {
const disposable = this.editor.onKeyDown((e) => {
if (e.keyCode === monaco.KeyCode.Tab) {
e.preventDefault();
this.insertSuggestion(suggestion);
disposable.dispose();
}
});
// Dispose after 5 seconds
setTimeout(() => disposable.dispose(), 5000);
}
}
hideAISuggestion() {
document.getElementById('ai-suggestion').classList.add('hidden');
}
insertSuggestion(suggestion) {
const position = this.editor.getPosition();
this.editor.executeEdits('ai-suggestion', [{
range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
text: suggestion.insert
}]);
this.hideAISuggestion();
this.aiLearning.recordAcceptance(suggestion);
this.updateAIPanel();
}
setupAI() {
this.updateAIPanel();
setInterval(() => this.updateAIPanel(), 5000);
}
updateAIPanel() {
const stats = this.aiLearning.getStats();
document.getElementById('patterns-count').textContent = stats.patterns;
document.getElementById('confidence-bar').style.width = stats.confidence + '%';
document.getElementById('confidence-text').textContent = stats.confidence + '%';
const snippetsEl = document.getElementById('frequent-snippets');
snippetsEl.innerHTML = stats.topSnippets.map(s => `
${s.trigger}
${s.code}
Used ${s.count} times
`).join('');
const historyEl = document.getElementById('suggestions-history');
historyEl.innerHTML = stats.recentSuggestions.map(s => `
${s.time}: ${s.text}
`).join('');
}
setupConsoleCapture() {
const originalLog = console.log;
const originalError = console.error;
const originalWarn = console.warn;
const self = this;
console.log = function(...args) {
self.appendToConsole('log', args);
originalLog.apply(console, args);
};
console.error = function(...args) {
self.appendToConsole('error', args);
originalError.apply(console, args);
};
console.warn = function(...args) {
self.appendToConsole('warn', args);
originalWarn.apply(console, args);
};
window.onerror = (msg, url, line) => {
this.appendToConsole('error', [msg + ' (line ' + line + ')']);
};
}
appendToConsole(level, args) {
const output = document.getElementById('console-output');
const line = document.createElement('div');
line.className = 'console-line ' + level;
const timestamp = new Date().toLocaleTimeString();
const message = args.map(arg => {
if (typeof arg === 'object') {
try {
return JSON.stringify(arg, null, 2);
} catch(e) {
return String(arg);
}
}
return String(arg);
}).join(' ');
line.textContent = '[' + timestamp + '] ' + message;
output.appendChild(line);
output.scrollTop = output.scrollHeight;
// Update problem count for errors
if (level === 'error') {
this.updateProblemCount();
}
}
updateProblemCount() {
const errors = document.querySelectorAll('.console-line.error').length;
document.getElementById('problem-count').textContent = errors;
}
getFileType(filename) {
const ext = filename.split('.').pop().toLowerCase();
const types = {
'html': 'html',
'htm': 'html',
'css': 'css',
'js': 'javascript',
'json': 'json',
'ts': 'typescript',
'py': 'python',
'md': 'markdown',
'xml': 'xml',
'svg': 'xml'
};
return types[ext] || 'plaintext';
}
getFileIcon(filename) {
const ext = filename.split('.').pop().toLowerCase();
const icons = {
'html': 'file-code',
'css': 'file-code',
'js': 'file-code',
'json': 'file-json',
'md': 'file-text',
'txt': 'file-text',
'jpg': 'image',
'png': 'image',
'gif': 'image',
'svg': 'image'
};
return icons[ext] || 'file';
}
renderFileTree() {
const tree = document.getElementById('file-tree');
tree.innerHTML = '';
const sortedFiles = Array.from(this.files.keys()).sort();
sortedFiles.forEach(filename => {
const item = document.createElement('div');
item.className = 'file-tree-item flex items-center gap-2 px-2 py-1.5 rounded text-sm' +
(this.activeTab === filename ? ' active' : '');
item.innerHTML = `
${filename}
`;
item.onclick = () => this.openFile(filename);
item.oncontextmenu = (e) => this.showContextMenu(e, filename);
tree.appendChild(item);
});
lucide.createIcons();
}
getFileIconColor(filename) {
const ext = filename.split('.').pop().toLowerCase();
const colors = {
'html': 'text-orange-400',
'css': 'text-blue-400',
'js': 'text-yellow-400',
'json': 'text-green-400'
};
return colors[ext] || 'text-slate-400';
}
openFile(filename) {
// Save current content if dirty
if (this.activeTab && this.isDirty.has(this.activeTab)) {
this.saveCurrentFile();
}
// Update UI
document.querySelectorAll('.file-tree-item').forEach(el => {
el.classList.remove('active');
if (el.textContent.trim() === filename) {
el.classList.add('active');
}
});
// Add to tabs if not open
if (!this.openTabs.includes(filename)) {
this.openTabs.push(filename);
this.renderTabs();
}
this.activeTab = filename;
this.updateTabStatus(filename);
// Load content
const file = this.files.get(filename);
if (file && this.editor) {
this.editor.setValue(file.content);
monaco.editor.setModelLanguage(this.editor.getModel(), file.type);
}
this.renderTabs();
}
renderTabs() {
const tabsContainer = document.getElementById('tabs');
tabsContainer.innerHTML = '';
this.openTabs.forEach(filename => {
const tab = document.createElement('div');
const isModified = this.isDirty.has(filename);
tab.className = 'tab' + (this.activeTab === filename ? ' active' : '') + (isModified ? ' modified' : '');
tab.innerHTML = `
${filename}
`;
tab.onclick = () => this.openFile(filename);
tabsContainer.appendChild(tab);
});
lucide.createIcons();
}
updateTabStatus(filename) {
this.renderTabs();
}
closeTab(filename) {
const index = this.openTabs.indexOf(filename);
this.openTabs = this.openTabs.filter(f => f !== filename);
if (this.isDirty.has(filename)) {
this.saveFile(filename);
}
if (this.activeTab === filename) {
this.activeTab = this.openTabs[Math.max(0, index - 1)] || null;
if (this.activeTab) {
this.openFile(this.activeTab);
} else {
this.editor.setValue('');
}
}
this.renderTabs();
}
saveCurrentFile() {
if (this.activeTab && this.editor) {
const content = this.editor.getValue();
const file = this.files.get(this.activeTab);
if (file) {
file.content = content;
this.files.set(this.activeTab, file);
this.isDirty.delete(this.activeTab);
this.saveFiles();
this.updateTabStatus(this.activeTab);
this.syncToRemote();
}
}
}
saveFile(filename) {
if (this.editor && this.activeTab === filename) {
this.saveCurrentFile();
}
}
saveAll() {
this.openTabs.forEach(tab => {
if (this.isDirty.has(tab)) {
this.saveFile(tab);
}
});
this.showNotification('All files saved');
}
setPrimaryFile(filename) {
this.primaryFile = filename;
document.getElementById('primary-file').value = filename;
this.updatePreview();
this.showNotification('Primary file set to: ' + filename);
}
updatePrimaryFileSelect() {
const select = document.getElementById('primary-file');
const currentValue = select.value;
// Clear non-placeholder options
while (select.options.length > 1) {
select.remove(1);
}
// Add HTML files
Array.from(this.files.keys())
.filter(f => f.endsWith('.html'))
.forEach(f => {
const option = document.createElement('option');
option.value = f;
option.textContent = f;
if (f === currentValue || f === this.primaryFile) {
option.selected = true;
}
select.appendChild(option);
});
}
updatePreview() {
if (!this.primaryFile || !this.files.has(this.primaryFile)) return;
const file = this.files.get(this.primaryFile);
let content = file.content;
// Process linked files
content = this.processIncludes(content);
// Update local preview
const frame = document.getElementById('preview-frame');
const blob = new Blob([content], { type: 'text/html' });
const url = URL.createObjectURL(blob);
frame.src = url;
// Sync to remote viewers
this.syncToRemote(content);
}
processIncludes(html) {
// Replace relative paths with data URIs for linked files
let processed = html;
// Process CSS links
const cssLinks = html.match(/href=["']([^"']+\.css)["']/g) || [];
cssLinks.forEach(link => {
const match = link.match(/href=["']([^"']+)["']/);
if (match) {
const path = match[1];
const filename = path.replace(/^\.\//, '').replace(/^\//, '');
if (this.files.has(filename)) {
const file = this.files.get(filename);
const dataUri = 'data:text/css;base64,' + btoa(file.content);
processed = processed.replace(path, dataUri);
}
}
});
// Process JS scripts (excluding external URLs)
const jsScripts = html.match(/src=["']([^"']+\.js)["']/g) || [];
jsScripts.forEach(script => {
const match = script.match(/src=["']([^"']+)["']/);
if (match) {
const path = match[1];
if (!path.startsWith('http') && !path.startsWith('//')) {
const filename = path.replace(/^\.\//, '').replace(/^\//, '');
if (this.files.has(filename)) {
const file = this.files.get(filename);
const dataUri = 'data:text/javascript;base64,' + btoa(file.content);
processed = processed.replace(path, dataUri);
}
}
}
});
return processed;
}
debouncedPreviewUpdate() {
clearTimeout(this.previewTimeout);
this.previewTimeout = setTimeout(() => this.updatePreview(), 1000);
}
syncToRemote(content) {
const data = {
content: content || (this.files.has(this.primaryFile) ? this.processIncludes(this.files.get(this.primaryFile).content) : ''),
timestamp: Date.now(),
primaryFile: this.primaryFile
};
// BroadcastChannel for same-origin tabs
if (this.broadcastChannel) {
this.broadcastChannel.postMessage({
type: 'code_update',
data: data
});
}
// localStorage for cross-tab/cross-device on same domain
try {
localStorage.setItem('codemaster_preview', JSON.stringify(data));
} catch (e) {
console.error('Failed to sync:', e);
}
}
newFile() {
this.showModal('New File', (name) => {
if (name && !this.files.has(name)) {
const type = this.getFileType(name);
this.files.set(name, {
name,
content: '',
type,
path: name
});
this.saveFiles();
this.renderFileTree();
this.openFile(name);
this.updatePrimaryFileSelect();
}
});
}
newFolder() {
this.showNotification('Folders are simulated - use "folder/file.ext" naming');
}
deleteFile(filename) {
if (confirm('Delete "' + filename + '"?')) {
this.files.delete(filename);
this.saveFiles();
if (this.openTabs.includes(filename)) {
this.closeTab(filename);
}
if (this.primaryFile === filename) {
this.primaryFile = null;
document.getElementById('primary-file').value = '';
}
this.renderFileTree();
this.updatePrimaryFileSelect();
}
}
renameFile(oldName) {
this.showModal('Rename File', (newName) => {
if (newName && newName !== oldName && !this.files.has(newName)) {
const file = this.files.get(oldName);
file.name = newName;
file.type = this.getFileType(newName);
this.files.delete(oldName);
this.files.set(newName, file);
// Update tabs
const tabIndex = this.openTabs.indexOf(oldName);
if (tabIndex !== -1) {
this.openTabs[tabIndex] = newName;
}
if (this.activeTab === oldName) {
this.activeTab = newName;
}
if (this.primaryFile === oldName) {
this.setPrimaryFile(newName);
}
this.saveFiles();
this.renderFileTree();
this.renderTabs();
this.updatePrimaryFileSelect();
}
}, oldName);
}
showModal(title, callback, defaultValue = '') {
document.getElementById('modal-title').textContent = title;
const input = document.getElementById('modal-input');
input.value = defaultValue;
input.placeholder = title === 'New File' ? 'example.html' : 'new-name.html';
document.getElementById('modal').classList.add('active');
input.focus();
input.select();
this.currentModalCallback = callback;
// Enter key handler
input.onkeydown = (e) => {
if (e.key === 'Enter') {
this.confirmModal();
} else if (e.key === 'Escape') {
this.closeModal();
}
};
}
closeModal() {
document.getElementById('modal').classList.remove('active');
this.currentModalCallback = null;
}
confirmModal() {
const value = document.getElementById('modal-input').value.trim();
if (value && this.currentModalCallback) {
this.currentModalCallback(value);
}
this.closeModal();
}
showContextMenu(event, filename) {
event.preventDefault();
this.contextMenuTarget = filename;
const menu = document.getElementById('context-menu');
menu.innerHTML = `
`;
menu.style.left = event.pageX + 'px';
menu.style.top = event.pageY + 'px';
menu.classList.remove('hidden');
lucide.createIcons();
// Close on click outside
setTimeout(() => {
document.addEventListener('click', () => {
menu.classList.add('hidden');
}, { once: true });
}, 0);
}
toggleAI() {
const panel = document.getElementById('ai-panel');
const btn = document.getElementById('ai-btn');
if (panel.style.width === '300px') {
panel.style.width = '0';
btn.classList.remove('bg-purple-700');
btn.classList.add('bg-purple-600');
} else {
panel.style.width = '300px';
btn.classList.remove('bg-purple-600');
btn.classList.add('bg-purple-700');
this.updateAIPanel();
}
}
showRemotePreview() {
const url = window.location.href.replace('index.html', 'viewer.html');
document.getElementById('preview-url').textContent = url;
// Generate QR code
const qrContainer = document.getElementById('qrcode');
qrContainer.innerHTML = '';
new QRCode(qrContainer, {
text: url,
width: 256,
height: 256,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
});
document.getElementById('remote-modal').classList.add('active');
}
closeRemoteModal() {
document.getElementById('remote-modal').classList.remove('active');
}
switchPanel(panel) {
document.querySelectorAll('.panel-section').forEach(p => p.classList.add('hidden'));
document.querySelectorAll('.panel-tab').forEach(t => t.classList.remove('active', 'text-blue-400'));
document.getElementById(panel + '-panel').classList.remove('hidden');
document.querySelector('[data-panel="' + panel + '"]').classList.add('active', 'text-blue-400');
if (panel === 'preview') {
this.updatePreview();
}
}
toggleBottomPanel() {
const panel = document.getElementById('bottom-panel');
if (panel.style.height === '40px') {
panel.style.height = '192px';
} else {
panel.style.height = '40px';
}
}
startResize(elementId) {
const isSidebar = elementId === 'sidebar';
const startX = event.clientX;
const startWidth = isSidebar ? this.sidebarWidth : this.aiPanelWidth;
const doDrag = (e) => {
if (isSidebar) {
this.sidebarWidth = Math.max(150, Math.min(400, startWidth + e.clientX - startX));
document.getElementById('sidebar').style.width = this.sidebarWidth + 'px';
}
};
const stopDrag = () => {
document.removeEventListener('mousemove', doDrag);
document.removeEventListener('mouseup', stopDrag);
};
document.addEventListener('mousemove', doDrag);
document.addEventListener('mouseup', stopDrag);
}
showNotification(message) {
const notif = document.createElement('div');
notif.className = 'fixed bottom-4 right-4 bg-blue-600 text-white px-4 py-2 rounded shadow-lg z-50 text-sm';
notif.textContent = message;
document.body.appendChild(notif);
setTimeout(() => notif.remove(), 3000);
}
setupEventListeners() {
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey || e.metaKey) {
switch(e.key.toLowerCase()) {
case 's':
e.preventDefault();
this.saveAll();
break;
case 'n':
e.preventDefault();
this.newFile();
break;
}
}
});
// Save on before unload
window.addEventListener('beforeunload', () => {
if (this.isDirty.size > 0) {
this.saveAll();
}
});
}
}
class AILearning {
constructor() {
this.patterns = new Map();
this.snippetUsage = new Map();
this.suggestionHistory = [];
this.userStyle = {
namingConvention: null,
preferredQuotes: null,
semicolonUsage: null,
indentStyle: null
};
this.loadData();
}
loadData() {
try {
const data = localStorage.getItem('codemaster_ai');
if (data) {
const parsed = JSON.parse(data);
this.patterns = new Map(parsed.patterns || []);
this.snippetUsage = new Map(parsed.snippets || []);
this.userStyle = parsed.style || this.userStyle;
}
} catch (e) {}
}
saveData() {
try {
const data = {
patterns: Array.from(this.patterns),
snippets: Array.from(this.snippetUsage),
style: this.userStyle
};
localStorage.setItem('codemaster_ai', JSON.stringify(data));
} catch (e) {}
}
recordEdit(filename, content) {
// Analyze content for patterns
this.analyzePatterns(content);
// Track snippet usage
this.trackSnippets(content);
// Debounced save
clearTimeout(this.saveTimeout);
this.saveTimeout = setTimeout(() => this.saveData(), 5000);
}
analyzePatterns(content) {
// Detect naming convention
const camelCase = content.match(/[a-z]+[A-Z][a-zA-Z]+/g) || [];
const snake_case = content.match(/[a-z]+_[a-z_]+/g) || [];
const PascalCase = content.match(/[A-Z][a-z]+[A-Z][a-zA-Z]+/g) || [];
if (camelCase.length > snake_case.length && camelCase.length > PascalCase.length) {
this.userStyle.namingConvention = 'camelCase';
} else if (snake_case.length > camelCase.length) {
this.userStyle.namingConvention = 'snake_case';
}
// Detect quote preference
const singleQuotes = (content.match(/'/g) || []).length;
const doubleQuotes = (content.match(/"/g) || []).length;
this.userStyle.preferredQuotes = doubleQuotes > singleQuotes ? 'double' : 'single';
// Detect semicolon usage
const lines = content.split('\n');
const linesWithSemicolon = lines.filter(l => l.trim().endsWith(';')).length;
this.userStyle.semicolonUsage = linesWithSemicolon > lines.length * 0.3;
// Detect indent style
const spaces = content.match(/^ +/gm) || [];
const tabs = content.match(/^\t+/gm) || [];
this.userStyle.indentStyle = spaces.length > tabs.length ? 'spaces' : 'tabs';
}
trackSnippets(content) {
// Common patterns to track
const patterns = [
{ regex: /console\.log\([^)]+\)/g, name: 'console.log' },
{ regex: /document\.getElementById\([^)]+\)/g, name: 'getElementById' },
{ regex: /document\.querySelector\([^)]+\)/g, name: 'querySelector' },
{ regex: /function\s+\w+\s*\([^)]*\)\s*{/g, name: 'function declaration' },
{ regex: /const\s+\w+\s*=/g, name: 'const declaration' },
{ regex: /let\s+\w+\s*=/g, name: 'let declaration' },
{ regex: /if\s*\([^)]+\)\s*{/g, name: 'if statement' },
{ regex: /for\s*\([^)]+\)\s*{/g, name: 'for loop' }
];
patterns.forEach(p => {
const matches = content.match(p.regex) || [];
matches.forEach(match => {
const key = p.name + ':' + match.substring(0, 20);
const count = this.snippetUsage.get(key) || { count: 0, code: match.substring(0, 30) };
count.count++;
this.snippetUsage.set(key, count);
});
});
}
getSuggestion(beforeCursor, typed, filename) {
const ext = filename.split('.').pop().toLowerCase();
// Context-aware suggestions based on file type
if (ext === 'html' || ext === 'htm') {
if (typed === '<') {
return { text: 'div', insert: 'div>', confidence: 0.9 };
}
if (beforeCursor.endsWith('', insert: '>', confidence: 0.85 };
}
if (beforeCursor.endsWith('', insert: '>', confidence: 0.85 };
}
}
if (ext === 'css') {
if (beforeCursor.endsWith(' {')) {
return { text: ' }', insert: '\n \n}', confidence: 0.9 };
}
}
if (ext === 'js' || ext === 'javascript') {
// Learned snippet suggestions
if (typed === 'c' && this.snippetUsage.has('console.log')) {
const usage = this.snippetUsage.get('console.log');
if (usage.count > 2) {
return { text: 'onsole.log()', insert: 'onsole.log()', confidence: Math.min(0.95, 0.7 + usage.count * 0.05) };
}
}
if (typed === 'f' && beforeCursor.match(/^\s*$/)) {
return { text: 'unction name() {}', insert: 'unction ' + this.guessFunctionName() + '() {\n \n}', confidence: 0.8 };
}
if (beforeCursor.endsWith('doc')) {
return { text: 'ument.', insert: 'ument.', confidence: 0.85 };
}
}
// Smart closing pairs based on user's learned patterns
if (this.userStyle.namingConvention === 'camelCase') {
// Suggest camelCase completions
}
return null;
}
guessFunctionName() {
// Could be enhanced based on context
return 'myFunction';
}
recordAcceptance(suggestion) {
this.suggestionHistory.unshift({
text: suggestion.text,
time: new Date().toLocaleTimeString()
});
if (this.suggestionHistory.length > 10) {
this.suggestionHistory.pop();
}
}
getStats() {
const snippets = Array.from(this.snippetUsage.entries())
.sort((a, b) => b[1].count - a[1].count)
.slice(0, 5)
.map(([key, value]) => ({
trigger: key.split(':')[0],
code: value.code,
count: value.count
}));
const totalPatterns = this.patterns.size + this.snippetUsage.size;
const confidence = Math.min(100, Math.floor(totalPatterns * 2));
return {
patterns: totalPatterns,
confidence: confidence,
topSnippets: snippets,
recentSuggestions: this.suggestionHistory,
style: this.userStyle
};
}
}
// Initialize
const app = new CodeMasterIDE();