<?php
// Editor for viewing/editing files with ACE Editor, Image Preview, and Play Button
// Security: Define the root directory to prevent directory traversal attacks
$rootDir = __DIR__;
$currentDir = isset($_GET['dir']) ? $_GET['dir'] : '';
// Sanitize the directory path
$currentDir = str_replace(['../', '..\\'], '', $currentDir);
$fullPath = realpath($rootDir . '/' . $currentDir);
// Security check: Ensure we're still within the root directory
if (!$fullPath || strpos($fullPath, realpath($rootDir)) !== 0) {
header('Location: explorer.php');
exit;
}
$viewFile = '';
$fileContent = '';
$isEditable = false;
$isExecutable = false;
$isImage = false;
$message = '';
$messageType = '';
if (isset($_GET['view'])) {
$viewFile = basename($_GET['view']);
$viewFilePath = $fullPath . '/' . $viewFile;
if (file_exists($viewFilePath) && is_file($viewFilePath)) {
$fileExt = strtolower(pathinfo($viewFile, PATHINFO_EXTENSION));
$editableExts = ['txt', 'php', 'html', 'css', 'js', 'json', 'xml', 'md', 'py', 'java', 'cpp', 'c', 'h', 'sql', 'yml', 'yaml'];
$executableExts = ['php', 'html', 'htm', 'js'];
$imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
if (in_array($fileExt, $imageExts)) {
$isImage = true;
} elseif (in_array($fileExt, $editableExts)) {
$isEditable = true;
$isExecutable = in_array($fileExt, $executableExts);
$fileContent = file_get_contents($viewFilePath);
}
} else {
header('Location: explorer.php?dir=' . urlencode($currentDir));
exit;
}
} else {
header('Location: explorer.php');
exit;
}
// Handle save submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save_file') {
if (isset($_POST['file_name']) && isset($_POST['file_content'])) {
$fileName = basename($_POST['file_name']);
$filePath = $fullPath . '/' . $fileName;
if (file_put_contents($filePath, $_POST['file_content']) !== false) {
setPermissions($filePath);
$message = "File '$fileName' saved successfully with 777 permissions!";
$messageType = 'success';
$fileContent = $_POST['file_content']; // Update displayed content
} else {
$message = "Failed to save file '$fileName'.";
$messageType = 'error';
}
}
}
// Handle execute submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'execute_file') {
if (isset($_POST['file_name']) && isset($_POST['file_content'])) {
$fileName = basename($_POST['file_name']);
$filePath = $fullPath . '/' . $fileName;
// Save the current content first
file_put_contents($filePath, $_POST['file_content']);
setPermissions($filePath);
// Redirect to console monitor with this file
$consoleMonitorPath = 'console_monitor.php';
if (file_exists($consoleMonitorPath)) {
header('Location: ' . $consoleMonitorPath . '?dir=' . urlencode($currentDir) . '&file=' . urlencode($fileName));
exit;
} else {
$message = "Console monitor not found. Please ensure console_monitor.php exists.";
$messageType = 'error';
}
}
}
// Helper function to ensure proper permissions
function setPermissions($path) {
if (file_exists($path)) {
chmod($path, 0777);
umask(0000);
return true;
}
return false;
}
// Helper function to determine file type for ACE editor
function getAceMode($filename) {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$modes = [
'php' => 'php',
'js' => 'javascript',
'html' => 'html',
'css' => 'css',
'json' => 'json',
'xml' => 'xml',
'md' => 'markdown',
'py' => 'python',
'java' => 'java',
'cpp' => 'c_cpp',
'c' => 'c_cpp',
'h' => 'c_cpp',
'sql' => 'sql',
'yml' => 'yaml',
'yaml' => 'yaml'
];
return isset($modes[$ext]) ? $modes[$ext] : 'text';
}
// Get file extension for icon
function getFileIcon($filename) {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$icons = [
'php' => '🐘',
'html' => '🌐',
'htm' => '🌐',
'js' => '📜',
'css' => '🎨',
'json' => '📄',
'xml' => '📋',
'md' => '📝',
'py' => '🐍',
'java' => '☕',
'cpp' => '⚙️',
'c' => '⚙️',
'sql' => '🗃️'
];
return $icons[$ext] ?? '📄';
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Editor - <?= htmlspecialchars($viewFile) ?></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ext-language_tools.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #f5f5f5;
color: #333;
line-height: 1.6;
overflow-x: hidden;
}
.file-viewer {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: white;
z-index: 1000;
display: flex;
flex-direction: column;
}
.message {
padding: 12px 15px;
border-radius: 6px;
margin: 10px 15px;
font-size: 14px;
}
.message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.viewer-header {
padding: 12px 15px;
border-bottom: 1px solid #ecf0f1;
background: #2c3e50;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
min-height: 50px;
}
.header-left {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
}
.file-icon {
font-size: 20px;
}
.viewer-title {
font-weight: 600;
font-size: 16px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
}
.header-buttons {
display: flex;
gap: 8px;
align-items: center;
}
.back-btn, .play-btn {
background: none;
border: none;
color: white;
font-size: 16px;
cursor: pointer;
padding: 8px 12px;
border-radius: 4px;
transition: background-color 0.2s;
text-decoration: none;
flex-shrink: 0;
display: flex;
align-items: center;
gap: 5px;
}
.back-btn:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.play-btn {
background-color: #27ae60;
}
.play-btn:hover {
background-color: #219a52;
}
.play-btn:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
.play-btn.executing {
background-color: #f39c12;
}
.viewer-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.image-preview {
text-align: center;
padding: 15px;
overflow: auto;
flex: 1;
}
.image-preview img {
max-width: 100%;
max-height: 100%;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.editor-toolbar {
padding: 10px 15px;
background: #f8f9fa;
border-bottom: 1px solid #ddd;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
flex-shrink: 0;
}
.editor-toolbar form {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
width: 100%;
}
.toolbar-left {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
flex: 1;
}
.toolbar-info {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
font-size: 13px;
color: #666;
margin-left: auto;
}
.toolbar-info span {
white-space: nowrap;
}
.find-group {
display: flex;
align-items: center;
gap: 0;
}
.find-mode {
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
font-size: 12px;
background: #f8f9fa;
color: #333;
cursor: pointer;
border-right: none;
min-width: 70px;
}
.find-mode:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
z-index: 1;
position: relative;
}
.find-input {
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 0;
font-size: 13px;
min-width: 120px;
flex-shrink: 0;
border-right: none;
}
.find-input:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
z-index: 1;
position: relative;
}
.find-btn {
background: #f8f9fa;
border: 1px solid #ddd;
color: #666;
font-size: 12px;
line-height: 1;
padding: 7px 8px;
cursor: pointer;
transition: all 0.2s;
min-width: 28px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.find-btn:hover {
background: #e9ecef;
color: #333;
}
.find-btn:active {
background: #dee2e6;
}
.find-btn:disabled {
background: #f8f9fa;
color: #ccc;
cursor: not-allowed;
}
.find-prev {
border-right: none;
}
.find-next {
border-radius: 0 4px 4px 0;
}
#editor {
flex: 1;
min-height: 0;
border: none;
border-radius: 0;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.2s;
text-decoration: none;
display: inline-block;
text-align: center;
flex-shrink: 0;
}
.btn-success {
background-color: #27ae60;
color: white;
}
.btn-success:hover {
background-color: #219a52;
}
.btn-success:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
.execution-status {
position: fixed;
top: 20px;
right: 20px;
background: #2c3e50;
color: white;
padding: 10px 15px;
border-radius: 6px;
font-size: 14px;
z-index: 1001;
transform: translateY(-100px);
opacity: 0;
transition: all 0.3s ease;
}
.execution-status.show {
transform: translateY(0);
opacity: 1;
}
.execution-status.success {
background: #27ae60;
}
.execution-status.error {
background: #e74c3c;
}
/* Mobile-specific styles */
@media (max-width: 768px) {
.viewer-header {
padding: 10px 12px;
min-height: 45px;
}
.viewer-title {
font-size: 14px;
}
.back-btn, .play-btn {
font-size: 14px;
padding: 6px 10px;
}
.header-buttons {
gap: 6px;
}
.editor-toolbar {
padding: 8px 12px;
gap: 8px;
}
.toolbar-left {
gap: 8px;
}
.btn {
padding: 6px 12px;
font-size: 13px;
}
.toolbar-info {
gap: 10px;
font-size: 12px;
}
.message {
padding: 10px 12px;
margin: 8px 12px;
font-size: 13px;
}
.find-mode {
font-size: 11px;
padding: 5px 6px;
min-width: 60px;
}
.find-input {
font-size: 12px;
min-width: 100px;
padding: 5px 8px;
}
.find-btn {
font-size: 11px;
padding: 6px 7px;
min-width: 26px;
height: 30px;
}
.image-preview {
padding: 10px;
}
.toolbar-info .keyboard-hint {
display: none;
}
}
@media (max-width: 480px) {
.viewer-header {
padding: 8px 10px;
min-height: 40px;
}
.viewer-title {
font-size: 13px;
}
.back-btn, .play-btn {
font-size: 13px;
padding: 5px 8px;
}
.editor-toolbar {
padding: 6px 10px;
}
.btn {
padding: 5px 10px;
font-size: 12px;
}
.toolbar-info {
font-size: 11px;
}
.message {
padding: 8px 10px;
margin: 6px 10px;
font-size: 12px;
}
.find-mode {
font-size: 10px;
padding: 4px 5px;
min-width: 55px;
}
.find-input {
font-size: 11px;
min-width: 80px;
padding: 4px 6px;
}
.find-btn {
font-size: 10px;
padding: 5px 6px;
min-width: 24px;
height: 28px;
}
}
.ace_editor {
font-size: 14px !important;
}
.ace_search {
background-color: rgba(255, 193, 7, 0.4) !important;
border: 1px solid #ffc107 !important;
}
.ace_selected-word {
background-color: rgba(255, 193, 7, 0.6) !important;
border: 1px solid #ffc107 !important;
}
.ace_selected_function {
background-color: rgba(52, 152, 219, 0.2) !important;
border: 1px solid rgba(52, 152, 219, 0.4) !important;
}
.ace_marker-layer .ace_active-line {
background: rgba(255, 255, 255, 0.08) !important;
}
.ace_marker-layer .ace_selection {
background: rgba(74, 144, 226, 0.3) !important;
}
@media (max-width: 768px) {
.ace_editor {
font-size: 13px !important;
}
}
@media (max-width: 480px) {
.ace_editor {
font-size: 12px !important;
}
}
</style>
</head>
<body>
<div class="file-viewer">
<div class="viewer-header">
<div class="header-left">
<span class="file-icon"><?= getFileIcon($viewFile) ?></span>
<div class="viewer-title"><?= htmlspecialchars($viewFile) ?></div>
</div>
<div class="header-buttons">
<?php if ($isExecutable): ?>
<button onclick="executeFile()" class="play-btn" id="playBtn">
<span>▶</span>
<span>Run & Monitor</span>
</button>
<?php endif; ?>
<a href="explorer.php?dir=<?= urlencode($currentDir) ?>" class="back-btn">
<span>←</span>
<span>Back</span>
</a>
</div>
</div>
<div class="viewer-content">
<?php if ($message): ?>
<div class="message <?= $messageType ?>">
<?= htmlspecialchars($message) ?>
</div>
<?php endif; ?>
<?php if ($isImage): ?>
<div class="image-preview">
<img src="data:image/<?= pathinfo($viewFile, PATHINFO_EXTENSION) ?>;base64,<?= base64_encode(file_get_contents($fullPath . '/' . $viewFile)) ?>"
alt="<?= htmlspecialchars($viewFile) ?>">
</div>
<?php elseif ($isEditable): ?>
<div class="editor-toolbar">
<form method="post" id="saveFileForm">
<input type="hidden" name="action" value="save_file">
<input type="hidden" name="file_name" value="<?= htmlspecialchars($viewFile) ?>">
<div class="toolbar-left">
<button type="submit" class="btn btn-success">
💾 Save File
</button>
<div class="find-group">
<select id="findMode" class="find-mode">
<option value="normal">Normal</option>
<option value="function">Function</option>
</select>
<input type="text" id="findInput" placeholder="Find..." class="find-input">
<button type="button" class="find-btn find-prev" title="Previous (Shift+Enter)">▲</button>
<button type="button" class="find-btn find-next" title="Next (Enter)">▼</button>
</div>
</div>
<div class="toolbar-info">
<span>📝 <?= ucfirst(getAceMode($viewFile)) ?></span>
<?php if ($isExecutable): ?>
<span>⚡ Executable</span>
<?php endif; ?>
<span class="keyboard-hint">Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find</span>
</div>
</form>
<form method="post" id="executeFileForm" style="display: none;">
<input type="hidden" name="action" value="execute_file">
<input type="hidden" name="file_name" value="<?= htmlspecialchars($viewFile) ?>">
<textarea name="file_content" id="execute_file_content"></textarea>
</form>
</div>
<div id="editor"><?= htmlspecialchars($fileContent) ?></div>
<textarea name="file_content" id="file_content" form="saveFileForm" style="display: none;"></textarea>
<?php endif; ?>
</div>
</div>
<div id="executionStatus" class="execution-status">
<span id="executionMessage"></span>
</div>
<script>
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
window.location.href = 'explorer.php?dir=<?= urlencode($currentDir) ?>';
}
});
<?php if ($isEditable): ?>
const editor = ace.edit("editor");
editor.setTheme("ace/theme/monokai");
editor.session.setMode("ace/mode/<?= getAceMode($viewFile) ?>");
editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
fontSize: 14,
showPrintMargin: false,
wrap: true,
useSoftTabs: true,
tabSize: 4
});
function updateEditorContent() {
const content = editor.getValue();
document.getElementById('file_content').value = content;
document.getElementById('execute_file_content').value = content;
}
function showExecutionStatus(message, type = 'info') {
const statusEl = document.getElementById('executionStatus');
const messageEl = document.getElementById('executionMessage');
messageEl.textContent = message;
statusEl.className = `execution-status ${type} show`;
setTimeout(() => {
statusEl.classList.remove('show');
}, 3000);
}
function saveFile() {
updateEditorContent();
const form = document.getElementById('saveFileForm');
const saveBtn = form.querySelector('.btn-success');
const originalText = saveBtn.innerHTML;
saveBtn.innerHTML = '⏳ Saving...';
saveBtn.disabled = true;
const formData = new FormData(form);
fetch(window.location.href, {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const messageEl = doc.querySelector('.message');
const messageText = messageEl ? messageEl.textContent : 'File saved successfully!';
const messageType = messageEl ? messageEl.classList.contains('success') ? 'success' : 'error' : 'success';
showExecutionStatus(messageText, messageType);
saveBtn.innerHTML = originalText;
saveBtn.disabled = false;
})
.catch(error => {
console.error('Save error:', error);
showExecutionStatus('Failed to save file!', 'error');
saveBtn.innerHTML = originalText;
saveBtn.disabled = false;
});
}
function executeFile() {
<?php if (!$isExecutable): ?>
showExecutionStatus('This file type is not executable', 'error');
return;
<?php endif; ?>
updateEditorContent();
const playBtn = document.getElementById('playBtn');
const originalContent = playBtn.innerHTML;
playBtn.innerHTML = '<span>⏳</span><span>Executing...</span>';
playBtn.classList.add('executing');
playBtn.disabled = true;
showExecutionStatus('Saving and executing file...', 'info');
const form = document.getElementById('executeFileForm');
const formData = new FormData(form);
fetch(window.location.href, {
method: 'POST',
body: formData
})
.then(response => {
if (response.redirected) {
showExecutionStatus('Opening Console Monitor...', 'success');
setTimeout(() => {
window.location.href = response.url;
}, 1000);
} else {
return response.text();
}
})
.then(html => {
if (html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const messageEl = doc.querySelector('.message');
const messageText = messageEl ? messageEl.textContent : 'Execution completed';
const messageType = messageEl ? messageEl.classList.contains('success') ? 'success' : 'error' : 'info';
showExecutionStatus(messageText, messageType);
}
playBtn.innerHTML = originalContent;
playBtn.classList.remove('executing');
playBtn.disabled = false;
})
.catch(error => {
console.error('Execution error:', error);
showExecutionStatus('Failed to execute file!', 'error');
playBtn.innerHTML = originalContent;
playBtn.classList.remove('executing');
playBtn.disabled = false;
});
}
const saveForm = document.getElementById('saveFileForm');
if (saveForm) {
saveForm.addEventListener('submit', function(e) {
e.preventDefault();
saveFile();
});
}
// Keyboard shortcuts
editor.commands.addCommand({
name: 'save',
bindKey: {win: 'Ctrl-S', mac: 'Command-S'},
exec: function(editor) {
saveFile();
}
});
<?php if ($isExecutable): ?>
editor.commands.addCommand({
name: 'run',
bindKey: {win: 'Ctrl-R', mac: 'Command-R'},
exec: function(editor) {
executeFile();
}
});
<?php endif; ?>
const findInput = document.getElementById('findInput');
const findMode = document.getElementById('findMode');
const findPrevBtn = document.querySelector('.find-prev');
const findNextBtn = document.querySelector('.find-next');
let currentSearchTerm = '';
let currentMatches = [];
let currentMatchIndex = -1;
let searchTimeout = null;
function updateFindButtons() {
const hasSearch = currentSearchTerm.length > 0;
findPrevBtn.disabled = !hasSearch;
findNextBtn.disabled = !hasSearch;
}
function findFunctions(searchTerm) {
const content = editor.getValue();
const lines = content.split('\n');
const matches = [];
const functionPatterns = [
/^\s*(?:function\s+|const\s+|let\s+|var\s+)?(\w*\s*(?:=\s*)?(?:function\s*)?(?:\([^)]*\)\s*)?(?:=>\s*)?{)/,
/^\s*(?:public\s+|private\s+|protected\s+|static\s+)*function\s+(\w+)\s*\([^)]*\)/,
/^\s*def\s+(\w+)\s*\([^)]*\):/,
/^\s*(?:public\s+|private\s+|protected\s+|static\s+)*(?:\w+\s+)*(\w+)\s*\([^)]*\)\s*{?/,
/^\s*([.#]?[\w-]+(?:\s*[,\s]\s*[.#]?[\w-]+)*)\s*{/
];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
for (let pattern of functionPatterns) {
const match = line.match(pattern);
if (match && match[1] && match[1].toLowerCase().includes(searchTerm.toLowerCase())) {
let braceCount = 0;
let endLine = i;
let foundOpenBrace = false;
for (let j = i; j < lines.length; j++) {
const currentLine = lines[j];
for (let char of currentLine) {
if (char === '{') {
braceCount++;
foundOpenBrace = true;
} else if (char === '}') {
braceCount--;
if (foundOpenBrace && braceCount === 0) {
endLine = j;
break;
}
}
}
if (foundOpenBrace && braceCount === 0) {
break;
}
if (pattern.source.includes('def\\s+') && j > i) {
const currentIndent = currentLine.match(/^\s*/)[0].length;
const funcIndent = lines[i].match(/^\s*/)[0].length;
if (currentLine.trim() && currentIndent <= funcIndent) {
endLine = j - 1;
break;
}
}
}
matches.push({
name: match[1],
startLine: i,
endLine: endLine,
startColumn: 0,
endColumn: lines[endLine] ? lines[endLine].length : 0
});
break;
}
}
}
return matches;
}
function clearAllHighlights() {
editor.findAll('');
if (editor._functionMarkers) {
editor._functionMarkers.forEach(id => editor.session.removeMarker(id));
editor._functionMarkers = [];
}
}
function selectFunction(match) {
clearAllHighlights();
const Range = ace.require('ace/range').Range;
const range = new Range(match.startLine, match.startColumn, match.endLine, match.endColumn);
const markerId = editor.session.addMarker(range, "ace_selected_function", "background");
editor._functionMarkers = [markerId];
editor.selection.setRange(range, false);
editor.scrollToLine(match.startLine, true, true, function() {});
setTimeout(() => {
editor.selection.setRange(range, false);
}, 50);
}
function performSearch() {
const searchTerm = findInput.value.trim();
const mode = findMode.value;
currentSearchTerm = searchTerm;
if (!searchTerm) {
clearAllHighlights();
editor.selection.clearSelection();
currentMatches = [];
currentMatchIndex = -1;
updateFindButtons();
return;
}
if (mode === 'function') {
currentMatches = findFunctions(searchTerm);
if (currentMatches.length > 0) {
currentMatchIndex = 0;
selectFunction(currentMatches[0]);
} else {
clearAllHighlights();
editor.selection.clearSelection();
}
} else {
clearAllHighlights();
editor.find(searchTerm, {
backwards: false,
wrap: true,
caseSensitive: false,
wholeWord: false,
regExp: false
});
currentMatches = [];
currentMatchIndex = -1;
}
updateFindButtons();
}
function debouncedSearch() {
if (searchTimeout) {
clearTimeout(searchTimeout);
}
searchTimeout = setTimeout(performSearch, 300);
}
findInput.addEventListener('input', function() {
if (findMode.value === 'function') {
debouncedSearch();
} else {
performSearch();
}
});
findMode.addEventListener('change', performSearch);
findInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
if (currentSearchTerm) {
if (e.shiftKey) {
findPrevious();
} else {
findNext();
}
}
} else if (e.key === 'Escape') {
this.value = '';
currentSearchTerm = '';
currentMatches = [];
currentMatchIndex = -1;
clearAllHighlights();
editor.selection.clearSelection();
if (searchTimeout) {
clearTimeout(searchTimeout);
searchTimeout = null;
}
editor.focus();
updateFindButtons();
}
});
function findNext() {
if (findMode.value === 'function' && currentMatches.length > 0) {
currentMatchIndex = (currentMatchIndex + 1) % currentMatches.length;
selectFunction(currentMatches[currentMatchIndex]);
} else if (findMode.value === 'normal' && currentSearchTerm) {
editor.findNext();
}
}
function findPrevious() {
if (findMode.value === 'function' && currentMatches.length > 0) {
currentMatchIndex = currentMatchIndex <= 0 ? currentMatches.length - 1 : currentMatchIndex - 1;
selectFunction(currentMatches[currentMatchIndex]);
} else if (findMode.value === 'normal' && currentSearchTerm) {
editor.findPrevious();
}
}
findPrevBtn.addEventListener('click', findPrevious);
findNextBtn.addEventListener('click', findNext);
editor.commands.addCommand({
name: 'find',
bindKey: {win: 'Ctrl-F', mac: 'Command-F'},
exec: function(editor) {
findInput.focus();
findInput.select();
}
});
updateFindButtons();
if (window.innerWidth <= 768) {
editor.setOptions({
fontSize: 13,
scrollPastEnd: 0.2
});
}
if (window.innerWidth <= 480) {
editor.setOptions({
fontSize: 12
});
}
window.addEventListener('orientationchange', function() {
setTimeout(function() {
editor.resize();
}, 100);
});
// Auto-save functionality (optional)
let autoSaveTimeout;
editor.session.on('change', function() {
if (autoSaveTimeout) {
clearTimeout(autoSaveTimeout);
}
// Auto-save after 3 seconds of inactivity
autoSaveTimeout = setTimeout(() => {
updateEditorContent();
// Uncomment the next line to enable auto-save
// saveFile();
}, 3000);
});
<?php endif; ?>
// Global keyboard shortcuts
document.addEventListener('keydown', function(e) {
if (e.ctrlKey || e.metaKey) {
switch (e.key) {
<?php if ($isExecutable): ?>
case 'r':
case 'R':
e.preventDefault();
executeFile();
break;
<?php endif; ?>
case 'Enter':
if (e.shiftKey) {
e.preventDefault();
executeFile();
}
break;
}
}
});
// Initialize
<?php if ($isEditable): ?>
// Focus editor on load
setTimeout(() => {
editor.focus();
}, 100);
<?php endif; ?>
</script>
</body>
</html>