Compare commits
53 Commits
0eed524575
...
main
Author | SHA1 | Date | |
---|---|---|---|
73f25aae1c | |||
3c3a5d2f50 | |||
d44e7d4e51 | |||
60289953ec | |||
df5f2d481a | |||
2ede33f3c2 | |||
822d778008 | |||
90c8c9b3ee | |||
79adb86d25 | |||
a0914e8096 | |||
a4d602f98a | |||
d6f766753c | |||
ae60905eb4 | |||
080cc22841 | |||
c5fdab59f6 | |||
ab17e48338 | |||
ec841445ed | |||
41401f7257 | |||
766e70d766 | |||
9e01628ffb | |||
27c4cda01a | |||
7b4529e8f3 | |||
23d9bb24f2 | |||
330817a745 | |||
f647aba153 | |||
a99cbc4cad | |||
ad5d2c9e92 | |||
857f0ded57 | |||
e64a18b18b | |||
5aa6c7ca10 | |||
d0c73fe4da | |||
f94ac677a9 | |||
051e42bfa3 | |||
a6b0bda263 | |||
289c061ab6 | |||
96eaa64b2a | |||
de2bb27b6d | |||
c99013df04 | |||
d1dbb7efcc | |||
d7c8ea1902 | |||
9915b98700 | |||
ad92bfc648 | |||
5965bb7552 | |||
dae0cd9516 | |||
d354a940c4 | |||
52a3785168 | |||
598a66f517 | |||
4193d46d0d | |||
41f6ea5854 | |||
3a29e0dcad | |||
c599013af4 | |||
5c19d82834 | |||
3ba44c43d8 |
@@ -34,7 +34,7 @@
|
||||
},
|
||||
{
|
||||
"glob":"**/*",
|
||||
"input":"node_modules/ace-builds/src-noconflict",
|
||||
"input":"node_modules/ace-builds/src-min-noconflict",
|
||||
"output":"ace"
|
||||
}
|
||||
],
|
||||
@@ -44,9 +44,11 @@
|
||||
"node_modules/bootstrap/scss/bootstrap.scss",
|
||||
"node_modules/bootstrap-icons/font/bootstrap-icons.css",
|
||||
"src/assets/css/styles.css",
|
||||
"src/assets/css/overrides.css"
|
||||
"src/assets/css/ace-overrides.css"
|
||||
],
|
||||
"scripts":[
|
||||
"src/libs/showdown.min.js"
|
||||
],
|
||||
"optimization": true
|
||||
},
|
||||
@@ -64,7 +66,7 @@
|
||||
"maximumError":"8kB"
|
||||
}
|
||||
],
|
||||
"optimization":false
|
||||
"optimization": true
|
||||
},
|
||||
"development":{
|
||||
"outputHashing": "all",
|
||||
@@ -110,7 +112,6 @@
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts":[
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,10 @@
|
||||
const { app } = require('electron');
|
||||
|
||||
|
||||
let startType = "build";
|
||||
let isDebug = false;
|
||||
let args = [];
|
||||
let startType = "build";
|
||||
let ipcPort = "4563";
|
||||
let isDebug = false;
|
||||
let args = [];
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +18,13 @@ const loadKWArgs = () => {
|
||||
console.log(startType);
|
||||
}
|
||||
|
||||
const hasIpcPort = app.commandLine.hasSwitch("ipc-port");
|
||||
if (hasIpcPort) {
|
||||
ipcPort = app.commandLine.getSwitchValue("ipc-port");
|
||||
console.log("Has ipc-port switch...");
|
||||
console.log(ipcPort);
|
||||
}
|
||||
|
||||
const hasDebug = app.commandLine.hasSwitch("app-debug");
|
||||
if (hasDebug) {
|
||||
isDebug = app.commandLine.getSwitchValue("app-debug");
|
||||
@@ -25,38 +33,29 @@ const loadKWArgs = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadVArgs = () => {
|
||||
console.log("\n\nStart VArgs:");
|
||||
|
||||
const filterOutLaunchAndKWArgs = () => {
|
||||
if (
|
||||
process.argv[0].endsWith("electron")
|
||||
) {
|
||||
process.argv = process.argv.slice(2);
|
||||
}
|
||||
|
||||
if (
|
||||
process.argv[0].endsWith("/newton") ||
|
||||
process.argv[0].endsWith(".AppImage")
|
||||
) {
|
||||
do {
|
||||
process.argv = process.argv.slice(1);
|
||||
}
|
||||
|
||||
if ( process.argv.length > 0 && (
|
||||
process.argv[0].includes("--trace-warnings") ||
|
||||
process.argv[0].includes("--start-as")
|
||||
} while (
|
||||
process.argv.length > 0 &&
|
||||
(
|
||||
process.argv[0].endsWith("/newton") ||
|
||||
process.argv[0].endsWith(".AppImage") ||
|
||||
process.argv[0].includes("--trace-warnings") ||
|
||||
process.argv[0].includes("--start-as") ||
|
||||
process.argv[0].includes("--ipc-port")
|
||||
)
|
||||
) {
|
||||
process.argv = process.argv.slice(1);
|
||||
}
|
||||
|
||||
if ( process.argv.length > 0 && (
|
||||
process.argv[0].includes("--trace-warnings") ||
|
||||
process.argv[0].includes("--start-as")
|
||||
)
|
||||
) {
|
||||
process.argv = process.argv.slice(1);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const loadVArgs = () => {
|
||||
console.log("\n\nStart VArgs:");
|
||||
args = process.argv;
|
||||
args.forEach((val, index, array) => {
|
||||
console.log(index + ': ' + val);
|
||||
@@ -67,6 +66,7 @@ const loadVArgs = () => {
|
||||
|
||||
const loadArgs = () => {
|
||||
loadKWArgs();
|
||||
filterOutLaunchAndKWArgs();
|
||||
loadVArgs();
|
||||
}
|
||||
|
||||
@@ -79,16 +79,21 @@ const getStartType = () => {
|
||||
return startType;
|
||||
}
|
||||
|
||||
const debugMode = () => {
|
||||
const getDebugMode = () => {
|
||||
return isDebug;
|
||||
}
|
||||
|
||||
const getIpcPort = () => {
|
||||
return ipcPort;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
argsParser: {
|
||||
loadArgs: loadArgs,
|
||||
getArgs: getArgs,
|
||||
getStartType: getStartType,
|
||||
debugMode: debugMode,
|
||||
getDebugMode: getDebugMode,
|
||||
getIpcPort: getIpcPort,
|
||||
}
|
||||
};
|
145
newton/fs.js
145
newton/fs.js
@@ -5,13 +5,15 @@ const os = require('os');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
|
||||
const HOME_DIR = os.homedir();
|
||||
const BASE_PATH = '../build/app';
|
||||
const CONFIG_PATH = path.join(HOME_DIR, "/.config/newton/");
|
||||
const SETTINGS_CONFIG_PATH = path.join(CONFIG_PATH, "/settings.json");
|
||||
const LSP_CONFIG_PATH = path.join(BASE_PATH, "/resources/lsp-servers-config.json");
|
||||
let window = null;
|
||||
let watcher = null;
|
||||
const HOME_DIR = os.homedir();
|
||||
const BASE_PATH = '../build/app';
|
||||
const CONFIG_PATH = path.join(HOME_DIR, "/.config/newton/");
|
||||
const SETTINGS_CONFIG_PATH = path.join(CONFIG_PATH, "/settings.json");
|
||||
const LSP_CONFIG_PATH = path.join(BASE_PATH, "/resources/lsp-servers-config.json");
|
||||
let window = null;
|
||||
let watcher = null;
|
||||
let skipOnceFileWatchChange = false;
|
||||
|
||||
|
||||
|
||||
const getIconPath = () => {
|
||||
@@ -19,25 +21,35 @@ const getIconPath = () => {
|
||||
}
|
||||
|
||||
const getSettingsConfigData = () => {
|
||||
return getFileContents(SETTINGS_CONFIG_PATH);
|
||||
return getFileContents(
|
||||
SETTINGS_CONFIG_PATH,
|
||||
useRelativePath = false,
|
||||
watchFile = false
|
||||
);
|
||||
}
|
||||
|
||||
const getLspConfigData = () => {
|
||||
const config = getFileContents(LSP_CONFIG_PATH, useRelativePath = true);
|
||||
return config.replaceAll("{user.home}", HOME_DIR);
|
||||
return getFileContents(
|
||||
LSP_CONFIG_PATH,
|
||||
useRelativePath = true,
|
||||
watchFile = false
|
||||
).replaceAll("{user.home}", HOME_DIR);
|
||||
}
|
||||
|
||||
const getFileContents = (_path, useRelativePath = false) => {
|
||||
const getFileContents = (_path, useRelativePath = false, watchFile = true) => {
|
||||
console.log(`Getting Contents For: ${_path}`);
|
||||
|
||||
try {
|
||||
if (!useRelativePath) {
|
||||
return fs.readFileSync(_path, 'utf8');
|
||||
} else {
|
||||
let fpath = path.join(__dirname, _path);
|
||||
watcher.add(fpath);
|
||||
return fs.readFileSync(fpath, 'utf8');
|
||||
}
|
||||
if (useRelativePath)
|
||||
return fs.readFileSync(
|
||||
path.join(__dirname, _path),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
if (watchFile)
|
||||
watcher.add(_path);
|
||||
|
||||
return fs.readFileSync(_path, 'utf8');
|
||||
} catch(err) {
|
||||
return `{"message": {"type": "error", "text": "Error: Could not read ${_path}"}}`;
|
||||
}
|
||||
@@ -52,21 +64,58 @@ const saveSettingsConfigData = (data) => {
|
||||
}
|
||||
|
||||
const saveFile = (fpath, content) => {
|
||||
fs.writeFile(fpath, content, (err) => {
|
||||
if (!err) return
|
||||
console.error("An error ocurred writing to the file " + err.message);
|
||||
});
|
||||
}
|
||||
skipOnceFileWatchChange = true;
|
||||
|
||||
const saveFileAs = (content) => {
|
||||
dialog.showSaveDialog().then((response) => {
|
||||
if (response.canceled) {
|
||||
console.log("You didn't save the file");
|
||||
fs.writeFile(fpath, content, (err) => {
|
||||
if (err) {
|
||||
console.error("An error ocurred writing to the file " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
saveFile(response.filePath, content);
|
||||
watcher.add(response.filePath);
|
||||
let parentDir = path.dirname(fpath);
|
||||
let watchers = watcher.getWatched();
|
||||
let targetDir = watchers[parentDir];
|
||||
if (
|
||||
!targetDir || !targetDir.includes( path.basename(fpath) )
|
||||
) {
|
||||
skipOnceFileWatchChange = false;
|
||||
watcher.add(fpath);
|
||||
window.webContents.send('update-file-path', fpath);
|
||||
}
|
||||
|
||||
try {
|
||||
window.webContents.send('file-saved', fpath);
|
||||
} catch(e) {}
|
||||
});
|
||||
}
|
||||
|
||||
const saveFileAs = () => {
|
||||
return dialog.showSaveDialog().then((response) => {
|
||||
if (response.canceled) {
|
||||
console.debug("You didn't save the file");
|
||||
return;
|
||||
}
|
||||
|
||||
return response.filePath;
|
||||
});
|
||||
}
|
||||
|
||||
const chooseFolder = () => {
|
||||
return dialog.showOpenDialog(
|
||||
{
|
||||
title: "Choose Folder:",
|
||||
defaultPath: HOME_DIR,
|
||||
properties: [
|
||||
'openDirectory'
|
||||
]
|
||||
}
|
||||
).then((response) => {
|
||||
if (response.canceled) {
|
||||
console.debug("Canceled folder selection...");
|
||||
return "";
|
||||
}
|
||||
|
||||
return response.filePaths[0];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -77,14 +126,21 @@ const openFiles = (startPath) => {
|
||||
defaultPath: (startPath) ? startPath : HOME_DIR,
|
||||
filters: [
|
||||
{ name: "All Files", extensions: ["*"] },
|
||||
{ name: "c", extensions: [".h", ".c"] },
|
||||
{ name: "cpp", extensions: ["hpp", "cpp"] },
|
||||
{ name: "html", extensions: ["js", "css", "scss", "html", "ts"] },
|
||||
{ name: "java", extensions: ["java"] },
|
||||
{ name: "python", extensions: ["py", "pyc"] },
|
||||
{ name: "rust", extensions: ["r", "rc"] },
|
||||
{ name: "text", extensions: ["txt", "log", "md"] },
|
||||
{ name: "go", extensions: ["go"] },
|
||||
{ name: "All Sub Filters",
|
||||
extensions: [
|
||||
"h", "c", "hpp", "cpp", "js", "css", "scss", "html",
|
||||
"ts", "java", "py", "pyc", "txt", "log", "md", "r",
|
||||
"rc", "go"
|
||||
]
|
||||
},
|
||||
{ name: "C", extensions: ["h", "c"] },
|
||||
{ name: "CPP", extensions: ["hpp", "cpp"] },
|
||||
{ name: "HTML", extensions: ["js", "css", "scss", "html", "ts"] },
|
||||
{ name: "Java", extensions: ["java"] },
|
||||
{ name: "Python", extensions: ["py", "pyc"] },
|
||||
{ name: "Text", extensions: ["txt", "log", "md"] },
|
||||
{ name: "Rust", extensions: ["r", "rc"] },
|
||||
{ name: "Go", extensions: ["go"] }
|
||||
],
|
||||
properties: [
|
||||
'openFile',
|
||||
@@ -93,7 +149,7 @@ const openFiles = (startPath) => {
|
||||
}
|
||||
).then((response) => {
|
||||
if (response.canceled) {
|
||||
console.log("Canceled file open request...");
|
||||
console.debug("Canceled file(s) open request...");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,14 +162,22 @@ const loadFilesWatcher = () => {
|
||||
watcher = chokidar.watch([], {});
|
||||
|
||||
watcher.on('change', (fpath) => {
|
||||
if (skipOnceFileWatchChange) {
|
||||
skipOnceFileWatchChange = false;
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug("File (changed) : ", fpath);
|
||||
const data = getFileContents(fpath, false, false);
|
||||
window.webContents.send('file-changed', fpath, data);
|
||||
}).on('unlink', (fpath) => {
|
||||
console.debug("File (unlinked) : ", fpath);
|
||||
console.debug("File (deleted) : ", fpath);
|
||||
window.webContents.send('file-deleted', fpath);
|
||||
});
|
||||
}
|
||||
|
||||
const unwatchFile = async (fpath) => {
|
||||
console.log("File (unwatch) : ", fpath);
|
||||
console.debug("File (unwatch) : ", fpath);
|
||||
await watcher.unwatch(fpath);
|
||||
}
|
||||
|
||||
@@ -126,6 +190,7 @@ const closeFile = (fpath) => {
|
||||
module.exports = {
|
||||
newtonFs: {
|
||||
setWindow: setWindow,
|
||||
chooseFolder: chooseFolder,
|
||||
openFiles: openFiles,
|
||||
saveFile: saveFile,
|
||||
saveFileAs: saveFileAs,
|
||||
|
@@ -8,12 +8,17 @@ const fetch = require('electron-fetch').default
|
||||
const IPC_SERVER_IP = "127.0.0.1";
|
||||
let window = null;
|
||||
let ipcServer = null;
|
||||
let ipcServerPort = "4563";
|
||||
let ipcServerURL = `http://${IPC_SERVER_IP}:${ipcServerPort}`;
|
||||
let ipcServerPort = "";
|
||||
let ipcServerURL = "";
|
||||
|
||||
|
||||
const setWindow = (win) => {
|
||||
window = win;
|
||||
window = win;
|
||||
}
|
||||
|
||||
const configure = (ipcPort) => {
|
||||
ipcServerPort = ipcPort;
|
||||
ipcServerURL = `http://${IPC_SERVER_IP}:${ipcServerPort}`;
|
||||
}
|
||||
|
||||
const loadIPCServer = (fpath) => {
|
||||
@@ -47,7 +52,8 @@ const loadIPCServer = (fpath) => {
|
||||
}
|
||||
|
||||
const isIPCServerUp = async () => {
|
||||
const response = await fetch(`${ipcServerURL}/is-up`).catch((err) => {
|
||||
const response = await fetch(`${ipcServerURL}/is-up`)
|
||||
.catch((err) => {
|
||||
console.debug("IPCServer (status) : Not up; okay to start.");
|
||||
return {
|
||||
text: () => {
|
||||
@@ -73,9 +79,10 @@ const sendFilesToIPC = async (files) => {
|
||||
|
||||
module.exports = {
|
||||
newtonIPC: {
|
||||
setWindow: setWindow,
|
||||
configure: configure,
|
||||
loadIPCServer: loadIPCServer,
|
||||
isIPCServerUp: isIPCServerUp,
|
||||
sendFilesToIPC: sendFilesToIPC,
|
||||
setWindow: setWindow
|
||||
}
|
||||
};
|
@@ -16,7 +16,7 @@ let hasExitSaved = false;
|
||||
const createWindow = () => {
|
||||
window = newton.createWindow(
|
||||
newton.args.getStartType(),
|
||||
newton.args.debugMode(),
|
||||
newton.args.getDebugMode(),
|
||||
newton.args.getArgs(),
|
||||
);
|
||||
}
|
||||
@@ -65,18 +65,22 @@ const loadProcessSignalHandlers = () => {
|
||||
}
|
||||
|
||||
const loadHandlers = () => {
|
||||
ipcMain.handle('quit', (eve) => app.quit());
|
||||
ipcMain.handle('toggleFullScreen', (eve) => { window.setFullScreen(!window.isFullScreen()); });
|
||||
ipcMain.handle('getLspConfigData', (eve) => newton.fs.getLspConfigData());
|
||||
ipcMain.handle('getFileContents', (eve, path) => newton.fs.getFileContents(path));
|
||||
ipcMain.handle('openFiles', (eve, startPath) => newton.fs.openFiles(startPath));
|
||||
ipcMain.handle('saveFile', (eve, path, content) => newton.fs.saveFile(path, content));
|
||||
ipcMain.handle('closeFile', (eve, path) => newton.fs.closeFile(path));
|
||||
ipcMain.handle('saveFileAs', (eve, content) => newton.fs.saveFileAs(content));
|
||||
ipcMain.handle('saveFileAs', (eve) => newton.fs.saveFileAs());
|
||||
ipcMain.handle('chooseFolder', (eve) => newton.fs.chooseFolder());
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
loadProcessSignalHandlers();
|
||||
newton.args.loadArgs();
|
||||
|
||||
newton.ipc.configure( newton.args.getIpcPort() );
|
||||
if ( !await newton.ipc.isIPCServerUp() ) {
|
||||
newton.ipc.loadIPCServer();
|
||||
} else {
|
||||
@@ -87,7 +91,7 @@ app.whenReady().then(async () => {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
newton.settings.loadsettings();
|
||||
newton.settings.loadSettings();
|
||||
newton.fs.loadFilesWatcher();
|
||||
|
||||
loadHandlers();
|
||||
@@ -108,5 +112,4 @@ app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
};
|
||||
});
|
||||
|
||||
});
|
@@ -22,6 +22,9 @@ const load = (win) => {
|
||||
}, {
|
||||
label: 'Terminal',
|
||||
click: () => {}
|
||||
}, {
|
||||
label: "Quit",
|
||||
click: () => win.webContents.send('menu-actions', "quit")
|
||||
}
|
||||
]
|
||||
}, {
|
||||
|
@@ -9,6 +9,8 @@ contextBridge.exposeInMainWorld('electron', {
|
||||
|
||||
contextBridge.exposeInMainWorld('main', {
|
||||
onMenuActions: (callback) => ipcRenderer.on('menu-actions', (_event, action) => callback(action)),
|
||||
quit: () => ipcRenderer.invoke("quit"),
|
||||
toggleFullScreen: () => ipcRenderer.invoke("toggleFullScreen"),
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('fs', {
|
||||
@@ -16,8 +18,13 @@ contextBridge.exposeInMainWorld('fs', {
|
||||
getFileContents: (path) => ipcRenderer.invoke("getFileContents", path),
|
||||
openFiles: (startPath) => ipcRenderer.invoke("openFiles", startPath),
|
||||
saveFile: (path, content) => ipcRenderer.invoke("saveFile", path, content),
|
||||
saveFileAs: (content) => ipcRenderer.invoke("saveFileAs", content),
|
||||
saveFileAs: () => ipcRenderer.invoke("saveFileAs"),
|
||||
chooseFolder: () => ipcRenderer.invoke("chooseFolder"),
|
||||
closeFile: (path) => ipcRenderer.invoke("closeFile", path),
|
||||
getPathForFile: (file) => webUtils.getPathForFile(file),
|
||||
onLoadFiles: (callback) => ipcRenderer.on('load-files', (_event, paths) => callback(paths)),
|
||||
onUpdateFilePath: (callback) => ipcRenderer.on('update-file-path', (_event, paths) => callback(paths)),
|
||||
onSavedFile: (callback) => ipcRenderer.on('file-saved', (_event, path) => callback(path)),
|
||||
onChangedFile: (callback) => ipcRenderer.on('file-changed', (_event, path, data) => callback(path, data)),
|
||||
onDeletedFile: (callback) => ipcRenderer.on('file-deleted', (_event, path) => callback(path)),
|
||||
});
|
@@ -4,7 +4,7 @@ const { newtonFs } = require('./fs');
|
||||
let config = {};
|
||||
|
||||
|
||||
const loadsettings = () => {
|
||||
const loadSettings = () => {
|
||||
config = JSON.parse(
|
||||
newtonFs.getSettingsConfigData()
|
||||
);
|
||||
@@ -38,7 +38,7 @@ const getIconPath = () => {
|
||||
module.exports = {
|
||||
settingsManager: {
|
||||
getIconPath: getIconPath,
|
||||
loadsettings: loadsettings,
|
||||
loadSettings: loadSettings,
|
||||
getConfig: getConfig,
|
||||
saveConfig: saveConfig,
|
||||
}
|
||||
|
@@ -33,6 +33,9 @@ const load = (win) => {
|
||||
}, {
|
||||
label: 'Help',
|
||||
click: () => win.webContents.send('menu-actions', "show-about")
|
||||
}, {
|
||||
label: 'Quit',
|
||||
click: () => win.webContents.send('menu-actions', "quit")
|
||||
}
|
||||
];
|
||||
|
||||
|
49
package.json
49
package.json
@@ -4,13 +4,16 @@
|
||||
"version": "0.0.1",
|
||||
"author": "ITDominator",
|
||||
"license": "GPL-2.0-only",
|
||||
"homepage": "https://www.itdominator.com",
|
||||
"email": "1itdominator@gmail.com",
|
||||
"main": "newton/main.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"app": "ng build --base-href ./ && electron . --trace-warnings --start-as=build",
|
||||
"electron-start": "electron . --trace-warnings --start-as=build",
|
||||
"app": "ng build --base-href ./ && electron . --trace-warnings --start-as=build --ipc-port=4588",
|
||||
"electron-start": "electron . --trace-warnings --start-as=build --ipc-port=4588",
|
||||
"electron-pack": "ng build --base-href ./ && electron-builder --dir",
|
||||
"electron-dist": "ng build --base-href ./ && electron-builder",
|
||||
"electron-dist-linux": "ng build --base-href ./ && electron-builder --linux deb zip AppImage",
|
||||
"electron-dist-all": "ng build --base-href ./ && electron-builder -mwl",
|
||||
"electron-concurrently": "concurrently 'ng serve' 'electron . --trace-warnings --start-as=ng-serve'",
|
||||
"ng-serve": "ng serve",
|
||||
@@ -24,7 +27,10 @@
|
||||
"icon": "./icos/",
|
||||
"files": [
|
||||
"newton/",
|
||||
"build/"
|
||||
"build/",
|
||||
"!node_modules/ace-builds/",
|
||||
"!node_modules/web-streams-polyfill/",
|
||||
"!node_modules/@angular/"
|
||||
],
|
||||
"mac": {
|
||||
"category": "public.app-category.developer-tools"
|
||||
@@ -33,8 +39,9 @@
|
||||
"target": "portable"
|
||||
},
|
||||
"linux": {
|
||||
"target": "AppImage",
|
||||
"category": "Development"
|
||||
"target": "zip",
|
||||
"category": "Development",
|
||||
"maintainer": "ITDominator"
|
||||
}
|
||||
},
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
@@ -44,8 +51,10 @@
|
||||
"@angular/core": "19.2.0",
|
||||
"@angular/forms": "19.2.0",
|
||||
"@angular/platform-browser": "19.2.0",
|
||||
"ace-builds": "1.41.0",
|
||||
"ace-linters": "1.5.3",
|
||||
"ace-builds": "1.43.0",
|
||||
"ace-diff": "3.0.3",
|
||||
"ace-layout": "1.5.0",
|
||||
"ace-linters": "1.8.3",
|
||||
"bootstrap": "5.3.6",
|
||||
"bootstrap-icons": "1.12.1",
|
||||
"chokidar": "4.0.3",
|
||||
@@ -73,7 +82,27 @@
|
||||
"karma-coverage": "2.2.0",
|
||||
"karma-jasmine": "5.1.0",
|
||||
"karma-jasmine-html-reporter": "2.1.0",
|
||||
"typescript": "5.7.2",
|
||||
"tslib": "2.3.0"
|
||||
"tree-sitter": "0.21.1",
|
||||
"tree-sitter-bash": "0.23.2",
|
||||
"tree-sitter-c": "0.23.1",
|
||||
"tree-sitter-cli": "0.25.8",
|
||||
"tree-sitter-cpp": "0.23.4",
|
||||
"tree-sitter-css": "0.23.0",
|
||||
"tree-sitter-go": "0.23.4",
|
||||
"tree-sitter-html": "0.23.2",
|
||||
"tree-sitter-java": "0.23.5",
|
||||
"tree-sitter-javascript": "0.23.1",
|
||||
"tree-sitter-json": "0.24.8",
|
||||
"tree-sitter-lua": "2.1.3",
|
||||
"tree-sitter-php": "0.23.12",
|
||||
"tree-sitter-python": "0.23.2",
|
||||
"tree-sitter-r": "0.0.1-security",
|
||||
"tree-sitter-sql": "0.1.0",
|
||||
"tree-sitter-sqlite": "0.0.1-security",
|
||||
"tree-sitter-toml": "0.5.1",
|
||||
"tree-sitter-typescript": "0.23.2",
|
||||
"tree-sitter-yaml": "0.5.0",
|
||||
"tslib": "2.3.0",
|
||||
"typescript": "5.7.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
public/imgs/only-in-selection.png
Normal file
BIN
public/imgs/only-in-selection.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
BIN
public/imgs/whole-word.png
Normal file
BIN
public/imgs/whole-word.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
185
public/js/theme-penguins_in_space.js
Normal file
185
public/js/theme-penguins_in_space.js
Normal file
@@ -0,0 +1,185 @@
|
||||
ace.define("ace/theme/penguins_in_space-css", ["require", "exports", "module"], function(require, exports, module) {
|
||||
module.exports = """
|
||||
.penguins-in-space .ace_gutter {
|
||||
background: #282c34;
|
||||
color: #6a6f7a;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_print-margin {
|
||||
width: 1px;
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
.penguins-in-space {
|
||||
background-color: #282c34;
|
||||
color: #abb2bf;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_cursor {
|
||||
color: #528bff;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_marker-layer .ace_selection {
|
||||
background: #3d4350;
|
||||
}
|
||||
|
||||
.penguins-in-space.ace_multiselect .ace_selection.ace_start {
|
||||
box-shadow: 0 0 3px 0 #282c34;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_marker-layer .ace_step {
|
||||
background: #c6dbae;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_marker-layer .ace_bracket {
|
||||
margin: -1px 0 0 -1px;
|
||||
border: 1px solid #747369;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_marker-layer .ace_active-line {
|
||||
background: rgba(76,87,103,.19);
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_gutter-active-line {
|
||||
background-color: rgba(76,87,103,.19);
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_marker-layer .ace_selected-word {
|
||||
border: 1px solid #3d4350;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_fold {
|
||||
background-color: #61afef;
|
||||
border-color: #abb2bf;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_keyword {
|
||||
color: #00a8c6;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_keyword.ace_operator {
|
||||
color: #00a8c6;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_keyword.ace_other.ace_unit {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_constant.ace_language {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_constant.ace_numeric {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_constant.ace_character {
|
||||
color: #56b6c2;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_constant.ace_other {
|
||||
color: #56b6c2;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_support.ace_function {
|
||||
color: #61afef;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_support.ace_constant {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_support.ace_class {
|
||||
color: #ff5d38;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_support.ace_type {
|
||||
color: #ff5d38;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_storage {
|
||||
color: #c678dd;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_storage.ace_type {
|
||||
color: #E6DB74;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_invalid {
|
||||
color: #fff;
|
||||
background-color: #f2777a;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_invalid.ace_deprecated {
|
||||
color: #272b33;
|
||||
background-color: #d27b53;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_string {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_string.ace_regexp {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_comment {
|
||||
font-style: italic;
|
||||
color: #454a54;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_variable {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_variable.ace_parameter {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_meta.ace_tag {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_entity.ace_other.ace_attribute-name {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_entity.ace_name.ace_function {
|
||||
color: #61afef;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_entity.ace_name.ace_tag {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_markup.ace_heading {
|
||||
color: #98c379;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_indent-guide {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ09NrYAgMjP4PAAtGAwchHMyAAAAAAElFTkSuQmCC) right repeat-y;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_indent-guide-active {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQIW2PQ1dX9zzBz5sz/ABCcBFFentLlAAAAAElFTkSuQmCC) right repeat-y;
|
||||
}
|
||||
""";
|
||||
|
||||
});
|
||||
|
||||
ace.define("ace/theme/penguins_in_space", ["require", "exports", "module", "ace/theme/penguins_in_space-css", "ace/lib/dom"], function(require, exports, module) {
|
||||
exports.isDark = true;
|
||||
exports.cssClass = "penguins-in-space";
|
||||
exports.cssText = require("./penguins_in_space-css");
|
||||
var dom = require("../lib/dom");
|
||||
dom.importCssString(exports.cssText, exports.cssClass, false);
|
||||
|
||||
});
|
||||
(function() {
|
||||
ace.require(["ace/theme/penguins_in_space"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
@@ -8,62 +8,78 @@
|
||||
"command": "lsp-ws-proxy --listen 4114 -- jdtls",
|
||||
"alt-command": "lsp-ws-proxy -- jdtls",
|
||||
"alt-command2": "java-language-server",
|
||||
"socket": "ws://127.0.0.1:4114/?name=jdtls",
|
||||
"alt-socket": "ws://127.0.0.1:3030/?name=java-language-server",
|
||||
"socket": "ws://127.0.0.1:9999/java",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=jdtls",
|
||||
"alt-socket": "ws://127.0.0.1:9999/?name=java-language-server",
|
||||
"initialization-options": {
|
||||
"bundles": [
|
||||
"intellicode-core.jar"
|
||||
],
|
||||
"workspaceFolders": [
|
||||
"file://"
|
||||
"file://{workspace.folder}"
|
||||
],
|
||||
"extendedClientCapabilities": {
|
||||
"classFileContentsSupport": true,
|
||||
"executeClientCommandSupport": true
|
||||
"executeClientCommandSupport": false
|
||||
},
|
||||
"settings": {
|
||||
"java": {
|
||||
"autobuild": {
|
||||
"enabled": false
|
||||
"enabled": true
|
||||
},
|
||||
"completion": {
|
||||
"enabled": true,
|
||||
"importOrder": [
|
||||
"java",
|
||||
"javax",
|
||||
"org",
|
||||
"com"
|
||||
]
|
||||
"jdt": {
|
||||
"ls": {
|
||||
"javac": {
|
||||
"enabled": true
|
||||
},
|
||||
"java": {
|
||||
"home": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2"
|
||||
},
|
||||
"lombokSupport": {
|
||||
"enabled": true
|
||||
},
|
||||
"protobufSupport":{
|
||||
"enabled": true
|
||||
},
|
||||
"androidSupport": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"updateBuildConfiguration": "automatic",
|
||||
"maven": {
|
||||
"userSettings": "{user.home}/.config/jdtls/settings.xml",
|
||||
"globalSettings": "{user.home}/.config/jdtls/settings.xml"
|
||||
"userSettings": "{user.home}/.config/lsps/jdtls/settings.xml",
|
||||
"globalSettings": "{user.home}/.config/lsps/jdtls/settings.xml"
|
||||
},
|
||||
"runtimes": [
|
||||
{
|
||||
"name": "JavaSE-17",
|
||||
"path": "/usr/lib/jvm/default-runtime",
|
||||
"path": "/usr/lib/jvm/java-17-openjdk",
|
||||
"javadoc": "https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "JavaSE-22",
|
||||
"path": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2",
|
||||
"javadoc": "https://docs.oracle.com/en/java/javase/22/docs/api/",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"classPath": [
|
||||
"{user.home}/.config/jdtls/m2/repository/**/*-sources.jar",
|
||||
"{user.home}/.config/lsps/jdtls/m2/repository/**/*-sources.jar",
|
||||
"lib/**/*-sources.jar"
|
||||
],
|
||||
"docPath": [
|
||||
"{user.home}/.config/jdtls/m2/repository/**/*-javadoc.jar",
|
||||
"{user.home}/.config/lsps/jdtls/m2/repository/**/*-javadoc.jar",
|
||||
"lib/**/*-javadoc.jar"
|
||||
],
|
||||
"silentNotification": true,
|
||||
"project": {
|
||||
"encoding": "ignore",
|
||||
"outputPath": "bin",
|
||||
"referencedLibraries": [
|
||||
"lib/**/*.jar",
|
||||
"{user.home}/.config/jdtls/m2/repository/**/*.jar"
|
||||
"{user.home}/.config/lsps/jdtls/m2/repository/**/*.jar",
|
||||
"lib/**/*.jar"
|
||||
],
|
||||
"importOnFirstTimeStartup": "automatic",
|
||||
"importHint": true,
|
||||
@@ -73,7 +89,7 @@
|
||||
],
|
||||
"sourcePaths": [
|
||||
"src",
|
||||
"{user.home}/.config/jdtls/m2/repository/**/*.jar"
|
||||
"{user.home}/.config/lsps/jdtls/m2/repository/**/*.jar"
|
||||
]
|
||||
},
|
||||
"sources": {
|
||||
@@ -103,9 +119,9 @@
|
||||
"enabled": true
|
||||
},
|
||||
"version": "",
|
||||
"home": "abs(static/gradle-7.3.3)",
|
||||
"home": "{user.home}/Portable_Apps/sdks/gradle/gradle-9.0.0",
|
||||
"java": {
|
||||
"home": "abs(static/launch_jres/17.0.6-linux-x86_64)"
|
||||
"home": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2"
|
||||
},
|
||||
"offline": {
|
||||
"enabled": false
|
||||
@@ -131,14 +147,65 @@
|
||||
"downloadSources": true,
|
||||
"updateSnapshots": true
|
||||
},
|
||||
"silentNotification": true,
|
||||
"contentProvider": {
|
||||
"preferred": "fernflower"
|
||||
},
|
||||
"signatureHelp": {
|
||||
"enabled": true,
|
||||
"description": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"completion": {
|
||||
"enabled": true,
|
||||
"matchCase": "firstletter",
|
||||
"maxResults": 25,
|
||||
"guessMethodArguments": true,
|
||||
"lazyResolveTextEdit": {
|
||||
"enabled": true
|
||||
},
|
||||
"postfix": {
|
||||
"enabled": true
|
||||
},
|
||||
"favoriteStaticMembers": [
|
||||
"org.junit.Assert.*",
|
||||
"org.junit.Assume.*",
|
||||
"org.junit.jupiter.api.Assertions.*",
|
||||
"org.junit.jupiter.api.Assumptions.*",
|
||||
"org.junit.jupiter.api.DynamicContainer.*",
|
||||
"org.junit.jupiter.api.DynamicTest.*"
|
||||
],
|
||||
"importOrder": [
|
||||
"#",
|
||||
"java",
|
||||
"javax",
|
||||
"org",
|
||||
"com"
|
||||
]
|
||||
},
|
||||
"references": {
|
||||
"includeAccessors": true,
|
||||
"includeDecompiledSources": true
|
||||
},
|
||||
"codeGeneration": {
|
||||
"toString": {
|
||||
"template": "${object.className}{${member.name()}=${member.value}, ${otherMembers}}"
|
||||
},
|
||||
"insertionLocation": "afterCursor",
|
||||
"useBlocks": true
|
||||
},
|
||||
"implementationsCodeLens": {
|
||||
"enabled": true
|
||||
},
|
||||
"referencesCodeLens": {
|
||||
"enabled": true
|
||||
},
|
||||
"progressReports": {
|
||||
"enabled": false
|
||||
},
|
||||
"saveActions": {
|
||||
"organizeImports": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,53 +217,39 @@
|
||||
"alt-command": "pylsp",
|
||||
"alt-command2": "lsp-ws-proxy --listen 4114 -- pylsp",
|
||||
"alt-command3": "pylsp --ws --port 4114",
|
||||
"socket": "ws://127.0.0.1:9999/?name=pylsp",
|
||||
"socket": "ws://127.0.0.1:9999/python",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=pylsp",
|
||||
"initialization-options": {
|
||||
"pyls": {
|
||||
"plugins": {
|
||||
"pycodestyle": {
|
||||
"enabled": false
|
||||
},
|
||||
"pydocstyle": {
|
||||
"enabled": false
|
||||
},
|
||||
"pyflakes": {
|
||||
"enabled": false
|
||||
},
|
||||
"pylint": {
|
||||
"enabled": false
|
||||
},
|
||||
"mccabe": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"pylsp": {
|
||||
"rope": {
|
||||
"ropeFolder": "{user.home}/.config/lsps/ropeproject"
|
||||
},
|
||||
"plugins": {
|
||||
"pycodestyle": {
|
||||
"enabled": false
|
||||
"ruff": {
|
||||
"enabled": true,
|
||||
"extendSelect": ["I"],
|
||||
"lineLength": 80
|
||||
},
|
||||
"pydocstyle": {
|
||||
"pycodestyle": {
|
||||
"enabled": false
|
||||
},
|
||||
"pyflakes": {
|
||||
"enabled": false
|
||||
},
|
||||
"pylint": {
|
||||
"enabled": false
|
||||
"enabled": true
|
||||
},
|
||||
"mccabe": {
|
||||
"enabled": false
|
||||
},
|
||||
"ruff": true,
|
||||
"pylsp_rope": {
|
||||
"rename": true
|
||||
"rename": false
|
||||
},
|
||||
"rope_rename": {
|
||||
"enabled": true
|
||||
"enabled": false
|
||||
},
|
||||
"rope_autoimport": {
|
||||
"enabled": true
|
||||
"enabled": false
|
||||
},
|
||||
"rope_completion": {
|
||||
"enabled": false,
|
||||
@@ -209,13 +262,13 @@
|
||||
"enabled": true,
|
||||
"include_class_objects": true,
|
||||
"include_function_objects": true,
|
||||
"fuzzy": true
|
||||
"fuzzy": false
|
||||
},
|
||||
"jedi":{
|
||||
"jedi": {
|
||||
"root_dir": "file://{workspace.folder}",
|
||||
"extra_paths": [
|
||||
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
|
||||
],
|
||||
"root_dir": ""
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,7 +279,8 @@
|
||||
"info": "https://pypi.org/project/jedi-language-server/",
|
||||
"command": "jedi-language-server",
|
||||
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
|
||||
"socket": "ws://127.0.0.1:3030/?name=jedi-language-server",
|
||||
"socket": "ws://127.0.0.1:9999/python",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
|
||||
"initialization-options": {
|
||||
"jediSettings": {
|
||||
"autoImportModules": [],
|
||||
@@ -245,7 +299,13 @@
|
||||
],
|
||||
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
|
||||
"symbols": {
|
||||
"ignoreFolders": [".nox", ".tox", ".venv", "__pycache__", "venv"],
|
||||
"ignoreFolders": [
|
||||
".nox",
|
||||
".tox",
|
||||
".venv",
|
||||
"__pycache__",
|
||||
"venv"
|
||||
],
|
||||
"maxSymbols": 20
|
||||
}
|
||||
}
|
||||
@@ -255,28 +315,8 @@
|
||||
"info": "https://clangd.llvm.org/",
|
||||
"command": "lsp-ws-proxy -- clangd",
|
||||
"alt-command": "clangd",
|
||||
"socket": "ws://127.0.0.1:3030/?name=clangd",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"sh": {
|
||||
"info": "",
|
||||
"command": "",
|
||||
"alt-command": "",
|
||||
"socket": "ws://127.0.0.1:3030/?name=shell",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"go": {
|
||||
"info": "https://pkg.go.dev/golang.org/x/tools/gopls#section-readme",
|
||||
"command": "lsp-ws-proxy -- gopls",
|
||||
"alt-command": "gopls",
|
||||
"socket": "ws://127.0.0.1:3030/?name=gopls",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"lua": {
|
||||
"info": "https://github.com/LuaLS/lua-language-server",
|
||||
"command": "lsp-ws-proxy -- lua-language-server",
|
||||
"alt-command": "lua-language-server",
|
||||
"socket": "ws://127.0.0.1:3030/?name=gopls",
|
||||
"socket": "ws://127.0.0.1:9999/cpp",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=clangd",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"c": {
|
||||
@@ -284,7 +324,40 @@
|
||||
"info": "https://clangd.llvm.org/",
|
||||
"command": "lsp-ws-proxy -- clangd",
|
||||
"alt-command": "clangd",
|
||||
"socket": "ws://127.0.0.1:3030/?name=clangd",
|
||||
"socket": "ws://127.0.0.1:9999/c",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=clangd",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"go": {
|
||||
"info": "https://pkg.go.dev/golang.org/x/tools/gopls#section-readme",
|
||||
"command": "lsp-ws-proxy -- gopls",
|
||||
"alt-command": "gopls",
|
||||
"socket": "ws://127.0.0.1:9999/go",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=gopls",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"typescript": {
|
||||
"info": "https://github.com/typescript-language-server/typescript-language-server",
|
||||
"command": "lsp-ws-proxy -- typescript-language-server",
|
||||
"alt-command": "typescript-language-server --stdio",
|
||||
"socket": "ws://127.0.0.1:9999/typescript",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=ts",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"sh": {
|
||||
"info": "",
|
||||
"command": "",
|
||||
"alt-command": "",
|
||||
"socket": "ws://127.0.0.1:9999/bash",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=shell",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"lua": {
|
||||
"info": "https://github.com/LuaLS/lua-language-server",
|
||||
"command": "lsp-ws-proxy -- lua-language-server",
|
||||
"alt-command": "lua-language-server",
|
||||
"socket": "ws://127.0.0.1:9999/lua",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=lua",
|
||||
"initialization-options": {}
|
||||
}
|
||||
}
|
BIN
public/wasm/tree-sitter-bash.wasm
Normal file
BIN
public/wasm/tree-sitter-bash.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-c.wasm
Normal file
BIN
public/wasm/tree-sitter-c.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-cpp.wasm
Normal file
BIN
public/wasm/tree-sitter-cpp.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-css.wasm
Normal file
BIN
public/wasm/tree-sitter-css.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-go.wasm
Normal file
BIN
public/wasm/tree-sitter-go.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-html.wasm
Normal file
BIN
public/wasm/tree-sitter-html.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-java.wasm
Normal file
BIN
public/wasm/tree-sitter-java.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-javascript.wasm
Normal file
BIN
public/wasm/tree-sitter-javascript.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-json.wasm
Normal file
BIN
public/wasm/tree-sitter-json.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-php.wasm
Normal file
BIN
public/wasm/tree-sitter-php.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-python.wasm
Normal file
BIN
public/wasm/tree-sitter-python.wasm
Normal file
Binary file not shown.
BIN
public/wasm/tree-sitter-typescript.wasm
Normal file
BIN
public/wasm/tree-sitter-typescript.wasm
Normal file
Binary file not shown.
@@ -2,4 +2,8 @@
|
||||
<info-bar></info-bar>
|
||||
<tabs></tabs>
|
||||
<editors></editors>
|
||||
<search-replace></search-replace>
|
||||
<markdown-preview></markdown-preview>
|
||||
|
||||
<lsp-manager></lsp-manager>
|
||||
</div>
|
@@ -3,6 +3,9 @@ import { Component } from '@angular/core';
|
||||
import { InfoBarComponent } from './editor/info-bar/info-bar.component';
|
||||
import { TabsComponent } from './editor/tabs/tabs.component';
|
||||
import { EditorsComponent } from './editor/editors.component';
|
||||
import { SearchReplaceComponent } from "./editor/search-replace/search-replace.component";
|
||||
import { MarkdownPreviewComponent } from "./editor/markdown-preview/markdown-preview.component";
|
||||
import { LspManagerComponent } from "./editor/lsp-manager/lsp-manager.component";
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +14,10 @@ import { EditorsComponent } from './editor/editors.component';
|
||||
imports: [
|
||||
InfoBarComponent,
|
||||
TabsComponent,
|
||||
EditorsComponent
|
||||
EditorsComponent,
|
||||
SearchReplaceComponent,
|
||||
MarkdownPreviewComponent,
|
||||
LspManagerComponent,
|
||||
],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
|
@@ -0,0 +1,28 @@
|
||||
<div #diffModal
|
||||
id="diffModal" class="modal fade" tabindex="-1" role="dialog"
|
||||
>
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Diff:</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
|
||||
<div class="diff-view"></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
inject
|
||||
} from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
|
||||
import AceDiff from 'ace-diff';
|
||||
|
||||
import 'ace-diff/dist/ace-diff.min.css';
|
||||
import 'ace-diff/dist/ace-diff-dark.min.css';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'diff-modal',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
templateUrl: './diff-modal.component.html',
|
||||
styleUrl: './diff-modal.component.css',
|
||||
host: {
|
||||
'class': ''
|
||||
}
|
||||
})
|
||||
export class DiffModalComponent {
|
||||
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
||||
|
||||
diffModal!: bootstrap.Modal;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
|
||||
private ngAfterViewInit(): void {
|
||||
this.loadDiffView();
|
||||
}
|
||||
|
||||
private loadDiffView() {
|
||||
// Notes: https://github.com/ace-diff/ace-diff
|
||||
// https://ajaxorg.github.io/ace-api-docs/classes/src_ext_diff_diff_view.DiffView.html#scrollB
|
||||
/*
|
||||
const differ = new AceDiff({
|
||||
ace: window.ace
|
||||
element: '.diff-view',
|
||||
left: {
|
||||
content: 'your first file content here',
|
||||
},
|
||||
right: {
|
||||
content: 'your second file content here',
|
||||
},
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
}
|
||||
|
||||
private createModal() {
|
||||
this.diffModal = new bootstrap.Modal("#diffModal", {});
|
||||
}
|
||||
|
||||
public showModal() {
|
||||
this.diffModal?.toggle();
|
||||
}
|
||||
|
||||
}
|
@@ -6,10 +6,12 @@ export const EditorSettings: any = {
|
||||
KEYBINDINGS: Keybindings,
|
||||
CONFIG: {
|
||||
behavioursEnabled: true,
|
||||
wrapBehavioursEnabled: true,
|
||||
fontSize: "12px",
|
||||
theme: "ace/theme/one_dark",
|
||||
theme: "ace/theme/gruvbox",
|
||||
mode: "ace/mode/text",
|
||||
printMarginColumn: 80,
|
||||
enableCodeLens: true,
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
enableSnippets: true,
|
||||
@@ -19,11 +21,10 @@ export const EditorSettings: any = {
|
||||
tabSize: 4,
|
||||
navigateWithinSoftTabs: true,
|
||||
tooltipFollowsMouse: true,
|
||||
wrapBehavioursEnabled: false,
|
||||
scrollPastEnd: 0.5,
|
||||
mergeUndoDeltas: false,
|
||||
showGutter: true,
|
||||
customScrollbar: true,
|
||||
// customScrollbar: true,
|
||||
scrollSpeed: 5
|
||||
}
|
||||
};
|
@@ -1,36 +1,45 @@
|
||||
export const Keybindings: Array<{}> = [
|
||||
{
|
||||
name: "showSettingsMenu",
|
||||
bindKey: {win: "Ctrl-Shift-m", mac: "Ctrl-Shift-m"},
|
||||
name: "quit",
|
||||
bindKey: {win: "ctrl-q", mac: "ctrl-q"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "showKeyboardShortcuts",
|
||||
name: "toggleFullScreen",
|
||||
bindKey: {win: "F11", mac: "F11"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "showKeyShortcuts",
|
||||
bindKey: {win: "ctrl-shift-k", mac: "command-shift-k"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "openCommandPalette",
|
||||
bindKey: {linux: "Command-shift-/|F1", win: "ctrl-shift-/|F1"},
|
||||
name: "openCommandPalette2",
|
||||
bindKey: {linux: "command-shift-/|F1", win: "ctrl-shift-/|F1"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "showFilesModal",
|
||||
name: "showFilesList",
|
||||
bindKey: {win: "ctrl-b", mac: "ctrl-b"},
|
||||
service: "filesModalService",
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "showLSPModal",
|
||||
name: "lspManagerPopup",
|
||||
bindKey: {win: "ctrl-shift-l", mac: "ctrl-shift-l"},
|
||||
service: "",
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "search",
|
||||
name: "markdownPreviewPopup",
|
||||
bindKey: {win: "ctrl-shift-m", mac: "ctrl-shift-m"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "searchPopup",
|
||||
bindKey: {win: "ctrl-f", mac: "ctrl-f"},
|
||||
readOnly: true
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "newSession",
|
||||
name: "replacePopup",
|
||||
bindKey: {win: "ctrl-r", mac: "ctrl-r"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "newFile",
|
||||
bindKey: {win: "ctrl-t", mac: "ctrl-t"},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "destroySession",
|
||||
bindKey: {win: "ctrl-w", mac: "ctrl-w"},
|
||||
service: "editorsService",
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "openFiles",
|
||||
@@ -44,6 +53,10 @@ export const Keybindings: Array<{}> = [
|
||||
name: "saveFileAs",
|
||||
bindKey: {win: "ctrl-shift-s", mac: "ctrl-shift-s"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "closeFile",
|
||||
bindKey: {win: "ctrl-w", mac: "ctrl-w"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "selectLeftEditor",
|
||||
bindKey: {win: "ctrl-pageup", mac: "ctrl-pageup"},
|
||||
|
5
src/app/common/constants/button.map.ts
Normal file
5
src/app/common/constants/button.map.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export abstract class ButtonMap {
|
||||
static LEFT: number = 0;
|
||||
static MIDDLE: number = 1;
|
||||
static RIGHT: number = 2;
|
||||
}
|
@@ -16,7 +16,7 @@ import { NewtonFile } from '../types/file.type';
|
||||
})
|
||||
export class DndDirective {
|
||||
@HostBinding('class.fileover') fileOver!: boolean;
|
||||
@Output() fileDropped = new EventEmitter<any>();
|
||||
@Output() fileDropped: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@HostListener('dragover', ['$event'])
|
||||
onDragOver(evt: any) {
|
||||
|
@@ -11,17 +11,18 @@ import {
|
||||
selector: '[draggable-item]'
|
||||
})
|
||||
export class DraggableDirective {
|
||||
@Output() dragStart = new EventEmitter<PointerEvent>();
|
||||
@Output() dragMove = new EventEmitter<PointerEvent>();
|
||||
@Output() dragEnd = new EventEmitter<PointerEvent>();
|
||||
@Output() dragStart: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
@Output() dragMove: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
@Output() dragEnd: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
|
||||
private dragging = false;
|
||||
private dragging: boolean = false;
|
||||
selected: any;
|
||||
|
||||
|
||||
@HostListener('pointerdown', ['$event'])
|
||||
onPointerDown(event: PointerEvent): void {
|
||||
console.log("pointerdown");
|
||||
this.dragging = true;
|
||||
|
||||
this.dragStart.emit(event);
|
||||
}
|
||||
@@ -31,14 +32,12 @@ export class DraggableDirective {
|
||||
if (!this.dragging) return;
|
||||
console.log("pointermove");
|
||||
|
||||
this.dragging = true;
|
||||
this.dragMove.emit(event);
|
||||
}
|
||||
|
||||
@HostListener('document:pointerup', ['$event'])
|
||||
onPointerUp(event: PointerEvent): void {
|
||||
if (!this.dragging) return;
|
||||
|
||||
console.log("pointerup");
|
||||
|
||||
this.dragging = false;
|
||||
|
82
src/app/common/directives/pane-handle.directive.ts
Normal file
82
src/app/common/directives/pane-handle.directive.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import {
|
||||
Directive,
|
||||
Output,
|
||||
EventEmitter,
|
||||
HostListener
|
||||
} from '@angular/core';
|
||||
|
||||
|
||||
|
||||
@Directive({
|
||||
selector: '[pane-handle]'
|
||||
})
|
||||
export class PaneHandleDirective {
|
||||
@Output() dragStart: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
@Output() dragMove: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
@Output() dragEnd: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
|
||||
private dragging: boolean = false;
|
||||
private isHrPane: boolean = false;
|
||||
private parentElm: any;
|
||||
private leftSiblingElm: any;
|
||||
private rightSiblingElm: any;
|
||||
|
||||
|
||||
@HostListener('pointerdown', ['$event'])
|
||||
onPointerDown(event: PointerEvent): void {
|
||||
event.preventDefault();
|
||||
|
||||
let target = event.target as Element;
|
||||
if (
|
||||
!target.classList.contains("hr-pane-handle") &&
|
||||
!target.classList.contains("vr-pane-handle")
|
||||
) {
|
||||
console.error("Must have 'hr-pane-handle' or 'vr-pane-handle' in classList!");
|
||||
return;
|
||||
}
|
||||
|
||||
target.requestPointerLock();
|
||||
|
||||
this.dragging = true;
|
||||
this.isHrPane = ( target.classList.contains("hr-pane-handle") ) ? true : false ;
|
||||
this.parentElm = target.parentElement as Element;
|
||||
this.leftSiblingElm = target.previousElementSibling as Element;
|
||||
this.rightSiblingElm = target.nextElementSibling as Element;
|
||||
|
||||
this.dragStart.emit(event);
|
||||
}
|
||||
|
||||
@HostListener('pointermove', ['$event'])
|
||||
onPointerMove(event: PointerEvent): void {
|
||||
if (!this.dragging) return;
|
||||
|
||||
let x = event.movementX;
|
||||
let y = event.movementY;
|
||||
let parentBounds = this.parentElm.getBoundingClientRect();
|
||||
let leftBounds = this.leftSiblingElm.getBoundingClientRect();
|
||||
let rightBounds = this.rightSiblingElm.getBoundingClientRect();
|
||||
|
||||
if (this.isHrPane) {
|
||||
this.leftSiblingElm.style.maxHeight = `${leftBounds.height + y}px`;
|
||||
this.rightSiblingElm.style.maxHeight = `${rightBounds.height - y}px`;
|
||||
} else {
|
||||
this.leftSiblingElm.style.maxWidth = `${leftBounds.width + x}px`;
|
||||
this.rightSiblingElm.style.maxWidth = `${rightBounds.width - x}px`;
|
||||
}
|
||||
|
||||
this.dragMove.emit(event);
|
||||
}
|
||||
|
||||
@HostListener('pointerup', ['$event'])
|
||||
onPointerUp(event: PointerEvent): void {
|
||||
if (!this.dragging) return;
|
||||
|
||||
this.dragging = false;
|
||||
this.leftSiblingElm = null;
|
||||
this.rightSiblingElm = null;
|
||||
|
||||
document.exitPointerLock();
|
||||
this.dragEnd.emit(event);
|
||||
}
|
||||
|
||||
}
|
64
src/app/common/services/color-tokenizer.service.ts
Normal file
64
src/app/common/services/color-tokenizer.service.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import * as ace from "ace-builds/src-min-noconflict/ace";
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ColorTokenizerService {
|
||||
readonly #RULES: {} = {
|
||||
start: [
|
||||
{ token: "hex3", regex: "#[A-Fa-f0-9]{3}(?![A-Fa-f0-9])" },
|
||||
{ token: "hex6", regex: "#[A-Fa-f0-9]{6}(?![A-Fa-f0-9])" },
|
||||
{ token: "hex8", regex: "#[A-Fa-f0-9]{8}(?![A-Fa-f0-9])" },
|
||||
{
|
||||
token: "rgb",
|
||||
regex: /rgb\s*\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)/
|
||||
},
|
||||
{
|
||||
token: "rgba",
|
||||
regex: /rgba\s*\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*(?:0(?:\.\d+)?|1(?:\.0+)?)\s*\)/
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
tokenizer!: any;
|
||||
cssLines: {} = {};
|
||||
|
||||
|
||||
constructor() {
|
||||
Object.freeze(this.#RULES)
|
||||
|
||||
const Tokenizer = ace.require("ace/tokenizer").Tokenizer;
|
||||
this.tokenizer = new Tokenizer(this.#RULES);
|
||||
}
|
||||
|
||||
|
||||
public async parse(data: string) {
|
||||
const lines = data.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const token = this.parseLine( lines[i] );
|
||||
if (!token) continue;
|
||||
this.cssLines[i] = token;
|
||||
this.cssLines[i]["hash"] = btoa(
|
||||
token["value"]
|
||||
);
|
||||
}
|
||||
|
||||
console.log(this.cssLines);
|
||||
}
|
||||
|
||||
public parseLine(line: string): {} | null {
|
||||
const tokens = this.tokenizer.getLineTokens(line, "start").tokens;
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
if ("text" === tokens[i]["type"]) continue;
|
||||
return tokens[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -1,47 +1,118 @@
|
||||
import { ComponentRef, Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { NewtonEditorComponent } from "../../../editor/newton-editor/newton-editor.component";
|
||||
import { CodeViewComponent } from "../../../editor/code-view/view.component";
|
||||
|
||||
import { ServiceMessage } from '../../types/service-message.type';
|
||||
import { EditorSettings } from "../../configs/editor.config";
|
||||
|
||||
import { NewtonFile } from '../../types/file.type';
|
||||
import { EditorType } from '../../types/editor.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EditorsService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
|
||||
editors: Map<string, ComponentRef<NewtonEditorComponent>>;
|
||||
editorSettings: typeof EditorSettings;
|
||||
editors: Map<string, CodeViewComponent> = new Map();
|
||||
editorSettings: typeof EditorSettings = EditorSettings;
|
||||
|
||||
activeEditor: string = "";
|
||||
miniMapView!: CodeViewComponent;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.editorSettings = EditorSettings;
|
||||
this.editors = new Map<string, ComponentRef<NewtonEditorComponent>>();
|
||||
}
|
||||
|
||||
|
||||
getEditorsAsArray(): ComponentRef<NewtonEditorComponent>[] {
|
||||
public getEditorsAsArray(): CodeViewComponent[] {
|
||||
return [...this.editors.values()];
|
||||
}
|
||||
|
||||
get(uuid: string): NewtonEditorComponent {
|
||||
return this.editors.get(uuid).instance;
|
||||
public get(uuid: string): CodeViewComponent {
|
||||
return this.editors.get(uuid);
|
||||
}
|
||||
|
||||
set(uuid: string, component: ComponentRef<NewtonEditorComponent>) {
|
||||
public set(uuid: string, component: CodeViewComponent) {
|
||||
if (component.mode == EditorType.MiniMap) {
|
||||
this.miniMapView = component;
|
||||
return;
|
||||
}
|
||||
|
||||
this.editors.set(uuid, component);
|
||||
|
||||
if (Array.from(this.editors.keys()).length <= 1) return;
|
||||
|
||||
let _editors = this.getEditorsAsArray();
|
||||
let leftEditor = null;
|
||||
let rightEditor = null;
|
||||
|
||||
for (let i = 0; i < _editors.length; i++) {
|
||||
if (_editors[i].uuid !== uuid) continue;
|
||||
|
||||
leftEditor = _editors[i - 1];
|
||||
rightEditor = _editors[i];
|
||||
|
||||
}
|
||||
|
||||
leftEditor.rightSiblingUUID = rightEditor.uuid;
|
||||
rightEditor.leftSiblingUUID = leftEditor.uuid;
|
||||
}
|
||||
|
||||
public setSession(file: NewtonFile | undefined | null) {
|
||||
if ( !file ) return;
|
||||
|
||||
let editorComponent = this.getActiveEditorComponent();
|
||||
editorComponent.assignSession(file);
|
||||
this.miniMapView.cloneSession(file);
|
||||
}
|
||||
|
||||
public newFile() {
|
||||
let editorComponent = this.getActiveEditorComponent();
|
||||
|
||||
editorComponent.newFile();
|
||||
this.miniMapView.newFile();
|
||||
}
|
||||
|
||||
public getSession() {
|
||||
let editorComponent = this.get(this.activeEditor);
|
||||
let editor = editorComponent.editor;
|
||||
|
||||
return editor.getSession();
|
||||
}
|
||||
|
||||
public async setActiveEditor(activeEditor: string) {
|
||||
this.activeEditor = activeEditor;
|
||||
let editorComponent = this.getActiveEditorComponent();
|
||||
|
||||
if (!this.miniMapView) return;
|
||||
|
||||
if (editorComponent.activeFile) {
|
||||
this.miniMapView.cloneSession(editorComponent.activeFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: likely a new file/buffer
|
||||
this.miniMapView.editor.session.setValue(
|
||||
editorComponent.editor.session.getValue()
|
||||
);
|
||||
}
|
||||
|
||||
public getActiveEditorComponent(): any {
|
||||
return this.get(this.activeEditor);
|
||||
}
|
||||
|
||||
protected getActiveEditor(): any {
|
||||
let editorComponent = this.get(this.activeEditor);
|
||||
let editor = editorComponent.editor;
|
||||
return editor;
|
||||
}
|
||||
|
||||
|
||||
sendMessage(data: ServiceMessage): void {
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
getMessage$(): Observable<ServiceMessage> {
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
|
@@ -1,90 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { EditSession } from 'ace-builds';
|
||||
import { getModeForPath } from 'ace-builds/src-noconflict/ext-modelist';
|
||||
|
||||
import { TabsService } from './tabs/tabs.service';
|
||||
|
||||
import { NewtonFile } from '../../types/file.type';
|
||||
import { ServiceMessage } from '../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FilesService {
|
||||
files: Map<string, NewtonFile>;
|
||||
|
||||
|
||||
constructor(
|
||||
private tabsService: TabsService,
|
||||
) {
|
||||
this.files = new Map<string, NewtonFile>();
|
||||
}
|
||||
|
||||
|
||||
get(path: string): NewtonFile {
|
||||
return this.files.get(path);
|
||||
}
|
||||
|
||||
delete(file: NewtonFile) {
|
||||
file.session.destroy();
|
||||
window.fs.closeFile(file.path);
|
||||
this.files.delete(file.path);
|
||||
}
|
||||
|
||||
set(file: NewtonFile) {
|
||||
this.files.set(file.path, file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async loadFilesList(files: Array<NewtonFile>): Promise<NewtonFile | undefined | null> {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const path = window.fs.getPathForFile(file);
|
||||
|
||||
if (!file || !path) continue;
|
||||
if ( this.files.get(path) ) continue;
|
||||
|
||||
await this.addFile(path, file);
|
||||
this.addTab(file);
|
||||
}
|
||||
|
||||
return files[ files.length - 1 ];
|
||||
}
|
||||
|
||||
async addFile(path: string, file: NewtonFile): Promise<void> {
|
||||
try {
|
||||
let pathParts = path.split("/");
|
||||
file.fname = pathParts[ pathParts.length - 1 ];
|
||||
file.path = path;
|
||||
file.hash = btoa(file.path);
|
||||
let data = await window.fs.getFileContents(file.path);
|
||||
file.session = new EditSession(data);
|
||||
|
||||
file.session.setMode(
|
||||
getModeForPath( file.path ).mode
|
||||
);
|
||||
|
||||
this.files.set(file.path, file);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`---- Error ----\nPath: ${path}\nMessage: ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async addTab(file: NewtonFile) {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "create-tab";
|
||||
message.fileName = file.fname;
|
||||
message.fileUUID = file.hash;
|
||||
message.filePath = file.path;
|
||||
|
||||
this.tabsService.sendMessage(message);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -8,53 +8,50 @@ import { ServiceMessage } from '../../../types/service-message.type';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class InfoBarService {
|
||||
private dataSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
|
||||
private fpathSubject: ReplaySubject<string> = new ReplaySubject<string>(1);
|
||||
private cursorPosSubject: ReplaySubject<any> = new ReplaySubject<any>(1);
|
||||
private encodeingSubject: ReplaySubject<string> = new ReplaySubject<string>(1);
|
||||
private ftypeSubject: ReplaySubject<string> = new ReplaySubject<string>(1);
|
||||
private dataSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
private fpathSubject: ReplaySubject<string> = new ReplaySubject(1);
|
||||
private cursorPosSubject: ReplaySubject<any> = new ReplaySubject(1);
|
||||
private encodeingSubject: ReplaySubject<string> = new ReplaySubject(1);
|
||||
private ftypeSubject: ReplaySubject<string> = new ReplaySubject(1);
|
||||
|
||||
|
||||
constructor() {}
|
||||
|
||||
|
||||
setData(data: ServiceMessage): void {
|
||||
public setData(data: ServiceMessage): void {
|
||||
this.dataSubject.next(data);
|
||||
}
|
||||
|
||||
getData$(): Observable<ServiceMessage> {
|
||||
public getData$(): Observable<ServiceMessage> {
|
||||
return this.dataSubject.asObservable();
|
||||
}
|
||||
|
||||
setInfoBarFPath(data: string): void {
|
||||
public setInfoBarFPath(data: string): void {
|
||||
this.fpathSubject.next(data);
|
||||
}
|
||||
|
||||
updateInfoBarFPath$(): Observable<string> {
|
||||
public updateInfoBarFPath$(): Observable<string> {
|
||||
return this.fpathSubject.asObservable();
|
||||
}
|
||||
|
||||
setInfoBarCursorPos(data: any): void {
|
||||
public setInfoBarCursorPos(data: any): void {
|
||||
this.cursorPosSubject.next(data);
|
||||
}
|
||||
|
||||
updateInfoBarCursorPos$(): Observable<any> {
|
||||
public updateInfoBarCursorPos$(): Observable<any> {
|
||||
return this.cursorPosSubject.asObservable();
|
||||
}
|
||||
|
||||
setInfoBarEncodeing(data: string): void {
|
||||
public setInfoBarEncodeing(data: string): void {
|
||||
this.encodeingSubject.next(data);
|
||||
}
|
||||
|
||||
updateInfoBarEncodeing$(): Observable<string> {
|
||||
public updateInfoBarEncodeing$(): Observable<string> {
|
||||
return this.encodeingSubject.asObservable();
|
||||
}
|
||||
|
||||
setInfoBarFType(data: string): void {
|
||||
public setInfoBarFType(data: string): void {
|
||||
this.ftypeSubject.next(data);
|
||||
}
|
||||
|
||||
updateInfoBarFType$(): Observable<string> {
|
||||
public updateInfoBarFType$(): Observable<string> {
|
||||
return this.ftypeSubject.asObservable();
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,182 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { CompletionProvider } from "ace-builds/src-min-noconflict/ace";
|
||||
import { CommandBarTooltip } from "ace-builds/src-min-noconflict/ext-command_bar";
|
||||
import { InlineAutocomplete } from "ace-builds/src-min-noconflict/ext-inline_autocomplete";
|
||||
|
||||
import { AceLanguageClient, LanguageClientConfig } from 'ace-linters/build/ace-language-client';
|
||||
import { LanguageProvider } from "ace-linters";
|
||||
|
||||
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LspManagerService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
|
||||
workspaceFolder: string = "";
|
||||
lspConfigDataStr: string = "";
|
||||
languageProviders: {} = {};
|
||||
|
||||
|
||||
public loadLspConfigData(): Promise<string | void> {
|
||||
return this.getLspConfigData().then((lspConfigData: string) => {
|
||||
this.lspConfigDataStr = lspConfigData;
|
||||
return lspConfigData;
|
||||
});
|
||||
}
|
||||
|
||||
public registerEditorToLSPClient(editor: any) {
|
||||
let mode = this.getMode(editor.session);
|
||||
|
||||
this.languageProviders[mode]?.registerEditor(
|
||||
editor,
|
||||
editor.session.lspConfig
|
||||
);
|
||||
}
|
||||
|
||||
private getLspConfigData(): Promise<string> {
|
||||
return window.fs.getLspConfigData();
|
||||
}
|
||||
|
||||
private parseAndReturnLSPConfigData(): {} {
|
||||
let configData = JSON.parse(
|
||||
this.lspConfigDataStr.replaceAll("{workspace.folder}", this.workspaceFolder)
|
||||
);
|
||||
|
||||
if (configData["message"]) {
|
||||
console.warn(
|
||||
"Warning: LSP this.lspConfigDataStr is a 'message'",
|
||||
this.lspConfigDataStr
|
||||
);
|
||||
|
||||
configData = {};
|
||||
}
|
||||
|
||||
return configData;
|
||||
}
|
||||
|
||||
private getInitializationOptions(mode: string, configData: {}): {} {
|
||||
let initializationOptions = {};
|
||||
|
||||
if ( Object.keys(configData).length !== 0 && configData[mode] ) {
|
||||
initializationOptions = configData[mode]["initialization-options"];
|
||||
}
|
||||
|
||||
return initializationOptions;
|
||||
}
|
||||
|
||||
public createLanguageProviderWithClientServer(mode: string): LanguageProvider {
|
||||
if ( this.languageProviders[mode] ) return;
|
||||
let servers: LanguageClientConfig[] = [];
|
||||
|
||||
try {
|
||||
let lspConfigData = this.parseAndReturnLSPConfigData();
|
||||
let initializationOptions = this.getInitializationOptions(mode, lspConfigData);
|
||||
servers = [
|
||||
{
|
||||
module: () => import("ace-linters/build/language-client"),
|
||||
modes: mode,
|
||||
type: "socket",
|
||||
socket: new WebSocket( lspConfigData[mode]["socket"] ),
|
||||
initializationOptions: initializationOptions
|
||||
}
|
||||
];
|
||||
} catch(error) {
|
||||
console.error(
|
||||
"Error: Language Server could not be loaded OR doesn't exist in Newton-LSP config setup...",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.languageProviders[mode] = AceLanguageClient.for(
|
||||
servers,
|
||||
{
|
||||
workspacePath: this.workspaceFolder,
|
||||
functionality: {
|
||||
hover: true,
|
||||
completion: {
|
||||
overwriteCompleters: true,
|
||||
lspCompleterOptions: {
|
||||
triggerCharacters: {
|
||||
add: [
|
||||
" ",
|
||||
".",
|
||||
"@"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
// inlineCompletion: {
|
||||
// overwriteCompleters: true
|
||||
// },
|
||||
completionResolve: true,
|
||||
format: true,
|
||||
documentHighlights: true,
|
||||
signatureHelp: true,
|
||||
semanticTokens: true,
|
||||
codeActions: true
|
||||
},
|
||||
// aceComponents: {
|
||||
// InlineAutocomplete,
|
||||
// CommandBarTooltip,
|
||||
// CompletionProvider
|
||||
// },
|
||||
manualSessionControl: true
|
||||
}
|
||||
);
|
||||
|
||||
return this.languageProviders[mode];
|
||||
}
|
||||
|
||||
public closeLanguageProviderWithClientServer(mode: string): LanguageProvider {
|
||||
if ( !this.languageProviders[mode] ) return;
|
||||
|
||||
let connection = this.languageProviders[mode];
|
||||
delete this.languageProviders[mode];
|
||||
connection.closeConnection();
|
||||
}
|
||||
|
||||
private getLanguageProviderWithWebWorker(): LanguageProvider {
|
||||
let worker = new Worker(new URL('./webworker.js', import.meta.url));
|
||||
return LanguageProvider.create(worker);
|
||||
}
|
||||
|
||||
public registerSession(editor: any) {
|
||||
let mode = this.getMode(editor.session);
|
||||
if ( !this.languageProviders[mode] ) return;
|
||||
|
||||
this.languageProviders[mode].registerSession(
|
||||
editor.session,
|
||||
editor,
|
||||
editor.session.lspConfig
|
||||
);
|
||||
}
|
||||
|
||||
public getMode(session: any): string {
|
||||
return session.getMode()["$id"].replace("ace/mode/", "");
|
||||
}
|
||||
|
||||
public closeDocument(session: any) {
|
||||
if ( !session ) return;
|
||||
let mode = this.getMode(session);
|
||||
if ( !this.languageProviders[mode] ) return;
|
||||
this.languageProviders[mode].closeDocument(session);
|
||||
}
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MarkdownPreviewService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
@@ -8,26 +8,22 @@ import { ReplaySubject, Observable } from 'rxjs';
|
||||
})
|
||||
export class FilesModalService {
|
||||
private showFilesModalSubject: ReplaySubject<null> = new ReplaySubject<null>(1);
|
||||
private addFileSubject: ReplaySubject<string> = new ReplaySubject<string>(1);
|
||||
private addFileSubject: ReplaySubject<string> = new ReplaySubject<string>(1);
|
||||
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
showFilesModal(): void {
|
||||
public showFilesModal(): void {
|
||||
this.showFilesModalSubject.next(null);
|
||||
}
|
||||
|
||||
showFilesModalRequested$(): Observable<null> {
|
||||
public showFilesModalRequested$(): Observable<null> {
|
||||
return this.showFilesModalSubject.asObservable();
|
||||
}
|
||||
|
||||
addFileToModal(data: string): void {
|
||||
public addFileToModal(data: string): void {
|
||||
this.addFileSubject.next(data);
|
||||
}
|
||||
|
||||
addFileToModalRequested$(): Observable<string> {
|
||||
public addFileToModalRequested$(): Observable<string> {
|
||||
return this.addFileSubject.asObservable();
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SearchReplaceService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
|
||||
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
@@ -1,6 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { EditorsService } from '../editors.service';
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
@@ -8,16 +11,84 @@ import { ServiceMessage } from '../../../types/service-message.type';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TabsService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
|
||||
constructor() {}
|
||||
private editorsService: EditorsService = inject(EditorsService);
|
||||
|
||||
tabs: any[] = [];
|
||||
newIndex: number = -1;
|
||||
|
||||
|
||||
sendMessage(data: ServiceMessage): void {
|
||||
public push(tabData: {}): void {
|
||||
this.tabs.push(tabData);
|
||||
}
|
||||
|
||||
public closeTab(fpath: string): void {
|
||||
this.sendEditorsServiceAMessage("close-tab", fpath);
|
||||
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i].path == fpath) {
|
||||
this.splice(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sendEditorsServiceAMessage(action: string, fpath: string) {
|
||||
let message = new ServiceMessage();
|
||||
message.action = action;
|
||||
message.filePath = fpath;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public getLeftSiblingTab(fpath: string): string {
|
||||
if (this.tabs.length === 0 ) return;
|
||||
|
||||
let i = this.tabs.indexOf(fpath);
|
||||
|
||||
(i === 0) ? i = this.tabs.length - 1 : i -= 1;
|
||||
return this.tabs[i].path;
|
||||
}
|
||||
|
||||
public getRightSiblingTab(fpath: string): string {
|
||||
if (this.tabs.length === 0 ) return;
|
||||
|
||||
let i = this.tabs.indexOf(fpath);
|
||||
|
||||
(i === (this.tabs.length - 1)) ? i = 0 : i += 1;
|
||||
return this.tabs[i].path;
|
||||
}
|
||||
|
||||
public setNewTargetIndex(fpath: string): void {
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i].path == fpath) {
|
||||
this.newIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public move(oldIndex: number): void {
|
||||
if (this.newIndex == -1) return;
|
||||
|
||||
moveItemInArray(this.tabs, oldIndex, this.newIndex);
|
||||
this.newIndex = -1;
|
||||
|
||||
// event.currentIndex not updating for some reason...
|
||||
// moveItemInArray(this.tabs, event.previousIndex, event.currentIndex);
|
||||
}
|
||||
|
||||
public splice(index: number): void {
|
||||
this.tabs.splice(index, 1);
|
||||
}
|
||||
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
getMessage$(): Observable<ServiceMessage> {
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
}
|
136
src/app/common/services/files.service.ts
Normal file
136
src/app/common/services/files.service.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { EditSession, UndoManager } from 'ace-builds';
|
||||
import { getModeForPath } from 'ace-builds/src-noconflict/ext-modelist';
|
||||
|
||||
import { TabsService } from './editor/tabs/tabs.service';
|
||||
import { ColorTokenizerService } from './color-tokenizer.service';
|
||||
|
||||
import { NewtonFile } from '../types/file.type';
|
||||
import { ServiceMessage } from '../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FilesService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
|
||||
private tabsService: TabsService = inject(TabsService);
|
||||
|
||||
files: Map<string, NewtonFile> = new Map();
|
||||
|
||||
|
||||
public get(path: string): NewtonFile {
|
||||
return this.files.get(path);
|
||||
}
|
||||
|
||||
public getAllPaths(): string[] {
|
||||
return [...this.files.keys()];
|
||||
}
|
||||
|
||||
public getAllFiles(): NewtonFile[] {
|
||||
return [...this.files.values()];
|
||||
}
|
||||
|
||||
public getPreviousFile(path: string): NewtonFile {
|
||||
let paths = this.getAllPaths();
|
||||
if (paths.length === 0 ) return;
|
||||
|
||||
let i = paths.indexOf(path);
|
||||
(i === 0) ? i = paths.length - 1 : i -= 1;
|
||||
return this.files.get( paths[i] );
|
||||
}
|
||||
|
||||
public getNextFile(path: string): NewtonFile {
|
||||
let paths = this.getAllPaths();
|
||||
if (paths.length === 0 ) return;
|
||||
|
||||
let i = paths.indexOf(path);
|
||||
(i === (paths.length - 1)) ? i = 0 : i += 1;
|
||||
return this.files.get( paths[i] );
|
||||
}
|
||||
|
||||
public unset(file: NewtonFile) {
|
||||
file.session.destroy();
|
||||
window.fs.closeFile(file.path);
|
||||
this.files.delete(file.path);
|
||||
}
|
||||
|
||||
public set(file: NewtonFile) {
|
||||
this.files.set(file.path, file);
|
||||
}
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
public async loadFilesList(
|
||||
files: Array<NewtonFile>
|
||||
): Promise<NewtonFile | undefined | null> {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const path = window.fs.getPathForFile(file);
|
||||
|
||||
if (!file || !path) continue;
|
||||
if ( this.files.get(path) ) continue;
|
||||
|
||||
await this.addFile(path, file);
|
||||
this.addTab(file);
|
||||
}
|
||||
|
||||
return files[ files.length - 1 ];
|
||||
}
|
||||
|
||||
public async addFile(
|
||||
path: string,
|
||||
file: NewtonFile,
|
||||
loadFileContents: boolean = true,
|
||||
data: string = ""
|
||||
): Promise<void> {
|
||||
try {
|
||||
let pathParts = path.split("/");
|
||||
file.fname = pathParts[ pathParts.length - 1 ];
|
||||
file.path = path;
|
||||
file.hash = btoa(file.path);
|
||||
|
||||
if (loadFileContents)
|
||||
data = await window.fs.getFileContents(file.path);
|
||||
|
||||
file.session = new EditSession(data);
|
||||
file.session["id"] = path;
|
||||
file.session.setUndoManager( new UndoManager() );
|
||||
file.session.setMode( getModeForPath( file.path ).mode );
|
||||
file.session["lspConfig"] = {
|
||||
filePath: path,
|
||||
joinWorkspaceURI: false
|
||||
}
|
||||
file.session["colorTokenizer"] = new ColorTokenizerService();
|
||||
file.session["colorTokenizer"].parse(data);
|
||||
|
||||
this.files.set(file.path, file);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`---- Error ----\nPath: ${path}\nMessage: ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async addTab(file: NewtonFile) {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "create-tab";
|
||||
message.fileName = file.fname;
|
||||
message.fileUUID = file.hash;
|
||||
message.filePath = file.path;
|
||||
|
||||
this.tabsService.sendMessage(message);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,75 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { AceLanguageClient, LanguageClientConfig } from 'ace-linters/build/ace-language-client';
|
||||
import { LanguageProvider } from "ace-linters";
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LSPService {
|
||||
lspConfigData!: {};
|
||||
languageProviders: {} = {};
|
||||
|
||||
constructor() {
|
||||
this.loadLSPService();
|
||||
}
|
||||
|
||||
private loadLSPService() {
|
||||
this.getLspConfigData().then((lspConfigData: string) => {
|
||||
this.lspConfigData = JSON.parse(lspConfigData);
|
||||
|
||||
if (this.lspConfigData["message"]) {
|
||||
console.log(
|
||||
"Warning: LSP this.lspConfigData is a 'message'",
|
||||
this.lspConfigData
|
||||
);
|
||||
|
||||
this.lspConfigData = {};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public registerEditor(editor: any): void {
|
||||
let modeParts = editor.getSession()["$modeId"].split("/");
|
||||
let mode = modeParts[ modeParts.length - 1 ];
|
||||
|
||||
if ( !this.languageProviders[mode] ) {
|
||||
this.languageProviders[mode] = this.getLanguageProviderWithClientServer(mode);
|
||||
}
|
||||
|
||||
this.languageProviders[mode].registerEditor(editor);
|
||||
}
|
||||
|
||||
private getLspConfigData(): Promise<string> {
|
||||
return window.fs.getLspConfigData();
|
||||
}
|
||||
|
||||
private getLanguageProviderWithClientServer(mode: string) {
|
||||
let _initializationOptions = {};
|
||||
|
||||
if ( Object.keys(this.lspConfigData).length !== 0 && this.lspConfigData[mode] ) {
|
||||
_initializationOptions = this.lspConfigData[mode]["initialization-options"];
|
||||
}
|
||||
|
||||
let servers: LanguageClientConfig[] = [
|
||||
{
|
||||
module: () => import("ace-linters/build/language-client"),
|
||||
modes: mode,
|
||||
type: "socket",
|
||||
socket: new WebSocket(`ws://127.0.0.1:9999/${mode}`),
|
||||
// socket: new WebSocket("ws://127.0.0.1:9999/?name=pylsp"),
|
||||
initializationOptions: _initializationOptions
|
||||
}
|
||||
];
|
||||
|
||||
return AceLanguageClient.for(servers);
|
||||
}
|
||||
|
||||
private getLanguageProviderWithWebWorker() {
|
||||
let worker = new Worker(new URL('./webworker.js', import.meta.url));
|
||||
return LanguageProvider.create(worker);
|
||||
}
|
||||
}
|
5
src/app/common/types/editor.type.ts
Normal file
5
src/app/common/types/editor.type.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export abstract class EditorType {
|
||||
static MiniMap: string = "mini-map";
|
||||
static ReadOnly: string = "read-only";
|
||||
static Standalone: string = "standalone";
|
||||
}
|
@@ -2,8 +2,8 @@ import { EditSession } from 'ace-builds';
|
||||
|
||||
|
||||
export interface NewtonFile extends File {
|
||||
fname: string,
|
||||
path: string,
|
||||
hash: string,
|
||||
session: EditSession
|
||||
fname?: string,
|
||||
path?: string,
|
||||
hash?: string,
|
||||
session?: EditSession,
|
||||
}
|
366
src/app/editor/code-view/view.base.ts
Normal file
366
src/app/editor/code-view/view.base.ts
Normal file
@@ -0,0 +1,366 @@
|
||||
import {
|
||||
Directive,
|
||||
ElementRef,
|
||||
Input,
|
||||
ViewChild,
|
||||
inject
|
||||
} from '@angular/core';
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.service';
|
||||
import { FilesModalService } from '../../common/services/editor/modals/files-modal.service';
|
||||
import { TabsService } from '../../common/services/editor/tabs/tabs.service';
|
||||
import { EditorsService } from '../../common/services/editor/editors.service';
|
||||
import { FilesService } from '../../common/services/files.service';
|
||||
import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service';
|
||||
import { MarkdownPreviewService } from '../../common/services/editor/markdown-preview/markdown-preview.service';
|
||||
import { LspManagerService } from '../../common/services/editor/lsp-manager/lsp-manager.service';
|
||||
|
||||
import { EditorSettings } from "../../common/configs/editor.config";
|
||||
import { NewtonFile } from '../../common/types/file.type';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Directive()
|
||||
export class CodeViewBase {
|
||||
public uuid: string = uuid.v4();
|
||||
@Input() public isDefault: boolean = false;
|
||||
@Input() public mode: string = "";
|
||||
public leftSiblingUUID!: string;
|
||||
public rightSiblingUUID!: string;
|
||||
|
||||
protected infoBarService: InfoBarService = inject(InfoBarService);
|
||||
protected filesModalService: FilesModalService = inject(FilesModalService);
|
||||
protected tabsService: TabsService = inject(TabsService);
|
||||
protected editorsService: EditorsService = inject(EditorsService);
|
||||
protected filesService: FilesService = inject(FilesService);
|
||||
protected searchReplaceService: SearchReplaceService = inject(SearchReplaceService);
|
||||
protected markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService);
|
||||
protected lspManagerService: LspManagerService = inject(LspManagerService);
|
||||
|
||||
@ViewChild('editor') editorElm!: ElementRef;
|
||||
@Input() editorSettings!: typeof EditorSettings;
|
||||
|
||||
public editor!: any;
|
||||
public aceApi!: any;
|
||||
public activeFile!: NewtonFile;
|
||||
|
||||
public cutBuffer: string = "";
|
||||
public timerId: number = -1;
|
||||
public debounceId: number = -1;
|
||||
public debounceWait: number = 800;
|
||||
|
||||
@ViewChild('contextMenu') contextMenu!: ElementRef;
|
||||
public showContextMenu: boolean = false;
|
||||
|
||||
|
||||
public selectLeftEditor() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "select-left-editor";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public selectRightEditor() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "select-right-editor";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public moveSessionLeft() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "move-session-left";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public moveSessionRight() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "move-session-right";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public addActiveStyling() {
|
||||
this.editorElm.nativeElement.classList.add("active-editor")
|
||||
}
|
||||
|
||||
public removeActiveStyling() {
|
||||
this.editorElm.nativeElement.classList.remove("active-editor")
|
||||
}
|
||||
|
||||
public openCommandPalette2() {
|
||||
this.editor.execCommand("openCommandPalette");
|
||||
}
|
||||
|
||||
public showKeyShortcuts() {
|
||||
this.editor.showKeyboardShortcuts();
|
||||
}
|
||||
|
||||
public lspManagerPopup() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-lsp-manager";
|
||||
this.lspManagerService.sendMessage(message);
|
||||
}
|
||||
|
||||
public markdownPreviewPopup() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-markdown-preview";
|
||||
this.markdownPreviewService.sendMessage(message);
|
||||
}
|
||||
|
||||
public searchPopup() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-search-replace";
|
||||
this.searchReplaceService.sendMessage(message);
|
||||
|
||||
// this.editor.execCommand("find");
|
||||
}
|
||||
|
||||
public replacePopup() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-search-replace";
|
||||
this.searchReplaceService.sendMessage(message);
|
||||
|
||||
// this.editor.execCommand("replace");
|
||||
}
|
||||
|
||||
public showFilesList() {
|
||||
let paths = this.filesService.getAllPaths();
|
||||
let stubPaths = [];
|
||||
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
let fpath = paths[i];
|
||||
if (fpath.length > 67) {
|
||||
fpath = "..." + fpath.slice(fpath.length - 67, fpath.length);
|
||||
}
|
||||
stubPaths.push(fpath);
|
||||
}
|
||||
|
||||
this.editor.prompt("",
|
||||
{
|
||||
name: "Files:",
|
||||
placeholder: "Search...",
|
||||
getCompletions: (search) => {
|
||||
let query = search.getValue();
|
||||
let result = [];
|
||||
|
||||
if (!query) return stubPaths;
|
||||
|
||||
for (let i = 0; i < stubPaths.length; i++) {
|
||||
if (stubPaths[i].includes(query)) {
|
||||
result.push(stubPaths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
onAccept: (data) => {
|
||||
let fpath = (data.value) ? data.value : data.item;
|
||||
let path = "";
|
||||
|
||||
for (let i = 0; i < stubPaths.length; i++) {
|
||||
if (stubPaths[i] === fpath) {
|
||||
path = paths[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!path) return;
|
||||
|
||||
this.editorsService.setSession(
|
||||
this.filesService.get(path)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public closeFile() {
|
||||
this.tabsService.closeTab(this.activeFile.path);
|
||||
}
|
||||
|
||||
public toggleFullScreen() {
|
||||
window.main.toggleFullScreen();
|
||||
}
|
||||
|
||||
public setAsReadOnly() {
|
||||
this.editor.setReadOnly(true);
|
||||
this.editor.setShowPrintMargin(false);
|
||||
}
|
||||
|
||||
public setAsMiniMapView() {
|
||||
this.editor.renderer.showLineNumbers = false;
|
||||
this.editor.renderer.setShowGutter(false);
|
||||
|
||||
this.editor.setReadOnly(true);
|
||||
this.editor.setFontSize(2);
|
||||
this.editor.setHighlightActiveLine(false);
|
||||
this.editor.setHighlightGutterLine(false);
|
||||
this.editor.setShowFoldWidgets(false);
|
||||
this.editor.setShowPrintMargin(false);
|
||||
this.editor.session.setUseWrapMode(true);
|
||||
|
||||
this.editorElm.nativeElement.parentElement.classList.add("col-1");
|
||||
this.editorElm.nativeElement.parentElement.classList.add("zero-margin-padding");
|
||||
|
||||
this.editor.on("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let editorComponent = this.editorsService.getActiveEditorComponent();
|
||||
let pos = this.editor.getCursorPosition();
|
||||
|
||||
editorComponent.editor.moveCursorToPosition(pos);
|
||||
editorComponent.editor.clearSelection();
|
||||
editorComponent.editor.renderer.scrollCursorIntoView(null, 0.5);
|
||||
});
|
||||
|
||||
this.editor.on("mousewheel", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let editorComponent = this.editorsService.getActiveEditorComponent();
|
||||
editorComponent.editor.renderer.scrollBy(null, event.domEvent.deltaY);
|
||||
});
|
||||
}
|
||||
|
||||
public zoomIn() {
|
||||
this.editor.setFontSize(
|
||||
parseInt(this.editor.getFontSize()) + 1
|
||||
);
|
||||
}
|
||||
|
||||
public zoomOut() {
|
||||
this.editor.setFontSize(
|
||||
parseInt(this.editor.getFontSize()) - 1
|
||||
);
|
||||
}
|
||||
|
||||
public movelinesUp() {
|
||||
this.editor.execCommand("movelinesup");
|
||||
}
|
||||
|
||||
public movelinesDown() {
|
||||
this.editor.execCommand("movelinesdown");
|
||||
}
|
||||
|
||||
public duplicateLines() {
|
||||
this.editor.execCommand("copylinesdown");
|
||||
}
|
||||
|
||||
public cutText() {
|
||||
let cutText = this.editor.getSelectedText();
|
||||
this.editor.remove();
|
||||
navigator.clipboard.writeText(cutText).catch(() => {
|
||||
console.error("Unable to cut text...");
|
||||
});
|
||||
}
|
||||
|
||||
public copyText() {
|
||||
let copyText = this.editor.getSelectedText();
|
||||
navigator.clipboard.writeText(copyText).catch(() => {
|
||||
console.error("Unable to copy text...");
|
||||
});
|
||||
}
|
||||
|
||||
public pasteText() {
|
||||
navigator.clipboard.readText().then((pasteText) => {
|
||||
this.editor.insert(pasteText, true);
|
||||
});
|
||||
}
|
||||
|
||||
protected updateInfoBar() {
|
||||
this.infoBarService.setInfoBarFPath(this.activeFile?.path)
|
||||
this.infoBarService.setInfoBarCursorPos(
|
||||
this.editor.getCursorPosition()
|
||||
);
|
||||
this.infoBarService.setInfoBarFType(
|
||||
this.editor.session.getMode()["$id"]
|
||||
);
|
||||
}
|
||||
|
||||
public newFile() {
|
||||
this.activeFile = null;
|
||||
let session = this.aceApi.createEditSession([""]);
|
||||
this.editor.setSession(session);
|
||||
}
|
||||
|
||||
protected openFiles() {
|
||||
let startDir = "";
|
||||
if (this.activeFile) {
|
||||
let pathParts = this.activeFile.path.split("/");
|
||||
pathParts.pop();
|
||||
startDir = pathParts.join( '/' );
|
||||
}
|
||||
|
||||
window.fs.openFiles(startDir);
|
||||
}
|
||||
|
||||
protected saveFile() {
|
||||
if (!this.activeFile) {
|
||||
this.saveFileAs();
|
||||
return;
|
||||
}
|
||||
|
||||
const text = this.activeFile.session.getValue();
|
||||
window.fs.saveFile(this.activeFile.path, text);
|
||||
}
|
||||
|
||||
protected saveFileAs() {
|
||||
window.fs.saveFileAs().then((path: string) => {
|
||||
if (!path) return;
|
||||
|
||||
let file: NewtonFile = new File([""], path, {});
|
||||
const text = this.editor.session.getValue();
|
||||
|
||||
window.fs.saveFile(path, text);
|
||||
this.filesService.addFile(
|
||||
path,
|
||||
file,
|
||||
false,
|
||||
text
|
||||
).then(() => {
|
||||
this.activeFile = this.filesService.get(path);
|
||||
this.editor.setSession(this.activeFile.session);
|
||||
this.filesService.addTab(this.activeFile);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
protected cutToBuffer() {
|
||||
if (this.timerId) { clearTimeout(this.timerId); }
|
||||
|
||||
const cursorPosition = this.editor.getCursorPosition();
|
||||
let lineText = this.editor.session.getLine(cursorPosition.row);
|
||||
this.cutBuffer += `${lineText}\n`;
|
||||
|
||||
this.editor.session.removeFullLines(cursorPosition.row, cursorPosition.row)
|
||||
this.setBufferClearTimeout();
|
||||
}
|
||||
|
||||
protected pasteCutBuffer() {
|
||||
if (this.timerId) { clearTimeout(this.timerId); }
|
||||
|
||||
this.editor.insert(this.cutBuffer, true);
|
||||
this.setBufferClearTimeout();
|
||||
}
|
||||
|
||||
private setBufferClearTimeout(timeout: number = 5000) {
|
||||
this.timerId = setTimeout(() => {
|
||||
this.cutBuffer = "";
|
||||
this.timerId = -1;
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
private quit() {
|
||||
window.main.quit();
|
||||
}
|
||||
}
|
33
src/app/editor/code-view/view.component.css
Normal file
33
src/app/editor/code-view/view.component.css
Normal file
@@ -0,0 +1,33 @@
|
||||
.editor {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.active-editor {
|
||||
border-style: solid;
|
||||
border-width: thin;
|
||||
border-color: rgba(124, 124, 124, 1);
|
||||
}
|
||||
|
||||
|
||||
.contextMenu {
|
||||
display: inline-table;
|
||||
z-index: 500;
|
||||
position: absolute;
|
||||
min-width: 2em;
|
||||
padding: 0.2em;
|
||||
top: 0em;
|
||||
right: 0em;
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
.contextMenu li:hover {
|
||||
background-color: rgba(0, 124, 0, 0.64);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.contextMenu li {
|
||||
padding: 0em 0.2em;
|
||||
}
|
15
src/app/editor/code-view/view.component.html
Normal file
15
src/app/editor/code-view/view.component.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<div class="editor" #editor >
|
||||
</div>
|
||||
|
||||
<ul #contextMenu
|
||||
class="contextMenu"
|
||||
[hidden]="!showContextMenu"
|
||||
(blur)="hideContextMenu"
|
||||
(click)="contextMenuClicked($event)"
|
||||
>
|
||||
<li command="cutText">Cut</li>
|
||||
<li command="copyText">Copy</li>
|
||||
<li command="pasteText">Paste</li>
|
||||
<hr/>
|
||||
<li command="prettyJSON">Prettify JSON</li>
|
||||
</ul>
|
299
src/app/editor/code-view/view.component.ts
Normal file
299
src/app/editor/code-view/view.component.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
// Import Ace and its modes/themes so that `ace` global is defined
|
||||
import * as ace from "ace-builds/src-min-noconflict/ace";
|
||||
|
||||
// Note: https://github.com/mkslanc/ace-linters/blob/c286d85c558530aa1b0597d02108bc782abd4736/packages/demo/file-api-websockets/client.ts#L27
|
||||
// import { AceLayout, Box, TabManager, Button, dom, AceTreeWrapper, FileSystemWeb, Pane, AceEditor, Tab } from "ace-layout";
|
||||
|
||||
import "ace-builds/src-min-noconflict/ext-settings_menu";
|
||||
import "ace-builds/src-min-noconflict/ext-keybinding_menu";
|
||||
import "ace-builds/src-min-noconflict/ext-command_bar";
|
||||
import "ace-builds/src-min-noconflict/ext-prompt";
|
||||
import "ace-builds/src-min-noconflict/ext-code_lens";
|
||||
// import "ace-builds/src-min-noconflict/ext-searchbox";
|
||||
import "ace-builds/src-min-noconflict/ext-language_tools";
|
||||
// import "ace-builds/src-min-noconflict/theme-one_dark";
|
||||
// import "ace-builds/src-min-noconflict/theme-penguins_in_space";
|
||||
import "ace-builds/src-min-noconflict/theme-gruvbox";
|
||||
|
||||
// https://www.npmjs.com/package/web-tree-sitter
|
||||
// import { Language, Parser } from 'web-tree-sitter';
|
||||
|
||||
import { CodeViewBase } from './view.base';
|
||||
|
||||
import { NewtonFile } from '../../common/types/file.type';
|
||||
import { EditorType } from '../../common/types/editor.type';
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
import { ButtonMap } from '../../common/constants/button.map';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'code-view',
|
||||
standalone: true,
|
||||
imports: [
|
||||
],
|
||||
templateUrl: './view.component.html',
|
||||
styleUrl: './view.component.css',
|
||||
host: {
|
||||
'class': 'col zero-margin-padding scroller'
|
||||
}
|
||||
})
|
||||
export class CodeViewComponent extends CodeViewBase {
|
||||
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// const { Parser } = window.TreeSitter;
|
||||
// const { Parser } = TreeSitter;
|
||||
// console.log(treeSitter);
|
||||
|
||||
// treeSitter.Parser.init().then(() => {
|
||||
// console.log("Parser ready...");
|
||||
// });
|
||||
|
||||
// const parser = new Parser();
|
||||
// const JavaScript = await Language.load('/path/to/tree-sitter-javascript.wasm');
|
||||
// Language.load('resources/wasm/tree-sitter-javascript.wasm').then((language) => {
|
||||
// console.log(language);
|
||||
// });
|
||||
|
||||
this.aceApi = ace;
|
||||
}
|
||||
|
||||
|
||||
private ngAfterViewInit(): void {
|
||||
this.loadAce();
|
||||
}
|
||||
|
||||
private loadAce(): void {
|
||||
this.configAceAndBindToElement()
|
||||
this.setupRequestedMode();
|
||||
}
|
||||
|
||||
private configAceAndBindToElement(): void {
|
||||
this.editorSettings = this.editorsService.editorSettings;
|
||||
|
||||
ace.config.set('basePath', this.editorSettings.BASE_PATH);
|
||||
|
||||
this.editor = ace.edit( this.editorElm.nativeElement );
|
||||
this.editor.setOptions( this.editorSettings.CONFIG );
|
||||
|
||||
this.editorsService.set(this.uuid, this);
|
||||
if (this.isDefault) {
|
||||
this.editorsService.setActiveEditor(this.uuid);
|
||||
this.addActiveStyling();
|
||||
this.editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private setupRequestedMode() {
|
||||
switch(this.mode) {
|
||||
case EditorType.Standalone:
|
||||
// Note: Ace editor without any additional Newton setup...
|
||||
break;
|
||||
case EditorType.MiniMap:
|
||||
this.setAsMiniMapView();
|
||||
break;
|
||||
case EditorType.ReadOnly:
|
||||
this.setAsReadOnly();
|
||||
break;
|
||||
default:
|
||||
this.loadNewtonKeyBindings();
|
||||
this.loadNewtonEventBindings();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private loadNewtonKeyBindings(): void {
|
||||
let keyBindings = [];
|
||||
for (let i = 0; i < this.editorSettings.KEYBINDINGS.length; i++) {
|
||||
let keyBinding = this.editorSettings.KEYBINDINGS[i];
|
||||
keyBindings.push(
|
||||
{
|
||||
name: keyBinding.name,
|
||||
bindKey: keyBinding.bindKey,
|
||||
exec: (keyBinding.name && keyBinding?.service) ?
|
||||
() => (
|
||||
this[keyBinding?.service][keyBinding.name]()
|
||||
)
|
||||
:
|
||||
(this[keyBinding.name]) ?
|
||||
() => (
|
||||
this[keyBinding.name]()
|
||||
)
|
||||
:
|
||||
() => (
|
||||
console.log(
|
||||
`Name: ${keyBinding.name}, is not mapping to a method OR mapping to a Service: ${keyBinding?.service} and Name: ${keyBinding.name}.`
|
||||
)
|
||||
)
|
||||
,
|
||||
readOnly: keyBinding.readOnly
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.editor.commands.addCommands( keyBindings );
|
||||
}
|
||||
|
||||
private loadNewtonEventBindings(): void {
|
||||
|
||||
// Note: https://ajaxorg.github.io/ace-api-docs/interfaces/ace.Ace.EditorEvents.html
|
||||
this.editor.on("focus", (e) => {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "set-active-editor";
|
||||
message.editorUUID = this.uuid;
|
||||
message.rawData = this.editor;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
this.searchReplaceService.sendMessage(message);
|
||||
|
||||
message = new ServiceMessage();
|
||||
message.action = "set-active-editor";
|
||||
message.rawData = this;
|
||||
this.lspManagerService.sendMessage(message);
|
||||
this.markdownPreviewService.sendMessage(message);
|
||||
|
||||
this.updateInfoBar();
|
||||
});
|
||||
|
||||
this.editor.on("click", (event) => {
|
||||
this.updateInfoBar();
|
||||
});
|
||||
|
||||
this.editor.addEventListener("mousedown", (event) => {
|
||||
if (ButtonMap.LEFT === event.domEvent.button) {
|
||||
this.showContextMenu = false;
|
||||
} else if (ButtonMap.RIGHT === event.domEvent.button) {
|
||||
let menuElm = this.contextMenu.nativeElement;
|
||||
let pageX = event.domEvent.pageX;
|
||||
let pageY = event.domEvent.pageY;
|
||||
|
||||
const origin = {
|
||||
left: pageX + 5,
|
||||
top: pageY - 5
|
||||
};
|
||||
|
||||
menuElm.style.left = `${origin.left}px`;
|
||||
menuElm.style.top = `${origin.top}px`;
|
||||
this.showContextMenu = true;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.on("input", () => {
|
||||
this.updateInfoBar();
|
||||
});
|
||||
|
||||
this.editor.on("keyboardActivity", (e) => {
|
||||
switch(e.command.name) {
|
||||
case "golineup":
|
||||
case "golinedown":
|
||||
case "gotoleft":
|
||||
case "gotoright":
|
||||
this.infoBarService.setInfoBarCursorPos(
|
||||
this.editor.getCursorPosition()
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.on("mousewheel", (event) => {
|
||||
if (event.domEvent.ctrlKey && event.domEvent.deltaY < 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.zoomIn();
|
||||
} else if (event.domEvent.ctrlKey && event.domEvent.deltaY > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.zoomOut();
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.on("change", () => {
|
||||
if (this.debounceId) { clearTimeout(this.debounceId); }
|
||||
this.setDebounceTimeout();
|
||||
|
||||
if (!this.activeFile) return;
|
||||
|
||||
let message = new ServiceMessage();
|
||||
message.action = "file-changed";
|
||||
message.filePath = this.activeFile.path;
|
||||
this.tabsService.sendMessage(message);
|
||||
|
||||
});
|
||||
|
||||
this.editor.on("changeSession", (session) => {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "editor-update";
|
||||
message.rawData = this;
|
||||
|
||||
this.lspManagerService.sendMessage(message);
|
||||
|
||||
this.updateInfoBar();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public prettyJSON() {
|
||||
let data = JSON.parse(this.editor.session.getValue());
|
||||
this.editor.session.setValue(
|
||||
JSON.stringify(data, null, 4)
|
||||
);
|
||||
}
|
||||
|
||||
public hideContextMenu() {
|
||||
this.showContextMenu = false;
|
||||
}
|
||||
|
||||
public contextMenuClicked(event: any) {
|
||||
this.showContextMenu = false;
|
||||
|
||||
const command = event.target.getAttribute("command");
|
||||
const args = event.target.getAttribute("args");
|
||||
|
||||
if (!command) return;
|
||||
|
||||
this[command]( (args) ? args : null );
|
||||
}
|
||||
|
||||
public assignSession(file: NewtonFile) {
|
||||
if (!file) return;
|
||||
|
||||
this.activeFile = file;
|
||||
this.editor.setSession(file.session);
|
||||
}
|
||||
|
||||
public cloneSession(file: NewtonFile) {
|
||||
if (!file) return;
|
||||
|
||||
this.activeFile = file;
|
||||
let session = this.aceApi.createEditSession(file.session.getValue());
|
||||
session["$config"] = {
|
||||
"lsp": {
|
||||
"filePath": file.path
|
||||
}
|
||||
}
|
||||
|
||||
session.setMode( file.session.getMode()["$id"] );
|
||||
session.setUseWrapMode(true);
|
||||
|
||||
this.editor.setSession(session);
|
||||
}
|
||||
|
||||
private setDebounceTimeout(timeout: number = null) {
|
||||
this.debounceId = setTimeout(() => {
|
||||
this.editorsService.miniMapView.editor.session.setValue(
|
||||
this.editor.session.getValue()
|
||||
);
|
||||
|
||||
this.debounceId = -1;
|
||||
}, (timeout) ? timeout : this.debounceWait);
|
||||
}
|
||||
|
||||
}
|
@@ -1,20 +1,3 @@
|
||||
.dropzone {
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.editor-size-button {
|
||||
text-align: center;
|
||||
width: 4em;
|
||||
color: rgba(225, 225, 225, 0.64);
|
||||
border-style: solid;
|
||||
border-width: thin;
|
||||
border-color: rgba(124, 124, 124, 0.64);
|
||||
margin-left: 0.2em;
|
||||
margin-right: 0.2em;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.editor-size-button:hover {
|
||||
cursor: pointer;
|
||||
border-color: rgba(0, 124, 0, 0.64);
|
||||
}
|
||||
height: 92vh;
|
||||
}
|
@@ -1,40 +1,10 @@
|
||||
<div class="col">
|
||||
|
||||
<div class="row dropzone" #dropzone dropzone (fileDropped)="onFileDropped($event)">
|
||||
<ng-container #containerRef>
|
||||
</ng-container>
|
||||
<code-view [isDefault]="true"></code-view>
|
||||
<hr class="col vr-pane-handle" pane-handle />
|
||||
<code-view></code-view>
|
||||
<code-view [mode]="'mini-map'"></code-view>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col editor-left-size" (click)="setEditorSize($event)">
|
||||
<div class="editor-size-button size-12">
|
||||
100%
|
||||
</div>
|
||||
<div class="editor-size-button size-8">
|
||||
75%
|
||||
</div>
|
||||
<div class="editor-size-button size-6">
|
||||
50%
|
||||
</div>
|
||||
<div class="editor-size-button size-2">
|
||||
25%
|
||||
</div>
|
||||
</div>
|
||||
<div class="col editor-right-size" (click)="setEditorSize($event)">
|
||||
<div class="editor-size-button size-12">
|
||||
100%
|
||||
</div>
|
||||
<div class="editor-size-button size-8">
|
||||
75%
|
||||
</div>
|
||||
<div class="editor-size-button size-6">
|
||||
50%
|
||||
</div>
|
||||
<div class="editor-size-button size-2">
|
||||
25%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<files-modal></files-modal>
|
||||
|
||||
<div>
|
@@ -1,12 +1,14 @@
|
||||
import { Component, ElementRef, ViewChild, TemplateRef, ComponentRef, ViewContainerRef } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Component, DestroyRef, inject } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { NewtonEditorComponent } from "./newton-editor/newton-editor.component";
|
||||
import { FilesModalComponent } from "./modals/files-modal.component";
|
||||
import { EditorsService } from '../common/services/editor/editors.service';
|
||||
import { FilesService } from '../common/services/editor/files.service';
|
||||
import { TabsService } from '../common/services/editor/tabs/tabs.service';
|
||||
import { FilesService } from '../common/services/files.service';
|
||||
|
||||
import { CodeViewComponent } from "./code-view/view.component";
|
||||
|
||||
import { DndDirective } from '../common/directives/dnd.directive';
|
||||
import { PaneHandleDirective } from '../common/directives/pane-handle.directive';
|
||||
import { NewtonFile } from '../common/types/file.type';
|
||||
import { ServiceMessage } from '../common/types/service-message.type';
|
||||
|
||||
@@ -17,7 +19,8 @@ import { ServiceMessage } from '../common/types/service-message.type';
|
||||
standalone: true,
|
||||
imports: [
|
||||
DndDirective,
|
||||
FilesModalComponent
|
||||
PaneHandleDirective,
|
||||
CodeViewComponent
|
||||
],
|
||||
templateUrl: './editors.component.html',
|
||||
styleUrl: './editors.component.css',
|
||||
@@ -26,116 +29,55 @@ import { ServiceMessage } from '../common/types/service-message.type';
|
||||
}
|
||||
})
|
||||
export class EditorsComponent {
|
||||
private unsubscribe = new Subject<void>();
|
||||
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
||||
|
||||
@ViewChild('containerRef', {read: ViewContainerRef}) containerRef!: ViewContainerRef;
|
||||
activeEditor!: string;
|
||||
private editorsService: EditorsService = inject(EditorsService);
|
||||
private tabsService: TabsService = inject(TabsService);
|
||||
private filesService: FilesService = inject(FilesService);
|
||||
|
||||
|
||||
constructor(
|
||||
private editorsService: EditorsService,
|
||||
private filesService: FilesService
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
this.loadMainSubscribers();
|
||||
|
||||
let leftEditor = this.createEditor();
|
||||
let rightEditor = this.createEditor();
|
||||
|
||||
this.activeEditor = leftEditor.instance.uuid;
|
||||
leftEditor.instance.isDefault = true;
|
||||
leftEditor.instance.rightSiblingUUID = rightEditor.instance.uuid;
|
||||
rightEditor.instance.leftSiblingUUID = leftEditor.instance.uuid;
|
||||
}
|
||||
|
||||
loadSubscribers() {
|
||||
|
||||
private loadSubscribers() {
|
||||
this.editorsService.getMessage$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
if (message.action == "select-left-editor") {
|
||||
let editorComponent = this.editorsService.get(message.editorUUID);
|
||||
if (!editorComponent.leftSiblingUUID) return;
|
||||
let siblingComponent = this.editorsService.get(editorComponent.leftSiblingUUID);
|
||||
siblingComponent.editor.focus()
|
||||
} else if (message.action == "select-right-editor") {
|
||||
let editorComponent = this.editorsService.get(message.editorUUID);
|
||||
if (!editorComponent.rightSiblingUUID) return;
|
||||
let siblingComponent = this.editorsService.get(editorComponent.rightSiblingUUID);
|
||||
siblingComponent.editor.focus()
|
||||
} else if (message.action == "move-session-left") {
|
||||
let editorComponent = this.editorsService.get(message.editorUUID);
|
||||
if (!editorComponent.leftSiblingUUID) return;
|
||||
let siblingComponent = this.editorsService.get(editorComponent.leftSiblingUUID);
|
||||
let session = editorComponent.editor.getSession();
|
||||
let siblingSession = siblingComponent.editor.getSession();
|
||||
|
||||
if (session == siblingSession) return;
|
||||
siblingComponent.editor.setSession(session);
|
||||
editorComponent.newBuffer();
|
||||
siblingComponent.editor.focus()
|
||||
} else if (message.action == "move-session-right") {
|
||||
let editorComponent = this.editorsService.get(message.editorUUID);
|
||||
if (!editorComponent.rightSiblingUUID) return;
|
||||
let siblingComponent = this.editorsService.get(editorComponent.rightSiblingUUID);
|
||||
let session = editorComponent.editor.getSession();
|
||||
let siblingSession = siblingComponent.editor.getSession();
|
||||
|
||||
if (session == siblingSession) return;
|
||||
siblingComponent.editor.setSession(session);
|
||||
editorComponent.newBuffer();
|
||||
siblingComponent.editor.focus()
|
||||
} else if (message.action == "set-active-editor") {
|
||||
this.editorsService.get(this.activeEditor).removeActiveStyling();
|
||||
this.activeEditor = message.editorUUID;
|
||||
this.editorsService.get(this.activeEditor).addActiveStyling();
|
||||
} else if (message.action == "set-tab-to-editor") {
|
||||
let file = this.filesService.get(message.filePath);
|
||||
let editorComponent = this.getActiveEditorComponent();
|
||||
let editor = editorComponent.editor;
|
||||
|
||||
editorComponent.activeFile = file;
|
||||
editor.setSession(file.session);
|
||||
} else if (message.action == "close-tab") {
|
||||
let file = this.filesService.get(message.filePath);
|
||||
let editors = this.editorsService.getEditorsAsArray();
|
||||
|
||||
for (let i = 0; i < editors.length; i++) {
|
||||
let editorComponent = editors[i].instance;
|
||||
if (editorComponent.editor.session == file.session) {
|
||||
editorComponent.newBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
this.filesService.delete(file);
|
||||
switch ( message.action ) {
|
||||
case "select-left-editor":
|
||||
this.selectLeftEditor(message);
|
||||
break;
|
||||
case "select-right-editor":
|
||||
this.selectRightEditor(message);
|
||||
break;
|
||||
case "move-session-left":
|
||||
this.moveSessionLeft(message);
|
||||
break;
|
||||
case "move-session-right":
|
||||
this.moveSessionRight(message);
|
||||
break;
|
||||
case "set-active-editor":
|
||||
this.setActiveEditor(message);
|
||||
break;
|
||||
case "set-tab-to-editor":
|
||||
this.setTabToEditor(message);
|
||||
break;
|
||||
case "close-tab":
|
||||
this.closeTab(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
loadMainSubscribers() {
|
||||
window.fs.onLoadFiles(async (paths: []) => {
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
let file = new File([], "") as NewtonFile;
|
||||
|
||||
if ( this.filesService.get(paths[i]) ) continue;
|
||||
|
||||
await this.filesService.addFile(paths[i], file);
|
||||
this.filesService.addTab(file);
|
||||
}
|
||||
|
||||
let path = paths[ paths.length - 1 ];
|
||||
let file = this.filesService.get(path);
|
||||
this.setSession(file);
|
||||
});
|
||||
|
||||
private loadMainSubscribers() {
|
||||
window.main.onMenuActions(async (action: string) => {
|
||||
let editorComponent = this.getActiveEditorComponent();
|
||||
let editorComponent = this.editorsService.getActiveEditorComponent();
|
||||
let editor = editorComponent.editor;
|
||||
|
||||
switch ( action ) {
|
||||
@@ -169,108 +111,175 @@ export class EditorsComponent {
|
||||
editor.showSettingsMenu();
|
||||
case "show-about":
|
||||
break;
|
||||
case "quit":
|
||||
window.main.quit();
|
||||
break;
|
||||
default:
|
||||
editor.execCommand(action);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsubscribe.next();
|
||||
this.unsubscribe.complete();
|
||||
}
|
||||
window.fs.onLoadFiles(async (paths: []) => {
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
let file = new File([], "") as NewtonFile;
|
||||
|
||||
// Note: Only really works with 2 editors and very brittle logic.
|
||||
protected setEditorSize(event: any) {
|
||||
let lEditorComponent = null;
|
||||
let rEditorComponent = null;
|
||||
let lSize = 6;
|
||||
let rSize = 6;
|
||||
if ( this.filesService.get(paths[i]) ) continue;
|
||||
|
||||
if (
|
||||
event.target.parentElement.classList.contains("editor-left-size")
|
||||
) {
|
||||
lSize = parseInt(
|
||||
event.target.classList[1].split("-")[1]
|
||||
);
|
||||
rSize = 12 - lSize;
|
||||
|
||||
lEditorComponent = this.editorsService.get(this.activeEditor);
|
||||
if (lEditorComponent.leftSiblingUUID) {
|
||||
rEditorComponent = lEditorComponent;
|
||||
lEditorComponent = this.editorsService.get(lEditorComponent.leftSiblingUUID);
|
||||
} else {
|
||||
rEditorComponent = this.editorsService.get(lEditorComponent.rightSiblingUUID);
|
||||
await this.filesService.addFile(paths[i], file);
|
||||
this.filesService.addTab(file);
|
||||
}
|
||||
} else {
|
||||
rSize = parseInt(
|
||||
event.target.classList[1].split("-")[1]
|
||||
);
|
||||
lSize = 12 - rSize;
|
||||
rEditorComponent = this.editorsService.get(this.activeEditor);
|
||||
if (rEditorComponent.rightSiblingUUID) {
|
||||
lEditorComponent = rEditorComponent;
|
||||
rEditorComponent = this.editorsService.get(rEditorComponent.rightSiblingUUID);
|
||||
} else {
|
||||
lEditorComponent = this.editorsService.get(rEditorComponent.leftSiblingUUID);
|
||||
}
|
||||
}
|
||||
|
||||
let lElm = lEditorComponent.editorElm.nativeElement.parentElement;
|
||||
let rElm = rEditorComponent.editorElm.nativeElement.parentElement;
|
||||
let path = paths[ paths.length - 1 ];
|
||||
let file = this.filesService.get(path);
|
||||
this.editorsService.setSession(file);
|
||||
});
|
||||
|
||||
lElm.setAttribute(
|
||||
'class',
|
||||
(lSize == 0) ? "hidden" : `col col-${lSize}`
|
||||
);
|
||||
window.fs.onChangedFile(async (path: string, data: string) => {
|
||||
let file = this.filesService.get(path);
|
||||
file.session.setValue(data);
|
||||
|
||||
rElm.setAttribute(
|
||||
'class',
|
||||
(rSize == 0) ? "hidden" : `col col-${rSize}`
|
||||
);
|
||||
// Note: fake 'save' event to not show as changed iven external save happened...
|
||||
let message = new ServiceMessage();
|
||||
message.action = "file-saved";
|
||||
message.filePath = path;
|
||||
|
||||
if (lSize == 0) rEditorComponent.editor.focus();
|
||||
if (rSize == 0) lEditorComponent.editor.focus();
|
||||
}
|
||||
this.tabsService.sendMessage(message);
|
||||
});
|
||||
|
||||
private createEditor() {
|
||||
const component = this.containerRef.createComponent(NewtonEditorComponent);
|
||||
component.instance.editorSettings = this.editorsService.editorSettings;
|
||||
this.editorsService.set(component.instance.uuid, component)
|
||||
window.fs.onDeletedFile(async (path: string) => {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "file-deleted";
|
||||
message.filePath = path;
|
||||
|
||||
this.tabsService.sendMessage(message);
|
||||
this.filesService.sendMessage(message);
|
||||
});
|
||||
|
||||
window.fs.onSavedFile(async (path: string) => {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "file-saved";
|
||||
message.filePath = path;
|
||||
|
||||
this.tabsService.sendMessage(message);
|
||||
});
|
||||
|
||||
window.fs.onUpdateFilePath(async (path: string) => {
|
||||
console.log("TODO (onUpdateFilePath) :", path);
|
||||
// this.tabsService.sendMessage(message);
|
||||
// this.filesService.sendMessage(message);
|
||||
});
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
protected onFileDropped(files: any) {
|
||||
this.filesService.loadFilesList(files).then((file: NewtonFile | undefined | null) => {
|
||||
this.setSession(file);
|
||||
// Note: if we drop an already loaded file the path doesn't get set and
|
||||
// therefor the last file in drop list might get returned without path.
|
||||
if (!file.path) return;
|
||||
|
||||
this.editorsService.setSession(file);
|
||||
});
|
||||
}
|
||||
|
||||
private async setSession(file: NewtonFile | undefined | null) {
|
||||
if ( !file ) return;
|
||||
let editorComponent = this.getActiveEditorComponent();
|
||||
let editor = editorComponent.editor;
|
||||
|
||||
editorComponent.activeFile = file;
|
||||
editor.setSession(file.session);
|
||||
private selectLeftEditor(message: ServiceMessage) {
|
||||
let editorComponent = this.editorsService.get(message.editorUUID);
|
||||
if (!editorComponent.leftSiblingUUID) return;
|
||||
let siblingComponent = this.editorsService.get(editorComponent.leftSiblingUUID);
|
||||
siblingComponent.editor.focus();
|
||||
}
|
||||
|
||||
private getSession() {
|
||||
let editorComponent = this.editorsService.get(this.activeEditor);
|
||||
private selectRightEditor(message: ServiceMessage) {
|
||||
let editorComponent = this.editorsService.get(message.editorUUID);
|
||||
if (!editorComponent.rightSiblingUUID) return;
|
||||
let siblingComponent = this.editorsService.get(editorComponent.rightSiblingUUID);
|
||||
siblingComponent.editor.focus();
|
||||
}
|
||||
|
||||
private moveSessionLeft(message: ServiceMessage) {
|
||||
let editorComponent = this.editorsService.get(message.editorUUID);
|
||||
if (!editorComponent.leftSiblingUUID) return;
|
||||
|
||||
let siblingComponent = this.editorsService.get(editorComponent.leftSiblingUUID);
|
||||
this.moveSession("left", editorComponent, siblingComponent);
|
||||
}
|
||||
|
||||
private moveSessionRight(message: ServiceMessage) {
|
||||
let editorComponent = this.editorsService.get(message.editorUUID);
|
||||
if (!editorComponent.rightSiblingUUID) return;
|
||||
|
||||
let siblingComponent = this.editorsService.get(editorComponent.rightSiblingUUID);
|
||||
this.moveSession("right", editorComponent, siblingComponent);
|
||||
}
|
||||
|
||||
private moveSession(
|
||||
direction: string,
|
||||
editorComponent: CodeViewComponent,
|
||||
siblingComponent: CodeViewComponent
|
||||
) {
|
||||
let session = editorComponent.editor.getSession();
|
||||
let siblingSession = siblingComponent.editor.getSession();
|
||||
|
||||
if (session == siblingSession) return;
|
||||
|
||||
let targetPath: string = this.tabsService.getRightSiblingTab(
|
||||
editorComponent.activeFile.path
|
||||
);
|
||||
|
||||
siblingComponent.assignSession(editorComponent.activeFile);
|
||||
if (targetPath) {
|
||||
editorComponent.assignSession(
|
||||
this.filesService.get(targetPath)
|
||||
);
|
||||
} else {
|
||||
editorComponent.newFile();
|
||||
}
|
||||
|
||||
siblingComponent.editor.focus()
|
||||
}
|
||||
|
||||
|
||||
private setActiveEditor(message: ServiceMessage) {
|
||||
this.editorsService.getActiveEditorComponent().removeActiveStyling();
|
||||
this.editorsService.setActiveEditor(message.editorUUID);
|
||||
this.editorsService.getActiveEditorComponent().addActiveStyling();
|
||||
}
|
||||
private setTabToEditor(message: ServiceMessage) {
|
||||
let file = this.filesService.get(message.filePath);
|
||||
let editorComponent = this.editorsService.getActiveEditorComponent();
|
||||
let editor = editorComponent.editor;
|
||||
|
||||
return editor.getSession();
|
||||
editorComponent.assignSession(file);
|
||||
this.editorsService.miniMapView.cloneSession(file);
|
||||
}
|
||||
|
||||
private getActiveEditorComponent(): any {
|
||||
return this.editorsService.get(this.activeEditor);
|
||||
}
|
||||
private closeTab(message: ServiceMessage) {
|
||||
let activeComponent = this.editorsService.getActiveEditorComponent();
|
||||
let editors = this.editorsService.getEditorsAsArray();
|
||||
let file = this.filesService.get(message.filePath);
|
||||
|
||||
private getActiveEditor(): any {
|
||||
let editorComponent = this.editorsService.get(this.activeEditor);
|
||||
let editor = editorComponent.editor;
|
||||
return editor;
|
||||
}
|
||||
for (let i = 0; i < editors.length; i++) {
|
||||
let editorComponent = editors[i];
|
||||
|
||||
if (editorComponent.editor.session !== file.session) continue;
|
||||
|
||||
let targetFile = this.filesService.getPreviousFile(file.path)
|
||||
if (targetFile && (targetFile.path !== message.filePath)) {
|
||||
editorComponent.assignSession(targetFile);
|
||||
if (activeComponent == editorComponent) {
|
||||
this.editorsService.miniMapView.cloneSession(targetFile);
|
||||
}
|
||||
} else {
|
||||
editorComponent.newFile();
|
||||
if (activeComponent == editorComponent) {
|
||||
this.editorsService.miniMapView.newFile();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
activeComponent.lspManagerService.closeDocument(file.session);
|
||||
this.filesService.unset(file);
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Component, DestroyRef, inject } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.service';
|
||||
|
||||
@@ -17,7 +17,9 @@ import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.s
|
||||
}
|
||||
})
|
||||
export class InfoBarComponent {
|
||||
private unsubscribe = new Subject<void>();
|
||||
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
||||
|
||||
private infoBarService: InfoBarService = inject(InfoBarService);
|
||||
|
||||
fpath: string = "";
|
||||
path: string = "";
|
||||
@@ -26,20 +28,14 @@ export class InfoBarComponent {
|
||||
ftype: string = "";
|
||||
|
||||
|
||||
constructor(
|
||||
private infoBarService: InfoBarService
|
||||
) {}
|
||||
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
|
||||
loadSubscribers() {
|
||||
|
||||
private loadSubscribers() {
|
||||
this.infoBarService.updateInfoBarFPath$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((fpath: string) => {
|
||||
this.fpath = fpath;
|
||||
let _path = fpath;
|
||||
@@ -52,19 +48,19 @@ export class InfoBarComponent {
|
||||
});
|
||||
|
||||
this.infoBarService.updateInfoBarCursorPos$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((cursorPos: any) => {
|
||||
this.cursorPos = `${cursorPos.row + 1}:${cursorPos.column}`;
|
||||
});
|
||||
|
||||
this.infoBarService.updateInfoBarEncodeing$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((encodeing: string) => {
|
||||
this.encodeing = encodeing;
|
||||
});
|
||||
|
||||
this.infoBarService.updateInfoBarFType$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((ftype: string) => {
|
||||
let mode = ftype.split("/");
|
||||
this.ftype = mode[ mode.length - 1 ];
|
||||
|
11
src/app/editor/lsp-manager/lsp-manager.component.css
Normal file
11
src/app/editor/lsp-manager/lsp-manager.component.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.lsp-config-text {
|
||||
display: grid;
|
||||
min-height: 25em;
|
||||
}
|
||||
|
||||
.clear-left-padding {
|
||||
padding-left: 0px;
|
||||
}
|
||||
.clear-right-padding {
|
||||
padding-right: 0px;
|
||||
}
|
66
src/app/editor/lsp-manager/lsp-manager.component.html
Normal file
66
src/app/editor/lsp-manager/lsp-manager.component.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row mt-2 mb-3">
|
||||
|
||||
<div class="col clear-right-padding">
|
||||
<div class="input-group-sm">
|
||||
<label class="form-control" [innerText]="lspManagerService.workspaceFolder || 'Project Path...'">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-auto clear-left-padding">
|
||||
<button class="btn btn-sm btn-dark" (click)="clearWorkspaceFolder()">x</button>
|
||||
</div>
|
||||
|
||||
<div class="col col-auto">
|
||||
<div class="input-group-sm">
|
||||
<button class="btn btn-sm btn-dark" (click)="setWorkspaceFolder()">Workspace Folder</button>
|
||||
<button class="btn btn-sm btn-danger ms-5" (click)="hideLspManager()">X</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mt-2 md-2">
|
||||
<div class="col">
|
||||
LSP Configs:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<code-view #lspEditorComponent [mode]="'standalone'" class="lsp-config-text"></code-view>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2 md-2">
|
||||
<div class="col col-sm" [hidden]="!lspManagerService.workspaceFolder">
|
||||
<button class="btn btn-sm btn-dark" (click)="createLanguageClient()">Create Language Client</button>
|
||||
<button class="btn btn-sm btn-dark" (click)="closeLanguageClient()">Close Language Client</button>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
Target Editor: <label [innerText]="editor?.id || '...'"></label>
|
||||
</div>
|
||||
<div class="col-sm" [hidden]="!lspManagerService.workspaceFolder">
|
||||
<button class="btn btn-sm btn-dark"
|
||||
(click)="registerEditorToLanguageClient()">
|
||||
Register Editor To LSP
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
Active Editor Session:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
<code-view #sessionEditorComponent [mode]="'read-only'" class="lsp-config-text"></code-view>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
160
src/app/editor/lsp-manager/lsp-manager.component.ts
Normal file
160
src/app/editor/lsp-manager/lsp-manager.component.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectorRef,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
ViewChild,
|
||||
inject } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { LspManagerService } from '../../common/services/editor/lsp-manager/lsp-manager.service';
|
||||
|
||||
import { CodeViewComponent } from '../code-view/view.component';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'lsp-manager',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CodeViewComponent
|
||||
],
|
||||
templateUrl: './lsp-manager.component.html',
|
||||
styleUrl: './lsp-manager.component.css',
|
||||
host: {
|
||||
'class': 'lsp-manager',
|
||||
"(keyup)": "globalLspManagerKeyHandler($event)"
|
||||
}
|
||||
})
|
||||
export class LspManagerComponent {
|
||||
readonly #destroyRef = inject(DestroyRef);
|
||||
private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef);
|
||||
|
||||
lspManagerService: LspManagerService = inject(LspManagerService);
|
||||
|
||||
@HostBinding("class.hidden") isHidden: boolean = true;
|
||||
@ViewChild('lspEditorComponent') lspEditorComponent!: CodeViewComponent;
|
||||
@ViewChild('sessionEditorComponent') sessionEditorComponent!: CodeViewComponent;
|
||||
lspTextEditor: any;
|
||||
innerEditor: any;
|
||||
editor: any;
|
||||
activeFile: any;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
private ngAfterViewInit(): void {
|
||||
this.mapEditorsAndLoadConfig();
|
||||
}
|
||||
|
||||
private mapEditorsAndLoadConfig() {
|
||||
this.lspTextEditor = this.lspEditorComponent.editor;
|
||||
this.innerEditor = this.sessionEditorComponent.editor;
|
||||
|
||||
this.lspTextEditor.on("input", () => {
|
||||
this.lspManagerService.lspConfigDataStr =
|
||||
this.lspTextEditor.session.getValue();
|
||||
});
|
||||
|
||||
this.lspManagerService.loadLspConfigData().then((lspConfigData) => {
|
||||
this.lspTextEditor.session.setMode("ace/mode/json");
|
||||
this.lspTextEditor.session.setValue(lspConfigData);
|
||||
});
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
this.lspManagerService.getMessage$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
if (message.action === "toggle-lsp-manager") {
|
||||
this.toggleLspManager(message);
|
||||
} else if (message.action === "set-active-editor") {
|
||||
this.setActiveEditor(message);
|
||||
} else if (message.action === "editor-update") {
|
||||
this.editorUpdate(message);
|
||||
} else if (message.action === "close-file") {
|
||||
this.closeFile(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public clearWorkspaceFolder() {
|
||||
this.lspManagerService.workspaceFolder = "";
|
||||
}
|
||||
|
||||
public setWorkspaceFolder() {
|
||||
window.fs.chooseFolder().then((folder: string) => {
|
||||
if (!folder) return;
|
||||
|
||||
this.lspManagerService.workspaceFolder = folder;
|
||||
});
|
||||
}
|
||||
|
||||
public createLanguageClient() {
|
||||
let mode = this.lspManagerService.getMode(this.editor.session);
|
||||
this.lspManagerService.createLanguageProviderWithClientServer(mode);
|
||||
}
|
||||
|
||||
public closeLanguageClient() {
|
||||
let mode = this.lspManagerService.getMode(this.editor.session);
|
||||
this.lspManagerService.closeLanguageProviderWithClientServer(mode);
|
||||
}
|
||||
|
||||
public registerEditorToLanguageClient() {
|
||||
this.lspManagerService.registerEditorToLSPClient(this.editor);
|
||||
}
|
||||
|
||||
|
||||
public globalLspManagerKeyHandler(event: any) {
|
||||
if (event.ctrlKey && event.shiftKey && event.key === "l") {
|
||||
this.hideLspManager();
|
||||
}
|
||||
}
|
||||
|
||||
public hideLspManager() {
|
||||
this.isHidden = true;
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
private toggleLspManager(message: ServiceMessage) {
|
||||
this.isHidden = !this.isHidden;
|
||||
|
||||
if (this.isHidden) return;
|
||||
|
||||
// Note: hack for issue with setActiveEditor TODO
|
||||
setTimeout(() => {
|
||||
this.innerEditor.setSession(this.editor.getSession());
|
||||
}, 10);
|
||||
}
|
||||
|
||||
private setActiveEditor(message: ServiceMessage) {
|
||||
this.editor = message.rawData.editor;
|
||||
this.activeFile = message.rawData.activeFile;
|
||||
|
||||
// TODO: figure out why this doesn't update the session consistently...
|
||||
// It seems maybe bound to visible state as change detector ref didn't help either.
|
||||
// this.innerEditor.setSession(this.editor.session);
|
||||
}
|
||||
|
||||
private editorUpdate(message: ServiceMessage) {
|
||||
if (
|
||||
!this.editor ||
|
||||
!message.rawData.activeFile
|
||||
) return;
|
||||
|
||||
this.editor.setSession(message.rawData.editor.getSession())
|
||||
this.activeFile = message.rawData.activeFile;
|
||||
|
||||
this.lspManagerService.registerSession(this.editor);
|
||||
}
|
||||
|
||||
private closeFile(message: ServiceMessage) {
|
||||
this.lspManagerService.closeDocument(message.rawData);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div [innerHtml]="bodyHtml || defaultHtml">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,97 @@
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
HostBinding,
|
||||
inject
|
||||
} from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { MarkdownPreviewService } from '../../common/services/editor/markdown-preview/markdown-preview.service';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'markdown-preview',
|
||||
standalone: true,
|
||||
imports: [
|
||||
],
|
||||
templateUrl: './markdown-preview.component.html',
|
||||
styleUrl: './markdown-preview.component.css',
|
||||
host: {
|
||||
'class': 'container-fluid markdown-preview'
|
||||
}
|
||||
})
|
||||
export class MarkdownPreviewComponent {
|
||||
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
||||
|
||||
private markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService);
|
||||
|
||||
@HostBinding("class.hidden") isHidden: boolean = true;
|
||||
converter: any = new showdown.Converter();
|
||||
defaultHtml: string = "<h1>NOT a Markdown file...</h1>"
|
||||
bodyHtml: string = "";
|
||||
|
||||
private editorComponent!: any;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
this.markdownPreviewService.getMessage$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
switch ( message.action ) {
|
||||
case "toggle-markdown-preview":
|
||||
this.toggleMarkdownPreview(message);
|
||||
break;
|
||||
case "set-active-editor":
|
||||
this.setActiveEditor(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private toggleMarkdownPreview(message: ServiceMessage) {
|
||||
this.isHidden = !this.isHidden;
|
||||
|
||||
setTimeout(() => {
|
||||
this.updatePreview();
|
||||
}, 10);
|
||||
}
|
||||
|
||||
private setActiveEditor(message: ServiceMessage) {
|
||||
if (this.editorComponent == message.rawData) return;
|
||||
|
||||
this.editorComponent = message.rawData;
|
||||
|
||||
if (this.isHidden) return;
|
||||
|
||||
this.updatePreview();
|
||||
}
|
||||
|
||||
public updatePreview() {
|
||||
let fileMode = this.editorComponent.editor.session.getMode()["$id"];
|
||||
let isMdFile = (fileMode.includes("markdown"));
|
||||
this.bodyHtml = "";
|
||||
|
||||
if (!isMdFile) return;
|
||||
|
||||
let mdStr = this.editorComponent.editor.session.getValue();
|
||||
let pathParts = this.editorComponent.activeFile.path.split("/");
|
||||
let basePath = "file://" + pathParts.slice(0, -1).join("/");
|
||||
this.bodyHtml = this.converter.makeHtml(
|
||||
mdStr.replaceAll("](images", `](${basePath}/images`)
|
||||
.replaceAll("](imgs", `](${basePath}/imgs`)
|
||||
.replaceAll("](pictures", `](${basePath}/pictures`)
|
||||
.replaceAll("](pics", `](${basePath}/pics`)
|
||||
.replaceAll("](screenshots", `](${basePath}/screenshots`)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -1,52 +0,0 @@
|
||||
<div #filesModal
|
||||
id="filesModal" class="modal fade" tabindex="-1" role="dialog"
|
||||
>
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Files:</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
||||
<div class="row">
|
||||
<div class="col modal-column">
|
||||
|
||||
<div #filesList *ngFor="let file of files" class="row">
|
||||
<div class="col-11 title"
|
||||
title="{{file.path}}"
|
||||
uuid="{{file.uuid}}"
|
||||
path="{{file.path}}"
|
||||
>
|
||||
{{file.title}}
|
||||
</div>
|
||||
<div class="col-1 close-button">
|
||||
X
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<input #filesSearch type="text" placeholder="Search..." />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col modal-column">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -1,85 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
|
||||
import { FilesModalService } from "../../common/services/editor/modals/files-modal.service";
|
||||
import { TabsService } from '../../common/services/editor/tabs/tabs.service';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'files-modal',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
templateUrl: './files-modal.component.html',
|
||||
styleUrl: './files-modal.component.css',
|
||||
host: {
|
||||
'class': ''
|
||||
}
|
||||
})
|
||||
export class FilesModalComponent {
|
||||
private unsubscribe = new Subject<void>();
|
||||
|
||||
filesModal!: bootstrap.Modal;
|
||||
files: any[] = [];
|
||||
|
||||
|
||||
constructor(
|
||||
private filesModalService: FilesModalService,
|
||||
private tabsService: TabsService
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
loadSubscribers() {
|
||||
this.tabsService.getMessage$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
).subscribe((data: ServiceMessage) => {
|
||||
if (data.action === "create-tab") {
|
||||
this.createFileRow(data.fileName, data.fileUUID, data.filePath);
|
||||
}
|
||||
});
|
||||
|
||||
this.filesModalService.showFilesModalRequested$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
).subscribe(() => {
|
||||
if (!this.filesModal) {
|
||||
this.createModal();
|
||||
}
|
||||
|
||||
this.showModal();
|
||||
});
|
||||
|
||||
this.filesModalService.addFileToModalRequested$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
).subscribe((uuid: string) => {
|
||||
if (!this.filesModal) {
|
||||
this.createModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createFileRow(title: string, uuid: string, path: string): void {
|
||||
this.files.push({title: title, uuid: uuid, path: path})
|
||||
}
|
||||
|
||||
createModal() {
|
||||
this.filesModal = new bootstrap.Modal("#filesModal", {});
|
||||
}
|
||||
|
||||
showModal() {
|
||||
this.filesModal?.toggle();
|
||||
}
|
||||
|
||||
}
|
@@ -1,153 +0,0 @@
|
||||
import { Directive, ElementRef, Input, ViewChild } from '@angular/core';
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
import { EditorSettings } from "../../common/configs/editor.config";
|
||||
import { NewtonFile } from '../../common/types/file.type';
|
||||
|
||||
|
||||
|
||||
@Directive()
|
||||
export class NewtonEditorBase {
|
||||
@ViewChild('editor') editorElm!: ElementRef;
|
||||
@Input() editorSettings!: typeof EditorSettings;
|
||||
editor!: any;
|
||||
uuid!: string;
|
||||
leftSiblingUUID!: string;
|
||||
rightSiblingUUID!: string;
|
||||
cutBuffer: string = "";
|
||||
timerId: number = -1;
|
||||
activeFile!: NewtonFile;
|
||||
isDefault: boolean = false;
|
||||
|
||||
|
||||
constructor(
|
||||
) {
|
||||
this.uuid = uuid.v4();
|
||||
}
|
||||
|
||||
|
||||
public addActiveStyling() {
|
||||
this.editorElm.nativeElement.classList.add("active-editor")
|
||||
}
|
||||
|
||||
public removeActiveStyling() {
|
||||
this.editorElm.nativeElement.classList.remove("active-editor")
|
||||
}
|
||||
|
||||
public openCommandPalette() {
|
||||
this.editor.execCommand("openCommandPalette");
|
||||
}
|
||||
|
||||
public showSettingsMenu() {
|
||||
this.editor.showSettingsMenu();
|
||||
}
|
||||
|
||||
public showKeyboardShortcuts() {
|
||||
this.editor.showKeyboardShortcuts();
|
||||
}
|
||||
|
||||
protected search() {
|
||||
console.log(this.editor.session.getMode()["$id"]);
|
||||
}
|
||||
|
||||
protected destroySession() {
|
||||
this.editor.session.destroy();
|
||||
}
|
||||
|
||||
protected openFiles() {
|
||||
let startDir = "";
|
||||
if (this.activeFile) {
|
||||
let pathParts = this.activeFile.path.split("/");
|
||||
pathParts.pop();
|
||||
startDir = pathParts.join( '/' );
|
||||
}
|
||||
|
||||
window.fs.openFiles(startDir);
|
||||
}
|
||||
|
||||
protected saveFile() {
|
||||
if (!this.activeFile) {
|
||||
this.saveFileAs();
|
||||
return;
|
||||
}
|
||||
|
||||
const text = this.activeFile.session.getValue();
|
||||
window.fs.saveFile(this.activeFile.path, text);
|
||||
}
|
||||
|
||||
protected saveFileAs() {
|
||||
const text = this.editor.session.getValue();
|
||||
window.fs.saveFileAs(text);
|
||||
}
|
||||
|
||||
protected zoomIn() {
|
||||
this.editor.setFontSize(
|
||||
parseInt(this.editor.getFontSize()) + 1
|
||||
)
|
||||
}
|
||||
|
||||
protected zoomOut() {
|
||||
this.editor.setFontSize(
|
||||
parseInt(this.editor.getFontSize()) - 1
|
||||
)
|
||||
}
|
||||
|
||||
protected cutText() {
|
||||
let cutText = this.editor.getSelectedText();
|
||||
this.editor.remove();
|
||||
navigator.clipboard.writeText(cutText).catch(() => {
|
||||
console.error("Unable to cut text...");
|
||||
});
|
||||
}
|
||||
|
||||
protected copyText() {
|
||||
let copyText = this.editor.getSelectedText();
|
||||
navigator.clipboard.writeText(copyText).catch(() => {
|
||||
console.error("Unable to copy text...");
|
||||
});
|
||||
}
|
||||
|
||||
protected pasteText() {
|
||||
navigator.clipboard.readText().then((pasteText) => {
|
||||
this.editor.insert(pasteText, true);
|
||||
});
|
||||
}
|
||||
|
||||
protected movelinesUp() {
|
||||
this.editor.execCommand("movelinesup");
|
||||
}
|
||||
|
||||
protected movelinesDown() {
|
||||
this.editor.execCommand("movelinesdown");
|
||||
}
|
||||
|
||||
protected duplicateLines() {
|
||||
this.editor.execCommand("copylinesdown");
|
||||
}
|
||||
|
||||
protected cutToBuffer() {
|
||||
if (this.timerId) { clearTimeout(this.timerId); }
|
||||
|
||||
const cursorPosition = this.editor.getCursorPosition();
|
||||
let lineText = this.editor.session.getLine(cursorPosition.row);
|
||||
this.cutBuffer += `${lineText}\n`;
|
||||
|
||||
this.editor.session.removeFullLines(cursorPosition.row, cursorPosition.row)
|
||||
this.setBufferClearTimeout();
|
||||
}
|
||||
|
||||
protected pasteCutBuffer() {
|
||||
if (this.timerId) { clearTimeout(this.timerId); }
|
||||
|
||||
this.editor.insert(this.cutBuffer, true);
|
||||
this.setBufferClearTimeout();
|
||||
}
|
||||
|
||||
private setBufferClearTimeout(timeout: number = 5000) {
|
||||
this.timerId = setTimeout(() => {
|
||||
this.cutBuffer = "";
|
||||
this.timerId = -1;
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
.editor {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.editor {
|
||||
height: 100vh;
|
||||
width: auto;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
.editor {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.active-editor {
|
||||
border-style: solid;
|
||||
border-width: thin;
|
||||
border-color: rgba(124, 124, 124, 1);
|
||||
}
|
@@ -1,2 +0,0 @@
|
||||
<div class="editor" #editor >
|
||||
</div>
|
@@ -1,182 +0,0 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
// Import Ace and its modes/themes so that `ace` global is defined
|
||||
import * as ace from "ace-builds/src-noconflict/ace";
|
||||
import "ace-builds/src-noconflict/ext-settings_menu";
|
||||
import "ace-builds/src-noconflict/ext-keybinding_menu";
|
||||
import "ace-builds/src-noconflict/ext-command_bar";
|
||||
import "ace-builds/src-noconflict/ext-language_tools";
|
||||
import "ace-builds/src-noconflict/theme-one_dark";
|
||||
import "ace-builds/src-noconflict/theme-dracula";
|
||||
|
||||
import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.service';
|
||||
import { FilesModalService } from '../../common/services/editor/modals/files-modal.service';
|
||||
import { LSPService } from '../../common/services/lsp.service';
|
||||
import { EditorsService } from '../../common/services/editor/editors.service';
|
||||
|
||||
import { NewtonEditorBase } from './newton-editor.base';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'newton-editor',
|
||||
standalone: true,
|
||||
imports: [
|
||||
],
|
||||
templateUrl: './newton-editor.component.html',
|
||||
styleUrl: './newton-editor.component.css',
|
||||
host: {
|
||||
'class': 'col col-6'
|
||||
}
|
||||
})
|
||||
export class NewtonEditorComponent extends NewtonEditorBase {
|
||||
|
||||
|
||||
constructor(
|
||||
private infoBarService: InfoBarService,
|
||||
private editorsService: EditorsService,
|
||||
private lspService: LSPService,
|
||||
private filesModalService: FilesModalService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
if (this.isDefault) {
|
||||
this.addActiveStyling();
|
||||
}
|
||||
|
||||
this.loadAce();
|
||||
}
|
||||
|
||||
public loadAce(): void {
|
||||
ace.config.set('basePath', this.editorSettings.BASE_PATH);
|
||||
|
||||
this.editor = ace.edit( this.editorElm.nativeElement );
|
||||
this.editor.setOptions( this.editorSettings.CONFIG );
|
||||
|
||||
let keyBindings = [];
|
||||
for (let i = 0; i < this.editorSettings.KEYBINDINGS.length; i++) {
|
||||
let keyBinding = this.editorSettings.KEYBINDINGS[i];
|
||||
keyBindings.push(
|
||||
{
|
||||
name: keyBinding.name,
|
||||
bindKey: keyBinding.bindKey,
|
||||
exec: (keyBinding.name && keyBinding?.service) ?
|
||||
() => (
|
||||
this[keyBinding?.service][keyBinding.name]()
|
||||
)
|
||||
:
|
||||
(this[keyBinding.name]) ?
|
||||
() => (
|
||||
this[keyBinding.name]()
|
||||
)
|
||||
:
|
||||
() => (
|
||||
console.log(
|
||||
`Name: ${keyBinding.name}, is not mapping to a method OR mapping to a Service: ${keyBinding?.service} and Name: ${keyBinding.name}.`
|
||||
)
|
||||
)
|
||||
,
|
||||
readOnly: keyBinding.readOnly
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.editor.commands.addCommands( keyBindings );
|
||||
|
||||
// Note: https://ajaxorg.github.io/ace-api-docs/interfaces/ace.Ace.EditorEvents.html
|
||||
this.editor.on("focus", (e) => {
|
||||
this.updateInfoBar();
|
||||
});
|
||||
|
||||
this.editor.on("click", () => {
|
||||
this.updateInfoBar();
|
||||
});
|
||||
|
||||
this.editor.on("input", () => {
|
||||
this.updateInfoBar();
|
||||
});
|
||||
|
||||
this.editor.on("keyboardActivity", (e) => {
|
||||
switch(e.command.name) {
|
||||
case "golineup":
|
||||
case "golinedown":
|
||||
case "gotoleft":
|
||||
case "gotoright":
|
||||
this.infoBarService.setInfoBarCursorPos(
|
||||
this.editor.getCursorPosition()
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.on("focus", () => {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "set-active-editor";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
});
|
||||
|
||||
this.editor.on("changeSession", (session) => {
|
||||
this.lspService.registerEditor(this.editor);
|
||||
this.updateInfoBar();
|
||||
});
|
||||
}
|
||||
|
||||
public updateInfoBar() {
|
||||
this.infoBarService.setInfoBarFPath(this.activeFile?.path)
|
||||
this.infoBarService.setInfoBarCursorPos(
|
||||
this.editor.getCursorPosition()
|
||||
);
|
||||
this.infoBarService.setInfoBarFType(
|
||||
this.editor.session.getMode()["$id"]
|
||||
);
|
||||
}
|
||||
|
||||
public newBuffer() {
|
||||
let buffer = ace.createEditSession([""]);
|
||||
this.editor.setSession(buffer);
|
||||
this.activeFile = null;
|
||||
this.updateInfoBar();
|
||||
}
|
||||
|
||||
public selectLeftEditor() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "select-left-editor";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public selectRightEditor() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "select-right-editor";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public moveSessionLeft() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "move-session-left";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public moveSessionRight() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "move-session-right";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
}
|
39
src/app/editor/search-replace/search-replace.component.css
Normal file
39
src/app/editor/search-replace/search-replace.component.css
Normal file
@@ -0,0 +1,39 @@
|
||||
.buttons > button {
|
||||
min-width: 3.5em;
|
||||
min-height: 2.5em;
|
||||
}
|
||||
|
||||
.width-8em {
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
.margin-tb-1em {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: rgba(125, 125, 125, 1);
|
||||
color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.searching,
|
||||
.search-success,
|
||||
.search-fail {
|
||||
border-style: solid;
|
||||
color: rgba(125, 125, 125, 1) !important;
|
||||
}
|
||||
|
||||
.searching {
|
||||
border-color: rgba(0, 225, 225, 0.64) !important;
|
||||
}
|
||||
|
||||
.search-success {
|
||||
background: rgba(136, 204, 39, 0.12) !important;
|
||||
border-color: rgba(136, 204, 39, 1) !important;
|
||||
}
|
||||
|
||||
.search-fail {
|
||||
background: rgba(170, 18, 18, 0.12) !important;
|
||||
border-color: rgba(200, 18, 18, 1) !important;
|
||||
}
|
84
src/app/editor/search-replace/search-replace.component.html
Normal file
84
src/app/editor/search-replace/search-replace.component.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col col-3">
|
||||
<label id="find-status-lbl">Find in Current File</label>
|
||||
</div>
|
||||
|
||||
<div class="col col-4">
|
||||
<label id="find-options-lbl">Finding with Options: {{findOptions || "Case Insensitive"}}</label>
|
||||
</div>
|
||||
|
||||
<div class="col col-5 line-height-32px buttons">
|
||||
<button title="Close Panel"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="hideSearchReplace()">X
|
||||
</button>
|
||||
<button id="whole-word-btn" title="Whole Word"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleWholeWordSearch($event)">
|
||||
<img src="resources/imgs/whole-word.png" />
|
||||
</button>
|
||||
<button id="only-in-selection-btn" title="Only In Selection"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleSelectionOnlyScan($event)">
|
||||
<img src="resources/imgs/only-in-selection.png" />
|
||||
</button>
|
||||
<button id="match-case-btn" title="Match Case"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleCaseSensitive($event)">Aa
|
||||
</button>
|
||||
<button id="use-regex-btn" title="Use Regex"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleRegex($event)">.*
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="margin-tb-1em"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group-sm mb-3">
|
||||
<input #findEntryElm
|
||||
id="find-entry"
|
||||
class="form-control"
|
||||
type="search"
|
||||
(keyup)="searchForString()"
|
||||
placeholder="Find in current file..."
|
||||
aria-label="Find in current file..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-auto">
|
||||
<button id="find-btn" class="width-8em btn btn-sm btn-dark" (click)="findNextEntry()">Find</button>
|
||||
<button id="find-all-btn" class="width-8em btn btn-sm btn-dark" (click)="findAllEntries()">Find All</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group-sm mb-3">
|
||||
<input #replaceEntryElm
|
||||
id="replace-entry"
|
||||
class="form-control"
|
||||
type="search"
|
||||
(keyup)="replaceEntry($event)"
|
||||
title="Replace in current file..."
|
||||
placeholder="Replace in current file..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-auto">
|
||||
<button id="replace-btn" class="width-8em btn btn-sm btn-dark" (click)="replaceEntry($event)">Replace</button>
|
||||
<button id="replace-all-btn" class="width-8em btn btn-sm btn-dark" (click)="replaceAll()">Replace All</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
272
src/app/editor/search-replace/search-replace.component.ts
Normal file
272
src/app/editor/search-replace/search-replace.component.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
Input,
|
||||
ViewChild,
|
||||
inject
|
||||
} from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'search-replace',
|
||||
standalone: true,
|
||||
imports: [
|
||||
],
|
||||
templateUrl: './search-replace.component.html',
|
||||
styleUrl: './search-replace.component.css',
|
||||
host: {
|
||||
'class': 'row search-replace',
|
||||
"(keyup)": "globalSearchReplaceKeyHandler($event)"
|
||||
}
|
||||
})
|
||||
export class SearchReplaceComponent {
|
||||
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
||||
|
||||
private searchReplaceService: SearchReplaceService = inject(SearchReplaceService);
|
||||
|
||||
@HostBinding("class.hidden") isHidden: boolean = true;
|
||||
@ViewChild('findEntryElm') findEntryElm!: ElementRef;
|
||||
@ViewChild('replaceEntryElm') replaceEntryElm!: ElementRef;
|
||||
|
||||
private editor!: any;
|
||||
|
||||
@Input() findOptions: string = "";
|
||||
private useWholeWordSearch: boolean = false;
|
||||
private searchOnlyInSelection: boolean = false;
|
||||
private useCaseSensitive: boolean = false;
|
||||
private useRegex: boolean = false;
|
||||
private selection: string = "";
|
||||
private query: string = "";
|
||||
private toStr: string = "";
|
||||
private isBackwards: boolean = false;
|
||||
private isWrap: boolean = true;
|
||||
private searchTimeoutId: number = -1;
|
||||
private searchTimeout: number = 400;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
this.searchReplaceService.getMessage$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
switch ( message.action ) {
|
||||
case "toggle-search-replace":
|
||||
this.toggleSearchReplace(message);
|
||||
break;
|
||||
case "set-active-editor":
|
||||
this.setActiveEditor(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private toggleSearchReplace(message: ServiceMessage) {
|
||||
this.selection = this.editor.getSelectedText();
|
||||
this.findEntryElm.nativeElement.value = this.selection;
|
||||
|
||||
if (this.selection && !this.isHidden) {
|
||||
this.findEntryElm.nativeElement.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isHidden = !this.isHidden;
|
||||
|
||||
if (this.isHidden) {
|
||||
this.editor.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.findEntryElm.nativeElement.focus();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
private setActiveEditor(message: ServiceMessage) {
|
||||
if (this.editor == message.rawData) return;
|
||||
|
||||
this.editor = message.rawData;
|
||||
|
||||
if (this.isHidden) return;
|
||||
|
||||
|
||||
this.searchForString();
|
||||
}
|
||||
|
||||
public hideSearchReplace() {
|
||||
if (this.selection) {
|
||||
this.selection = "";
|
||||
return;
|
||||
}
|
||||
|
||||
this.isHidden = true;
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
public globalSearchReplaceKeyHandler(event: any) {
|
||||
if (event.ctrlKey && event.key === "f") {
|
||||
this.hideSearchReplace();
|
||||
} else if (event.ctrlKey && event.key === "l") {
|
||||
this.findEntryElm.nativeElement.focus();
|
||||
} else if (event.ctrlKey && event.key === "r") {
|
||||
this.replaceEntryElm.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public toggleWholeWordSearch(event: any) {
|
||||
let target = event.target;
|
||||
if (target.nodeName === "IMG")
|
||||
target = target.parentElement;
|
||||
|
||||
this.useWholeWordSearch = !this.useWholeWordSearch;
|
||||
target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
public toggleSelectionOnlyScan(event: any) {
|
||||
let target = event.target;
|
||||
if (target.nodeName === "IMG")
|
||||
target = target.parentElement;
|
||||
|
||||
this.searchOnlyInSelection = !this.searchOnlyInSelection;
|
||||
target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
public toggleCaseSensitive(event: any) {
|
||||
this.useCaseSensitive = !this.useCaseSensitive;
|
||||
event.target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
public toggleRegex(event: any) {
|
||||
this.useRegex = !this.useRegex;
|
||||
event.target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
private setFindOptionsLbl() {
|
||||
let findOptionsStr = "";
|
||||
|
||||
if (this.useRegex)
|
||||
findOptionsStr += "Regex"
|
||||
|
||||
findOptionsStr += (findOptionsStr) ? ", " : "";
|
||||
findOptionsStr += (this.useCaseSensitive) ? "Case Sensitive" : "Case InSensitive";
|
||||
|
||||
if (this.searchOnlyInSelection)
|
||||
findOptionsStr += ", Within Current Selection"
|
||||
|
||||
if (this.useWholeWordSearch)
|
||||
findOptionsStr += ", Whole Word"
|
||||
|
||||
this.findOptions = findOptionsStr;
|
||||
}
|
||||
|
||||
|
||||
public findNextEntry() {
|
||||
this.editor.findNext();
|
||||
}
|
||||
|
||||
public findAllEntries() {
|
||||
this.query = this.findEntryElm.nativeElement.value;
|
||||
|
||||
if (!this.query) return;
|
||||
|
||||
let totalCount = this.editor.findAll(this.query, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
}
|
||||
|
||||
public findPreviousEntry() {
|
||||
this.editor.findPrevious();
|
||||
}
|
||||
|
||||
public replaceEntry(event: any) {
|
||||
if (event instanceof KeyboardEvent) {
|
||||
if (event.key !== "Enter") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let fromStr = this.findEntryElm.nativeElement.value;
|
||||
let toStr = this.replaceEntryElm.nativeElement.value;
|
||||
|
||||
if (!fromStr) return;
|
||||
|
||||
let totalCount = this.editor.replace(toStr, fromStr, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
|
||||
this.editor.clearSelection();
|
||||
this.editor.findNext();
|
||||
}
|
||||
|
||||
public replaceAll() {
|
||||
let fromStr = this.findEntryElm.nativeElement.value;
|
||||
let toStr = this.replaceEntryElm.nativeElement.value;
|
||||
|
||||
if (!fromStr) return;
|
||||
|
||||
let totalCount = this.editor.replaceAll(toStr, fromStr, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
}
|
||||
|
||||
public searchForString() {
|
||||
if (event instanceof KeyboardEvent) {
|
||||
if (event.key !== "Enter") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.query = this.findEntryElm.nativeElement.value;
|
||||
|
||||
if (!this.query) return;
|
||||
|
||||
if (this.searchTimeoutId) { clearTimeout(this.searchTimeoutId); }
|
||||
|
||||
this.searchTimeoutId = setTimeout(() => {
|
||||
let totalCount = this.editor.find(this.query, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
}, this.searchTimeout);
|
||||
}
|
||||
|
||||
}
|
@@ -19,17 +19,30 @@
|
||||
border-right-style: solid;
|
||||
border-right-color: #ffffff64;
|
||||
border-right-width: 2px;
|
||||
}
|
||||
|
||||
.active-tab {
|
||||
background-color: rgba(255, 255, 255, 0.46);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-changed {
|
||||
color: rgba(255, 168, 0, 0.64);
|
||||
}
|
||||
|
||||
.file-deleted {
|
||||
color: rgba(255, 0, 0, 0.64);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
font-size: 4em;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
|
@@ -1,9 +1,13 @@
|
||||
import { Component, ChangeDetectorRef } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectorRef,
|
||||
DestroyRef,
|
||||
inject
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { EditorsService } from '../../common/services/editor/editors.service';
|
||||
import { TabsService } from '../../common/services/editor/tabs/tabs.service';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
@@ -25,75 +29,76 @@ import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
}
|
||||
})
|
||||
export class TabsComponent {
|
||||
private unsubscribe = new Subject<void>();
|
||||
readonly #destroyRef = inject(DestroyRef);
|
||||
|
||||
activeTab!: string;
|
||||
tabs: any[] = [];
|
||||
newIndex: number = -1;
|
||||
private tabsService: TabsService = inject(TabsService);
|
||||
private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef);
|
||||
|
||||
tabs: any[] = this.tabsService.tabs;
|
||||
|
||||
|
||||
constructor(
|
||||
private editorsService: EditorsService,
|
||||
private tabsService: TabsService,
|
||||
private changeDetectorRef: ChangeDetectorRef
|
||||
) {
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
|
||||
private loadSubscribers() {
|
||||
this.tabsService.getMessage$().pipe(
|
||||
takeUntil(this.unsubscribe)
|
||||
).subscribe((data: ServiceMessage) => {
|
||||
if (data.action === "create-tab") {
|
||||
this.createTab(data.fileName, data.fileUUID, data.filePath);
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
let elm = document.querySelectorAll(`[title="${message.filePath}"]`)[1];
|
||||
|
||||
switch ( message.action ) {
|
||||
case "create-tab":
|
||||
this.createTab(message.fileName, message.fileUUID, message.filePath);
|
||||
break;
|
||||
case "file-changed":
|
||||
elm.classList.add("file-changed");
|
||||
elm.classList.remove("file-deleted");
|
||||
break;
|
||||
case "file-deleted":
|
||||
elm.classList.add("file-deleted");
|
||||
elm.classList.remove("file-changed");
|
||||
break;
|
||||
case "file-saved":
|
||||
elm.classList.remove("file-deleted");
|
||||
elm.classList.remove("file-changed");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.unsubscribe.next();
|
||||
this.unsubscribe.complete();
|
||||
}
|
||||
|
||||
private createTab(title: string, uuid: string, path: string): void {
|
||||
this.tabs.push({title: title, uuid: uuid, path: path});
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
handleAction(event: any): void {
|
||||
protected handleAction(event: any): void {
|
||||
let target = event.target;
|
||||
|
||||
if ( target.classList.contains("tab") ) {
|
||||
this.sendEditorsServiceAMessage(
|
||||
this.tabsService.sendEditorsServiceAMessage(
|
||||
"set-tab-to-editor",
|
||||
event.srcElement.getAttribute("title")
|
||||
);
|
||||
|
||||
} else if ( target.classList.contains("title") ) {
|
||||
this.sendEditorsServiceAMessage(
|
||||
this.tabsService.sendEditorsServiceAMessage(
|
||||
"set-tab-to-editor",
|
||||
event.srcElement.parentElement.getAttribute("title")
|
||||
);
|
||||
} else if ( target.classList.contains("close-button") ) {
|
||||
this.closeTab(
|
||||
this.tabsService.closeTab(
|
||||
event.srcElement.parentElement.getAttribute("title")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
closeTab(fpath: string): void {
|
||||
this.sendEditorsServiceAMessage("close-tab", fpath);
|
||||
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i].path == fpath) {
|
||||
this.tabs.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public createTab(title: string, uuid: string, path: string): void {
|
||||
this.tabsService.push({title: title, uuid: uuid, path: path});
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
|
||||
moved(event: any): void {
|
||||
private moved(event: any): void {
|
||||
let target = event.event.target;
|
||||
let fpath = "";
|
||||
|
||||
@@ -105,31 +110,11 @@ export class TabsComponent {
|
||||
fpath = target.getAttribute("title")
|
||||
)
|
||||
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i].path == fpath) {
|
||||
this.newIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
this.tabsService.setNewTargetIndex(fpath);
|
||||
}
|
||||
|
||||
dropped(event: CdkDragDrop<any>): void {
|
||||
if (this.newIndex == -1) return;
|
||||
|
||||
moveItemInArray(this.tabs, event.previousIndex, this.newIndex);
|
||||
this.newIndex = -1;
|
||||
|
||||
// event.currentIndex not updating for some reason...
|
||||
// moveItemInArray(this.tabs, event.previousIndex, event.currentIndex);
|
||||
}
|
||||
|
||||
|
||||
private sendEditorsServiceAMessage(action: string, fpath: string) {
|
||||
let message = new ServiceMessage();
|
||||
message.action = action;
|
||||
message.filePath = fpath;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
protected dropped(event: CdkDragDrop<any>): void {
|
||||
this.tabsService.move(event.previousIndex);
|
||||
}
|
||||
|
||||
}
|
@@ -3,7 +3,7 @@
|
||||
|
||||
/* IDs */
|
||||
#ace_settingsmenu, #kbshortcutmenu {
|
||||
background-color: rgba(0, 0, 0, 0.0);
|
||||
background-color: rgba(64, 64, 64, 0.84);
|
||||
color: rgba(255, 255, 255, 1.0);
|
||||
box-shadow: -1px 4px 5px rgba(124, 124, 124, 0.64);
|
||||
padding: 1em 0.5em 2em 1em;
|
||||
@@ -38,6 +38,19 @@
|
||||
color: rgba(255, 255, 255, 1.0);
|
||||
}
|
||||
|
||||
.ace_optionsMenuEntry button {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.ace_optionsMenuEntry button[ace_selected_button=true] {
|
||||
background: #00e877;
|
||||
}
|
||||
|
||||
.ace_print-margin {
|
||||
width: 1px !important;
|
||||
background-color: rgba(255, 0, 0, 0.84) !important;
|
||||
}
|
||||
|
||||
.ace_selected {
|
||||
background: rgba(249, 148, 6, 0.64);
|
||||
color: rgba(0, 0, 0, 1.0);
|
||||
@@ -47,4 +60,16 @@
|
||||
.ace_sb-v,
|
||||
.ace_sb-h {
|
||||
width: 0.8em !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ace_cursor {
|
||||
color: rgba(249, 148, 6, 0.64);
|
||||
animation: blinker 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blinker {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
21
src/assets/css/overrides.css
Normal file
21
src/assets/css/overrides.css
Normal file
@@ -0,0 +1,21 @@
|
||||
/* TAGS */
|
||||
|
||||
html {
|
||||
background-color: rgba(40, 44, 52, 0.64);
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: rgba(64, 64, 64, 0.0);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ul, li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
|
||||
/* IDs */
|
||||
|
||||
|
||||
/* CLASSES */
|
@@ -1,24 +1,45 @@
|
||||
/* TAGS */
|
||||
|
||||
html {
|
||||
/*
|
||||
background-color: rgba(64, 64, 64, 0.64);
|
||||
background-color: rgb(40, 44, 52);
|
||||
*/
|
||||
background-color: rgba(40, 44, 52, 0.64);
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: rgba(64, 64, 64, 0.0)
|
||||
}
|
||||
|
||||
|
||||
/* IDs */
|
||||
|
||||
|
||||
/* CLASSES */
|
||||
|
||||
.search-replace,
|
||||
.markdown-preview,
|
||||
.lsp-manager {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
background-color: rgba(64, 64, 64, 0.84);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.search-replace {
|
||||
bottom: 2em;
|
||||
left: 2em;
|
||||
right: 2em;
|
||||
z-index: 900;
|
||||
}
|
||||
|
||||
.markdown-preview {
|
||||
top: 2em;
|
||||
bottom: 2em;
|
||||
right: 2em;
|
||||
z-index: 700;
|
||||
width: 50vw;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.lsp-manager {
|
||||
top: 2em;
|
||||
bottom: 2em;
|
||||
left: 2em;
|
||||
right: 2em;
|
||||
z-index: 800;
|
||||
}
|
||||
|
||||
|
||||
.info-bar {
|
||||
font-size: 0.8em;
|
||||
color: rgba(255, 255, 255, 0.84);
|
||||
@@ -36,19 +57,31 @@ body {
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
|
||||
.hr-pane-handle,
|
||||
.vr-pane-handle {
|
||||
border: 2px dashed lightblue;
|
||||
}
|
||||
|
||||
.vr-pane-handle {
|
||||
max-width: max-content;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.zero-margin-padding {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scroller {
|
||||
/*
|
||||
-webkit-scrollbar-color: #00000084 #ffffff64;
|
||||
scrollbar-color: #00000084 #ffffff64;
|
||||
*/
|
||||
-webkit-scrollbar-color: #00000084 #ffffff06;
|
||||
-webkit-scrollbar-width: thin;
|
||||
scrollbar-color: #00000084 #ffffff06;
|
||||
|
3
src/libs/showdown.min.js
vendored
Normal file
3
src/libs/showdown.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3978
src/libs/tree-sitter.js
Normal file
3978
src/libs/tree-sitter.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/libs/tree-sitter.wasm
Executable file
BIN
src/libs/tree-sitter.wasm
Executable file
Binary file not shown.
@@ -20,16 +20,23 @@ declare global {
|
||||
},
|
||||
main: {
|
||||
onMenuActions: (arg0: any) => Promise<string>,
|
||||
quit: any,
|
||||
toggleFullScreen: any,
|
||||
},
|
||||
fs: {
|
||||
getLspConfigData: () => Promise<string>,
|
||||
getFileContents: (arg0: any) => Promise<string>,
|
||||
openFiles: (arg0) => Promise<string>,
|
||||
saveFile: (arg0: any, arg1: any) => Promise<string>,
|
||||
saveFileAs: (arg0: any) => Promise<string>,
|
||||
saveFileAs: () => Promise<string>,
|
||||
chooseFolder: () => Promise<string>,
|
||||
closeFile: (arg0: any) => Promise<string>,
|
||||
getPathForFile: any,
|
||||
onLoadFiles: (arg0: any) => Promise<string>,
|
||||
onUpdateFilePath: (arg0: any) => Promise<string>,
|
||||
onSavedFile: (arg0: any) => Promise<string>,
|
||||
onChangedFile: (arg0: any) => Promise<string>,
|
||||
onDeletedFile: (arg0: any) => Promise<string>,
|
||||
}
|
||||
}
|
||||
}
|
1
src/typings.d.ts
vendored
Normal file
1
src/typings.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare var showdown: any;
|
@@ -13,4 +13,4 @@
|
||||
"src/polyfills.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
@@ -16,8 +16,11 @@
|
||||
"declaration": false,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
},
|
||||
"includes": [
|
||||
"src/typings.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +56,10 @@
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
},
|
||||
"includes": [
|
||||
"src/typings.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
*/
|
Reference in New Issue
Block a user