34 Commits

Author SHA1 Message Date
9aae679d21 Adding images to README file 2026-03-21 15:41:33 -05:00
d179e1c42e WIP to handle outside of electron build 2025-12-10 06:56:24 +00:00
da4e87d3cd WIP new websocket setup; build setup changes; types cleanup 2025-12-10 04:37:26 +00:00
f1e3557db1 Correcting angular.json file styles field 2025-12-02 20:26:23 -06:00
dd969302cf Upgrading glob and rimraf; downgrading electron-builder; reverting font additions 2025-12-02 20:23:36 -06:00
21cf5c794a Adding fonts 2025-12-02 18:11:29 -06:00
a4765952bf Adding pre-dynamic load of css and js 2025-11-27 23:01:55 -06:00
de5184ce22 Added show and focus on ipc load-file 2025-11-23 16:11:04 -06:00
de0802cb2c removing WIP terminal 2025-11-23 15:49:48 -06:00
c4ab66141c package.json dependency movement to dev deps; updated build field too 2025-11-23 15:11:21 -06:00
6bedb69909 Improved search-replace component logic by disabing buttons when no query; displaying found/not found status; displaying too long query; adding ctrl-up and down key bindig on query field to god prev-next instance found 2025-11-23 01:13:58 -06:00
8b47ff3919 Fixing file edit status; modified build options 2025-11-17 19:10:09 -06:00
fd40340d97 Removed logging entries; added tab bottom border 2025-11-10 22:20:05 -06:00
ea134caf2b Added tab highlighting and scroll into view logic 2025-11-08 17:24:40 -06:00
88b38370e1 added context menu for lsp modal field; cleaned up context menu css locations 2025-11-08 13:32:23 -06:00
ae6c21f8ad Added right click options to tabs bar 2025-11-08 01:20:38 -06:00
f94a8ca26c Moving to 'marked' for markdown parsing; added reference dependencies for future consideration 2025-10-07 19:29:55 -05:00
ed89f34d40 WIP terminal integration 2025-09-04 00:26:22 -05:00
73f25aae1c Cleaning up type asignment 2025-08-23 14:58:23 -05:00
3c3a5d2f50 Removed empty constructors 2025-08-23 14:20:37 -05:00
d44e7d4e51 Moved loading of subscribers to constructors; improved subscriber destruction pattern; code quality improvements 2025-08-23 14:07:19 -05:00
60289953ec Update ace-linters; WIP color tokenizer 2025-08-11 19:22:06 -05:00
df5f2d481a Various LSP or LSP tangental changes 2025-08-09 15:57:07 -05:00
2ede33f3c2 Setting context menu bg color; aligning minimap scroll styling; adding wrap mode for minimap to remove hscroll bar 2025-07-26 01:28:03 -05:00
822d778008 WIP context menu added; upgraded ace-linters; integrated auto session register 2025-07-26 00:43:45 -05:00
90c8c9b3ee Wiring lsp config editor to update lspManagerService.lspConfigDataStr; removed redundant check; update lsp config 2025-07-19 02:13:52 -05:00
79adb86d25 Fixed editors.component close-tab when 1 file remains 2025-07-16 23:39:00 -05:00
a0914e8096 Refactored editors.component and tabs.service 2025-07-15 23:11:37 -05:00
a4d602f98a Added mouse scroll zoom 2025-07-15 21:21:14 -05:00
d6f766753c Update close file logic to select prior if exists; bug fix in LSP regarding editor and assignment 2025-07-15 19:46:17 -05:00
ae60905eb4 Adding quit to menus 2025-07-14 23:42:51 -05:00
080cc22841 Making IPC port kwarg configurable; improving kwarhs filter out 2025-07-14 23:00:36 -05:00
c5fdab59f6 Bumping ace-linters to fix desync issue 2025-07-14 21:33:20 -05:00
ab17e48338 Changing cursor color and blink settings 2025-07-13 20:55:17 -05:00
57 changed files with 1399 additions and 637 deletions

View File

@@ -1 +1,17 @@
# Newton # Newton
### Images
![1 Newton default view.](images/pic1.png)
![2 Newton split pane view.](images/pic2.png)
![3 Newton search and replace shown.](images/pic3.png)
![4 Newton as transparent with youtube playing below it.](images/pic4.png)
### Additional Tools
* [Fzf](https://github.com/junegunn/fzf)
* [bat (cat but has highlighting)](https://github.com/sharkdp/bat)
* [ripgrep (very fast grep alternative)](https://github.com/BurntSushi/ripgrep/tree/master)
`
sudo pacman -Sy bat ripgrep fzf
`

View File

@@ -43,12 +43,11 @@
"styles":[ "styles":[
"node_modules/bootstrap/scss/bootstrap.scss", "node_modules/bootstrap/scss/bootstrap.scss",
"node_modules/bootstrap-icons/font/bootstrap-icons.css", "node_modules/bootstrap-icons/font/bootstrap-icons.css",
"src/assets/css/overrides.css",
"src/assets/css/styles.css", "src/assets/css/styles.css",
"src/assets/css/overrides.css"
"src/assets/css/ace-overrides.css" "src/assets/css/ace-overrides.css"
], ],
"scripts":[ "scripts":[
"src/libs/showdown.min.js"
], ],
"optimization": true "optimization": true
}, },

BIN
images/pic1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 KiB

BIN
images/pic2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
images/pic3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
images/pic4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB

View File

@@ -5,6 +5,7 @@ const { menu } = require('./menu');
const { systemTray } = require('./system-tray'); const { systemTray } = require('./system-tray');
const { argsParser } = require('./args-parser'); const { argsParser } = require('./args-parser');
const { settingsManager } = require('./settings-manager'); const { settingsManager } = require('./settings-manager');
const { terminal } = require('./terminal');
const { newtonFs } = require('./fs'); const { newtonFs } = require('./fs');
const { newtonIPC } = require('./ipc'); const { newtonIPC } = require('./ipc');
@@ -53,8 +54,23 @@ const createWindow = (startType = "build", debug = false, args = []) => {
window.webContents.send('load-files', args); window.webContents.send('load-files', args);
}); });
window.webContents.on('did-finish-load', () => {
const cssFiles = [
path.join(newtonFs.CONFIG_PATH, 'override.css')
];
const jsFiles = [
// path.join(newtonFs.CONFIG_PATH, 'scripts', 'script1.js'),
// path.join(newtonFs.CONFIG_PATH, 'scripts', 'script2.js')
];
cssFiles.forEach(cssFile => newtonFs.readAndInjectCSS(window, cssFile));
jsFiles.forEach(jsFile => newtonFs.readAndInjectJS(window, jsFile));
});
menu.load(window); menu.load(window);
systemTray.load(menu.menuStruct); systemTray.load(menu.menuStruct);
terminal.load(window);
// window.setAutoHideMenuBar(true) // window.setAutoHideMenuBar(true)

View File

@@ -1,9 +1,10 @@
const { app } = require('electron'); const { app } = require('electron');
let startType = "build"; let startType = "build";
let isDebug = false; let ipcPort = "4563";
let args = []; let isDebug = false;
let args = [];
@@ -17,6 +18,13 @@ const loadKWArgs = () => {
console.log(startType); 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"); const hasDebug = app.commandLine.hasSwitch("app-debug");
if (hasDebug) { if (hasDebug) {
isDebug = app.commandLine.getSwitchValue("app-debug"); isDebug = app.commandLine.getSwitchValue("app-debug");
@@ -25,38 +33,29 @@ const loadKWArgs = () => {
} }
} }
const loadVArgs = () => { const filterOutLaunchAndKWArgs = () => {
console.log("\n\nStart VArgs:");
if ( if (
process.argv[0].endsWith("electron") process.argv[0].endsWith("electron")
) { ) {
process.argv = process.argv.slice(2); process.argv = process.argv.slice(2);
} }
if ( do {
process.argv[0].endsWith("/newton") ||
process.argv[0].endsWith(".AppImage")
) {
process.argv = process.argv.slice(1); process.argv = process.argv.slice(1);
} } while (
process.argv.length > 0 &&
if ( process.argv.length > 0 && ( (
process.argv[0].includes("--trace-warnings") || process.argv[0].endsWith("/newton") ||
process.argv[0].includes("--start-as") 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 = process.argv;
args.forEach((val, index, array) => { args.forEach((val, index, array) => {
console.log(index + ': ' + val); console.log(index + ': ' + val);
@@ -67,6 +66,7 @@ const loadVArgs = () => {
const loadArgs = () => { const loadArgs = () => {
loadKWArgs(); loadKWArgs();
filterOutLaunchAndKWArgs();
loadVArgs(); loadVArgs();
} }
@@ -83,6 +83,10 @@ const getDebugMode = () => {
return isDebug; return isDebug;
} }
const getIpcPort = () => {
return ipcPort;
}
module.exports = { module.exports = {
argsParser: { argsParser: {
@@ -90,5 +94,6 @@ module.exports = {
getArgs: getArgs, getArgs: getArgs,
getStartType: getStartType, getStartType: getStartType,
getDebugMode: getDebugMode, getDebugMode: getDebugMode,
getIpcPort: getIpcPort,
} }
}; };

View File

@@ -114,7 +114,6 @@ const chooseFolder = () => {
console.debug("Canceled folder selection..."); console.debug("Canceled folder selection...");
return ""; return "";
} }
console.log(response)
return response.filePaths[0]; return response.filePaths[0];
}); });
@@ -186,10 +185,33 @@ const closeFile = (fpath) => {
unwatchFile(fpath); unwatchFile(fpath);
} }
const readAndInjectCSS = (window, filePath) => {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error(`Error reading CSS file: ${filePath}`, err);
return;
}
window.webContents.insertCSS(data);
});
}
const readAndInjectJS = (window, filePath) => {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error(`Error reading JS file: ${filePath}`, err);
return;
}
window.webContents.executeJavaScript(data);
});
}
module.exports = { module.exports = {
newtonFs: { newtonFs: {
CONFIG_PATH: CONFIG_PATH,
setWindow: setWindow, setWindow: setWindow,
chooseFolder: chooseFolder, chooseFolder: chooseFolder,
openFiles: openFiles, openFiles: openFiles,
@@ -201,6 +223,8 @@ module.exports = {
getLspConfigData: getLspConfigData, getLspConfigData: getLspConfigData,
getSettingsConfigData: getSettingsConfigData, getSettingsConfigData: getSettingsConfigData,
saveSettingsConfigData: saveSettingsConfigData, saveSettingsConfigData: saveSettingsConfigData,
readAndInjectJS: readAndInjectJS,
readAndInjectCSS: readAndInjectCSS,
loadFilesWatcher: loadFilesWatcher, loadFilesWatcher: loadFilesWatcher,
unwatchFile: unwatchFile, unwatchFile: unwatchFile,
} }

View File

@@ -8,12 +8,17 @@ const fetch = require('electron-fetch').default
const IPC_SERVER_IP = "127.0.0.1"; const IPC_SERVER_IP = "127.0.0.1";
let window = null; let window = null;
let ipcServer = null; let ipcServer = null;
let ipcServerPort = "4563"; let ipcServerPort = "";
let ipcServerURL = `http://${IPC_SERVER_IP}:${ipcServerPort}`; let ipcServerURL = "";
const setWindow = (win) => { const setWindow = (win) => {
window = win; window = win;
}
const configure = (ipcPort) => {
ipcServerPort = ipcPort;
ipcServerURL = `http://${IPC_SERVER_IP}:${ipcServerPort}`;
} }
const loadIPCServer = (fpath) => { const loadIPCServer = (fpath) => {
@@ -37,6 +42,9 @@ const loadIPCServer = (fpath) => {
console.debug("Load File(s) : ", req.body); console.debug("Load File(s) : ", req.body);
window.webContents.send('load-files', req.body); window.webContents.send('load-files', req.body);
window.show();
window.focus();
res.status(200).send(''); res.status(200).send('');
}); });
@@ -47,7 +55,8 @@ const loadIPCServer = (fpath) => {
} }
const isIPCServerUp = async () => { 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."); console.debug("IPCServer (status) : Not up; okay to start.");
return { return {
text: () => { text: () => {
@@ -73,9 +82,10 @@ const sendFilesToIPC = async (files) => {
module.exports = { module.exports = {
newtonIPC: { newtonIPC: {
setWindow: setWindow, configure: configure,
loadIPCServer: loadIPCServer, loadIPCServer: loadIPCServer,
isIPCServerUp: isIPCServerUp, isIPCServerUp: isIPCServerUp,
sendFilesToIPC: sendFilesToIPC, sendFilesToIPC: sendFilesToIPC,
setWindow: setWindow
} }
}; };

View File

@@ -80,6 +80,7 @@ app.whenReady().then(async () => {
loadProcessSignalHandlers(); loadProcessSignalHandlers();
newton.args.loadArgs(); newton.args.loadArgs();
newton.ipc.configure( newton.args.getIpcPort() );
if ( !await newton.ipc.isIPCServerUp() ) { if ( !await newton.ipc.isIPCServerUp() ) {
newton.ipc.loadIPCServer(); newton.ipc.loadIPCServer();
} else { } else {

View File

@@ -22,6 +22,9 @@ const load = (win) => {
}, { }, {
label: 'Terminal', label: 'Terminal',
click: () => {} click: () => {}
}, {
label: "Quit",
click: () => win.webContents.send('menu-actions', "quit")
} }
] ]
}, { }, {

View File

@@ -9,6 +9,7 @@ contextBridge.exposeInMainWorld('electron', {
contextBridge.exposeInMainWorld('main', { contextBridge.exposeInMainWorld('main', {
onMenuActions: (callback) => ipcRenderer.on('menu-actions', (_event, action) => callback(action)), onMenuActions: (callback) => ipcRenderer.on('menu-actions', (_event, action) => callback(action)),
onTerminalActions: (callback) => ipcRenderer.on('terminal-actions', (_event, action) => callback(action)),
quit: () => ipcRenderer.invoke("quit"), quit: () => ipcRenderer.invoke("quit"),
toggleFullScreen: () => ipcRenderer.invoke("toggleFullScreen"), toggleFullScreen: () => ipcRenderer.invoke("toggleFullScreen"),
}); });

View File

@@ -33,6 +33,9 @@ const load = (win) => {
}, { }, {
label: 'Help', label: 'Help',
click: () => win.webContents.send('menu-actions', "show-about") click: () => win.webContents.send('menu-actions', "show-about")
}, {
label: 'Quit',
click: () => win.webContents.send('menu-actions', "quit")
} }
]; ];

35
newton/terminal.js Normal file
View File

@@ -0,0 +1,35 @@
const { ipcMain } = require('electron');
// const pty = require('node-pty');
const os = require("os");
const shell = "win32" === os.platform() ? "powershell.exe" : "bash";
const load = (win) => {
// const ptyProcess = pty.spawn(shell, [], {
// name: "xterm-color",
// cols: 172,
// rows: 256,
// cwd: process.env.HOME,
// env: process.env
// });
// ptyProcess.on('data', function(data) {
// win.webContents.send("terminal-actions", data);
// });
// ipcMain.on("terminal-keystroke", (event, key) => {
// ptyProcess.write(key);
// });
}
module.exports = {
terminal: {
load: load
}
};

View File

@@ -9,15 +9,18 @@
"main": "newton/main.js", "main": "newton/main.js",
"private": true, "private": true,
"scripts": { "scripts": {
"app": "ng build --base-href ./ && 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", "electron-start": "electron . --trace-warnings --start-as=build --ipc-port=4588",
"electron-pack": "ng build --base-href ./ && electron-builder --dir", "electron-pack": "ng build --base-href ./ && electron-builder --dir",
"electron-dist": "ng build --base-href ./ && electron-builder", "electron-dist": "ng build --base-href ./ && electron-builder",
"electron-dist-linux": "ng build --base-href ./ && electron-builder --linux deb zip AppImage", "electron-dist-zip-linux": "ng build --base-href ./ && electron-builder --linux zip",
"electron-dist-deb-linux": "ng build --base-href ./ && electron-builder --linux deb",
"electron-dist-appimage-linux": "ng build --base-href ./ && electron-builder --linux AppImage",
"electron-dist-all-linux": "ng build --base-href ./ && electron-builder --linux deb zip AppImage",
"electron-dist-all": "ng build --base-href ./ && electron-builder -mwl", "electron-dist-all": "ng build --base-href ./ && electron-builder -mwl",
"electron-concurrently": "concurrently 'ng serve' 'electron . --trace-warnings --start-as=ng-serve'", "electron-concurrently": "concurrently 'ng serve' 'electron . --trace-warnings --start-as=ng-serve'",
"ng-serve": "ng serve", "ng-serve": "ng serve",
"ng-build": "ng build", "ng-build": "ng build --base-href ./",
"ng-watch-build": "ng build --watch --configuration development", "ng-watch-build": "ng build --watch --configuration development",
"ng-test": "ng test", "ng-test": "ng test",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
@@ -27,10 +30,7 @@
"icon": "./icos/", "icon": "./icos/",
"files": [ "files": [
"newton/", "newton/",
"build/", "build/"
"!node_modules/ace-builds/",
"!node_modules/web-streams-polyfill/",
"!node_modules/@angular/"
], ],
"mac": { "mac": {
"category": "public.app-category.developer-tools" "category": "public.app-category.developer-tools"
@@ -44,45 +44,50 @@
"maintainer": "ITDominator" "maintainer": "ITDominator"
} }
}, },
"postinstall": "electron-builder install-app-deps", "overrides": {
"glob": "9.0.0",
"rimraf": "4.3.1"
},
"dependencies": { "dependencies": {
"@angular/cdk": "19.2.0",
"@angular/common": "19.2.0",
"@angular/core": "19.2.0",
"@angular/forms": "19.2.0",
"@angular/platform-browser": "19.2.0",
"ace-builds": "1.43.0",
"ace-diff": "3.0.3", "ace-diff": "3.0.3",
"ace-layout": "1.5.0", "ace-layout": "1.5.0",
"ace-linters": "1.7.0", "ace-linters": "1.8.3",
"bootstrap": "5.3.6", "bootstrap": "5.3.6",
"bootstrap-icons": "1.12.1", "bootstrap-icons": "1.12.1",
"chokidar": "4.0.3", "chokidar": "4.0.3",
"electron-fetch": "1.9.1", "electron-fetch": "1.9.1",
"express": "4.18.2", "express": "4.18.2",
"node-fetch": "3.3.2", "marked": "16.4.0",
"rxjs": "7.8.0",
"socket.io": "4.8.1", "socket.io": "4.8.1",
"uuid": "11.1.0", "uuid": "11.1.0",
"web-tree-sitter": "0.25.8",
"zone.js": "0.15.0" "zone.js": "0.15.0"
}, },
"devDependencies": { "devDependencies": {
"ace-builds": "1.43.0",
"@angular-devkit/build-angular": "19.2.8", "@angular-devkit/build-angular": "19.2.8",
"@angular/cdk": "19.2.0",
"@angular/common": "19.2.0",
"@angular/core": "19.2.0",
"@angular/cli": "19.2.8", "@angular/cli": "19.2.8",
"@angular/compiler-cli": "19.2.0", "@angular/compiler-cli": "19.2.0",
"@angular/forms": "19.2.0",
"@angular/platform-browser": "19.2.0",
"@types/express": "4.17.17", "@types/express": "4.17.17",
"@types/jasmine": "5.1.0", "@types/jasmine": "5.1.0",
"@types/node": "18.18.0", "@types/node": "18.18.0",
"concurrently": "9.1.2", "concurrently": "9.1.2",
"electron": "36.2.0", "electron": "36.2.0",
"electron-builder": "26.0.12", "@electron/remote": "2.1.2",
"electron-builder": "22.7.0",
"jasmine-core": "5.6.0", "jasmine-core": "5.6.0",
"jimp": "1.6.0",
"karma": "6.4.0", "karma": "6.4.0",
"karma-chrome-launcher": "3.2.0", "karma-chrome-launcher": "3.2.0",
"karma-coverage": "2.2.0", "karma-coverage": "2.2.0",
"karma-jasmine": "5.1.0", "karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.1.0", "karma-jasmine-html-reporter": "2.1.0",
"nanoevents": "9.1.0",
"rxjs": "7.8.0",
"tree-sitter": "0.21.1", "tree-sitter": "0.21.1",
"tree-sitter-bash": "0.23.2", "tree-sitter-bash": "0.23.2",
"tree-sitter-c": "0.23.1", "tree-sitter-c": "0.23.1",
@@ -106,4 +111,4 @@
"tslib": "2.3.0", "tslib": "2.3.0",
"typescript": "5.7.2" "typescript": "5.7.2"
} }
} }

View File

@@ -13,58 +13,73 @@
"alt-socket": "ws://127.0.0.1:9999/?name=java-language-server", "alt-socket": "ws://127.0.0.1:9999/?name=java-language-server",
"initialization-options": { "initialization-options": {
"bundles": [ "bundles": [
"intellicode-core.jar"
], ],
"workspaceFolders": [ "workspaceFolders": [
"file://{workspace.folder}" "file://{workspace.folder}"
], ],
"extendedClientCapabilities": { "extendedClientCapabilities": {
"classFileContentsSupport": true, "classFileContentsSupport": true,
"executeClientCommandSupport": true "executeClientCommandSupport": false
}, },
"settings": { "settings": {
"java": { "java": {
"autobuild": { "autobuild": {
"enabled": false "enabled": true
}, },
"completion": { "jdt": {
"enabled": true, "ls": {
"importOrder": [ "javac": {
"java", "enabled": true
"javax", },
"org", "java": {
"com" "home": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2"
] },
"lombokSupport": {
"enabled": true
},
"protobufSupport":{
"enabled": true
},
"androidSupport": {
"enabled": true
}
}
}, },
"configuration": { "configuration": {
"updateBuildConfiguration": "automatic",
"maven": { "maven": {
"userSettings": "{user.home}/.config/jdtls/settings.xml", "userSettings": "{user.home}/.config/lsps/jdtls/settings.xml",
"globalSettings": "{user.home}/.config/jdtls/settings.xml" "globalSettings": "{user.home}/.config/lsps/jdtls/settings.xml"
}, },
"runtimes": [ "runtimes": [
{ {
"name": "JavaSE-17", "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/", "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 "default": true
} }
] ]
}, },
"classPath": [ "classPath": [
"{user.home}/.config/jdtls/m2/repository/**/*-sources.jar", "{user.home}/.config/lsps/jdtls/m2/repository/**/*-sources.jar",
"lib/**/*-sources.jar" "lib/**/*-sources.jar"
], ],
"docPath": [ "docPath": [
"{user.home}/.config/jdtls/m2/repository/**/*-javadoc.jar", "{user.home}/.config/lsps/jdtls/m2/repository/**/*-javadoc.jar",
"lib/**/*-javadoc.jar" "lib/**/*-javadoc.jar"
], ],
"silentNotification": true,
"project": { "project": {
"encoding": "ignore", "encoding": "ignore",
"outputPath": "bin", "outputPath": "bin",
"referencedLibraries": [ "referencedLibraries": [
"lib/**/*.jar", "{user.home}/.config/lsps/jdtls/m2/repository/**/*.jar",
"{user.home}/.config/jdtls/m2/repository/**/*.jar" "lib/**/*.jar"
], ],
"importOnFirstTimeStartup": "automatic", "importOnFirstTimeStartup": "automatic",
"importHint": true, "importHint": true,
@@ -74,7 +89,7 @@
], ],
"sourcePaths": [ "sourcePaths": [
"src", "src",
"{user.home}/.config/jdtls/m2/repository/**/*.jar" "{user.home}/.config/lsps/jdtls/m2/repository/**/*.jar"
] ]
}, },
"sources": { "sources": {
@@ -104,9 +119,9 @@
"enabled": true "enabled": true
}, },
"version": "", "version": "",
"home": "abs(static/gradle-7.3.3)", "home": "{user.home}/Portable_Apps/sdks/gradle/gradle-9.0.0",
"java": { "java": {
"home": "abs(static/launch_jres/17.0.6-linux-x86_64)" "home": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2"
}, },
"offline": { "offline": {
"enabled": false "enabled": false
@@ -132,14 +147,65 @@
"downloadSources": true, "downloadSources": true,
"updateSnapshots": true "updateSnapshots": true
}, },
"silentNotification": true,
"contentProvider": {
"preferred": "fernflower"
},
"signatureHelp": { "signatureHelp": {
"enabled": true, "enabled": true,
"description": { "description": {
"enabled": true "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": { "implementationsCodeLens": {
"enabled": true "enabled": true
},
"referencesCodeLens": {
"enabled": true
},
"progressReports": {
"enabled": false
},
"saveActions": {
"organizeImports": true
} }
} }
} }
@@ -154,51 +220,36 @@
"socket": "ws://127.0.0.1:9999/python", "socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=pylsp", "socket-two": "ws://127.0.0.1:9999/?name=pylsp",
"initialization-options": { "initialization-options": {
"pyls": {
"plugins": {
"pycodestyle": {
"enabled": false
},
"pydocstyle": {
"enabled": false
},
"pyflakes": {
"enabled": false
},
"pylint": {
"enabled": false
},
"mccabe": {
"enabled": false
}
}
},
"pylsp": { "pylsp": {
"rope": {
"ropeFolder": "{user.home}/.config/lsps/ropeproject"
},
"plugins": { "plugins": {
"pycodestyle": { "ruff": {
"enabled": false "enabled": true,
"extendSelect": ["I"],
"lineLength": 80
}, },
"pydocstyle": { "pycodestyle": {
"enabled": false "enabled": false
}, },
"pyflakes": { "pyflakes": {
"enabled": false "enabled": false
}, },
"pylint": { "pylint": {
"enabled": false "enabled": true
}, },
"mccabe": { "mccabe": {
"enabled": false "enabled": false
}, },
"ruff": true,
"pylsp_rope": { "pylsp_rope": {
"rename": true "rename": false
}, },
"rope_rename": { "rope_rename": {
"enabled": true "enabled": false
}, },
"rope_autoimport": { "rope_autoimport": {
"enabled": true "enabled": false
}, },
"rope_completion": { "rope_completion": {
"enabled": false, "enabled": false,
@@ -213,7 +264,7 @@
"include_function_objects": true, "include_function_objects": true,
"fuzzy": false "fuzzy": false
}, },
"jedi":{ "jedi": {
"root_dir": "file://{workspace.folder}", "root_dir": "file://{workspace.folder}",
"extra_paths": [ "extra_paths": [
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages" "{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
@@ -248,7 +299,13 @@
], ],
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python", "environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
"symbols": { "symbols": {
"ignoreFolders": [".nox", ".tox", ".venv", "__pycache__", "venv"], "ignoreFolders": [
".nox",
".tox",
".venv",
"__pycache__",
"venv"
],
"maxSymbols": 20 "maxSymbols": 20
} }
} }

View File

@@ -1,6 +1,7 @@
<div class="col"> <div class="col">
<info-bar></info-bar> <info-bar></info-bar>
<tabs></tabs> <tabs></tabs>
<hr class="tabs-bar-underline"/>
<editors></editors> <editors></editors>
<search-replace></search-replace> <search-replace></search-replace>
<markdown-preview></markdown-preview> <markdown-preview></markdown-preview>

View File

@@ -1,4 +1,6 @@
import { Component } from '@angular/core'; import { Component, inject } from '@angular/core';
import { WebsocketService } from './common/services/websocket.service';
import { InfoBarComponent } from './editor/info-bar/info-bar.component'; import { InfoBarComponent } from './editor/info-bar/info-bar.component';
import { TabsComponent } from './editor/tabs/tabs.component'; import { TabsComponent } from './editor/tabs/tabs.component';
@@ -28,6 +30,69 @@ import { LspManagerComponent } from "./editor/lsp-manager/lsp-manager.component"
export class AppComponent { export class AppComponent {
title = 'Newton'; title = 'Newton';
constructor() {} protected ws: WebsocketService = inject(WebsocketService);
constructor() {
this.checkIfNotElectronMode();
}
ngOnInit() {}
checkIfNotElectronMode() {
if (
window.electron ||
window.main ||
window.fs
) { return; }
this.setupWebsocket();
this.setupWindowBindings();
}
setupWindowBindings() {
window.electron ??= {
node: () => { return "" },
chrome: () => { return "" },
electron: () => { return "" },
};
window.main ??= {
onMenuActions: () => {},
onTerminalActions: () => {},
quit: () => {},
toggleFullScreen: () => {},
};
window.fs ??= {
getLspConfigData: () => {
return new Promise((resolve, reject) => {
resolve("{}");
});
},
getFileContents: () => {},
openFiles: () => {},
saveFile: () => {},
saveFileAs: () => {},
chooseFolder: () => {},
closeFile: () => {},
getPathForFile: () => {},
onLoadFiles: () => {},
onUpdateFilePath: () => {},
onSavedFile: () => {},
onChangedFile: () => {},
onDeletedFile: () => {},
};
}
setupWebsocket() {
// TODO: Set with dynamic address and port
this.ws.connect('ws://localhost:7272').subscribe(msg => {
console.log(msg);
console.log(window.fs);
// this.ws.send("{ 'text': 'Hello server!' }");
});
}
} }

View File

@@ -1,7 +1,11 @@
import { Component, inject } from "@angular/core"; import {
Component,
DestroyRef,
inject
} from "@angular/core";
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Subject, takeUntil } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import * as bootstrap from "bootstrap"; import * as bootstrap from "bootstrap";
@@ -25,17 +29,18 @@ import 'ace-diff/dist/ace-diff-dark.min.css';
} }
}) })
export class DiffModalComponent { export class DiffModalComponent {
readonly #destroyRef: DestroyRef = inject(DestroyRef);
diffModal!: bootstrap.Modal; diffModal!: bootstrap.Modal;
constructor() { constructor() {
this.loadSubscribers();
} }
private ngAfterViewInit(): void { private ngAfterViewInit(): void {
this.loadDiffView(); this.loadDiffView();
this.loadSubscribers();
} }
private loadDiffView() { private loadDiffView() {

View File

@@ -37,6 +37,11 @@ export const Keybindings: Array<{}> = [
bindKey: {win: "ctrl-r", mac: "ctrl-r"}, bindKey: {win: "ctrl-r", mac: "ctrl-r"},
readOnly: false readOnly: false
}, { }, {
name: "terminalPopup",
bindKey: {win: "ctrl-shift-.", mac: "ctrl-shift-."},
readOnly: false
}, {
name: "newFile", name: "newFile",
bindKey: {win: "ctrl-t", mac: "ctrl-t"}, bindKey: {win: "ctrl-t", mac: "ctrl-t"},
service: "editorsService", service: "editorsService",

View File

@@ -0,0 +1,5 @@
export abstract class ButtonMap {
static LEFT: number = 0;
static MIDDLE: number = 1;
static RIGHT: number = 2;
}

View File

@@ -16,7 +16,7 @@ import { NewtonFile } from '../types/file.type';
}) })
export class DndDirective { export class DndDirective {
@HostBinding('class.fileover') fileOver!: boolean; @HostBinding('class.fileover') fileOver!: boolean;
@Output() fileDropped = new EventEmitter<any>(); @Output() fileDropped: EventEmitter<any> = new EventEmitter();
@HostListener('dragover', ['$event']) @HostListener('dragover', ['$event'])
onDragOver(evt: any) { onDragOver(evt: any) {

View File

@@ -11,11 +11,11 @@ import {
selector: '[draggable-item]' selector: '[draggable-item]'
}) })
export class DraggableDirective { export class DraggableDirective {
@Output() dragStart = new EventEmitter<PointerEvent>(); @Output() dragStart: EventEmitter<PointerEvent> = new EventEmitter();
@Output() dragMove = new EventEmitter<PointerEvent>(); @Output() dragMove: EventEmitter<PointerEvent> = new EventEmitter();
@Output() dragEnd = new EventEmitter<PointerEvent>(); @Output() dragEnd: EventEmitter<PointerEvent> = new EventEmitter();
private dragging = false; private dragging: boolean = false;
selected: any; selected: any;

View File

@@ -11,9 +11,9 @@ import {
selector: '[pane-handle]' selector: '[pane-handle]'
}) })
export class PaneHandleDirective { export class PaneHandleDirective {
@Output() dragStart = new EventEmitter<PointerEvent>(); @Output() dragStart: EventEmitter<PointerEvent> = new EventEmitter();
@Output() dragMove = new EventEmitter<PointerEvent>(); @Output() dragMove: EventEmitter<PointerEvent> = new EventEmitter();
@Output() dragEnd = new EventEmitter<PointerEvent>(); @Output() dragEnd: EventEmitter<PointerEvent> = new EventEmitter();
private dragging: boolean = false; private dragging: boolean = false;
private isHrPane: boolean = false; private isHrPane: boolean = false;

View 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;
}
}

View File

@@ -15,21 +15,15 @@ import { EditorType } from '../../types/editor.type';
providedIn: 'root' providedIn: 'root'
}) })
export class EditorsService { export class EditorsService {
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1); private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
editors: Map<string, CodeViewComponent>; editors: Map<string, CodeViewComponent> = new Map();
editorSettings: typeof EditorSettings; editorSettings: typeof EditorSettings = EditorSettings;
activeEditor!: string; activeEditor: string = "";
miniMapView!: CodeViewComponent; miniMapView!: CodeViewComponent;
constructor() {
this.editorSettings = EditorSettings;
this.editors = new Map<string, CodeViewComponent>();
}
public getEditorsAsArray(): CodeViewComponent[] { public getEditorsAsArray(): CodeViewComponent[] {
return [...this.editors.values()]; return [...this.editors.values()];
} }

View File

@@ -8,14 +8,11 @@ import { ServiceMessage } from '../../../types/service-message.type';
providedIn: 'root' providedIn: 'root'
}) })
export class InfoBarService { export class InfoBarService {
private dataSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1); private dataSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
private fpathSubject: ReplaySubject<string> = new ReplaySubject<string>(1); private fpathSubject: ReplaySubject<string> = new ReplaySubject(1);
private cursorPosSubject: ReplaySubject<any> = new ReplaySubject<any>(1); private cursorPosSubject: ReplaySubject<any> = new ReplaySubject(1);
private encodeingSubject: ReplaySubject<string> = new ReplaySubject<string>(1); private encodeingSubject: ReplaySubject<string> = new ReplaySubject(1);
private ftypeSubject: ReplaySubject<string> = new ReplaySubject<string>(1); private ftypeSubject: ReplaySubject<string> = new ReplaySubject(1);
constructor() {}
public setData(data: ServiceMessage): void { public setData(data: ServiceMessage): void {

View File

@@ -1,9 +1,15 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ReplaySubject, Observable } from 'rxjs'; 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 { AceLanguageClient, LanguageClientConfig } from 'ace-linters/build/ace-language-client';
import { LanguageProvider } from "ace-linters"; import { LanguageProvider } from "ace-linters";
import { ServiceMessage } from '../../../types/service-message.type'; import { ServiceMessage } from '../../../types/service-message.type';
@@ -12,17 +18,13 @@ import { ServiceMessage } from '../../../types/service-message.type';
providedIn: 'root' providedIn: 'root'
}) })
export class LspManagerService { export class LspManagerService {
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1); private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
workspaceFolder: string = ""; workspaceFolder: string = "";
lspConfigDataStr: string = ""; lspConfigDataStr: string = "";
languageProviders: {} = {}; languageProviders: {} = {};
constructor() {
}
public loadLspConfigData(): Promise<string | void> { public loadLspConfigData(): Promise<string | void> {
return this.getLspConfigData().then((lspConfigData: string) => { return this.getLspConfigData().then((lspConfigData: string) => {
this.lspConfigDataStr = lspConfigData; this.lspConfigDataStr = lspConfigData;
@@ -33,16 +35,14 @@ export class LspManagerService {
public registerEditorToLSPClient(editor: any) { public registerEditorToLSPClient(editor: any) {
let mode = this.getMode(editor.session); let mode = this.getMode(editor.session);
if ( this.languageProviders[mode] ) { this.languageProviders[mode]?.registerEditor(
this.languageProviders[mode].registerEditor(editor); editor,
return; editor.session.lspConfig
} );
this.languageProviders[mode]?.registerEditor(editor);
} }
private getLspConfigData(): Promise<string> { private getLspConfigData(): Promise<string> {
return window.fs.getLspConfigData(); return window?.fs.getLspConfigData();
} }
private parseAndReturnLSPConfigData(): {} { private parseAndReturnLSPConfigData(): {} {
@@ -63,13 +63,13 @@ export class LspManagerService {
} }
private getInitializationOptions(mode: string, configData: {}): {} { private getInitializationOptions(mode: string, configData: {}): {} {
let _initializationOptions = {}; let initializationOptions = {};
if ( Object.keys(configData).length !== 0 && configData[mode] ) { if ( Object.keys(configData).length !== 0 && configData[mode] ) {
_initializationOptions = configData[mode]["initialization-options"]; initializationOptions = configData[mode]["initialization-options"];
} }
return _initializationOptions; return initializationOptions;
} }
public createLanguageProviderWithClientServer(mode: string): LanguageProvider { public createLanguageProviderWithClientServer(mode: string): LanguageProvider {
@@ -77,15 +77,15 @@ export class LspManagerService {
let servers: LanguageClientConfig[] = []; let servers: LanguageClientConfig[] = [];
try { try {
let configData = this.parseAndReturnLSPConfigData(); let lspConfigData = this.parseAndReturnLSPConfigData();
let _initializationOptions = this.getInitializationOptions(mode, configData); let initializationOptions = this.getInitializationOptions(mode, lspConfigData);
servers = [ servers = [
{ {
module: () => import("ace-linters/build/language-client"), module: () => import("ace-linters/build/language-client"),
modes: mode, modes: mode,
type: "socket", type: "socket",
socket: new WebSocket( configData[mode]["socket"] ), socket: new WebSocket( lspConfigData[mode]["socket"] ),
initializationOptions: _initializationOptions initializationOptions: initializationOptions
} }
]; ];
} catch(error) { } catch(error) {
@@ -96,22 +96,68 @@ export class LspManagerService {
return; return;
} }
this.languageProviders[mode] = AceLanguageClient.for(servers); this.languageProviders[mode] = AceLanguageClient.for(
// this.languageProviders[mode].requireFilePath = true; servers,
this.languageProviders[mode].changeWorkspaceFolder(this.workspaceFolder); {
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]; 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 { private getLanguageProviderWithWebWorker(): LanguageProvider {
let worker = new Worker(new URL('./webworker.js', import.meta.url)); let worker = new Worker(new URL('./webworker.js', import.meta.url));
return LanguageProvider.create(worker); return LanguageProvider.create(worker);
} }
public setSessionFilePath(session: any, filePath: string = "") { public registerSession(editor: any) {
if ( !session || !filePath ) return; let mode = this.getMode(editor.session);
let mode = this.getMode(session);
if ( !this.languageProviders[mode] ) return; if ( !this.languageProviders[mode] ) return;
this.languageProviders[mode].setSessionFilePath(session, filePath);
this.languageProviders[mode].registerSession(
editor.session,
editor,
editor.session.lspConfig
);
} }
public getMode(session: any): string { public getMode(session: any): string {

View File

@@ -9,11 +9,7 @@ import { ServiceMessage } from '../../../types/service-message.type';
providedIn: 'root' providedIn: 'root'
}) })
export class MarkdownPreviewService { export class MarkdownPreviewService {
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1); private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
constructor() {
}
public sendMessage(data: ServiceMessage): void { public sendMessage(data: ServiceMessage): void {

View File

@@ -8,11 +8,7 @@ import { ReplaySubject, Observable } from 'rxjs';
}) })
export class FilesModalService { export class FilesModalService {
private showFilesModalSubject: ReplaySubject<null> = new ReplaySubject<null>(1); 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() {
}
public showFilesModal(): void { public showFilesModal(): void {

View File

@@ -12,10 +12,6 @@ export class SearchReplaceService {
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1); private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
constructor() {
}
public sendMessage(data: ServiceMessage): void { public sendMessage(data: ServiceMessage): void {
this.messageSubject.next(data); this.messageSubject.next(data);
} }

View File

@@ -11,15 +11,13 @@ import { ServiceMessage } from '../../../types/service-message.type';
providedIn: 'root' providedIn: 'root'
}) })
export class TabsService { export class TabsService {
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1); private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
private editorsService: EditorsService = inject(EditorsService); private editorsService: EditorsService = inject(EditorsService);
tabs: any[] = []; tabs: any[] = [];
newIndex: number = -1; newIndex: number = -1;
constructor() {}
public push(tabData: {}): void { public push(tabData: {}): void {
this.tabs.push(tabData); this.tabs.push(tabData);
@@ -45,45 +43,21 @@ export class TabsService {
} }
public getLeftSiblingTab(fpath: string): string { public getLeftSiblingTab(fpath: string): string {
let size = this.tabs.length; if (this.tabs.length === 0 ) return;
let i = 0;
for (; i < size; i++) { let i = this.tabs.indexOf(fpath);
if (this.tabs[i].path == fpath) {
break;
}
}
if ( !(size > 1) ) { (i === 0) ? i = this.tabs.length - 1 : i -= 1;
return ""; return this.tabs[i].path;
}
if ( i === 0 ) {
return this.tabs[i + 1].path;
}
return this.tabs[i - 1].path;
} }
public getRightSiblingTab(fpath: string): string { public getRightSiblingTab(fpath: string): string {
let size = this.tabs.length; if (this.tabs.length === 0 ) return;
let i = 0;
for (; i < size; i++) { let i = this.tabs.indexOf(fpath);
if (this.tabs[i].path == fpath) {
break;
}
}
if ( !(size > 1) ) { (i === (this.tabs.length - 1)) ? i = 0 : i += 1;
return ""; return this.tabs[i].path;
}
if ( i === (size - 1) ) {
return this.tabs[i - 1].path;
}
return this.tabs[i + 1].path;
} }
public setNewTargetIndex(fpath: string): void { public setNewTargetIndex(fpath: string): void {

View File

@@ -5,6 +5,7 @@ import { EditSession, UndoManager } from 'ace-builds';
import { getModeForPath } from 'ace-builds/src-noconflict/ext-modelist'; import { getModeForPath } from 'ace-builds/src-noconflict/ext-modelist';
import { TabsService } from './editor/tabs/tabs.service'; import { TabsService } from './editor/tabs/tabs.service';
import { ColorTokenizerService } from './color-tokenizer.service';
import { NewtonFile } from '../types/file.type'; import { NewtonFile } from '../types/file.type';
import { ServiceMessage } from '../types/service-message.type'; import { ServiceMessage } from '../types/service-message.type';
@@ -15,16 +16,11 @@ import { ServiceMessage } from '../types/service-message.type';
providedIn: 'root' providedIn: 'root'
}) })
export class FilesService { export class FilesService {
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1); private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
private tabsService: TabsService = inject(TabsService); private tabsService: TabsService = inject(TabsService);
files: Map<string, NewtonFile>; files: Map<string, NewtonFile> = new Map();
constructor() {
this.files = new Map<string, NewtonFile>();
}
public get(path: string): NewtonFile { public get(path: string): NewtonFile {
@@ -39,9 +35,27 @@ export class FilesService {
return [...this.files.values()]; return [...this.files.values()];
} }
public delete(file: NewtonFile) { 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(); file.session.destroy();
window.fs.closeFile(file.path); window?.fs.closeFile(file.path);
this.files.delete(file.path); this.files.delete(file.path);
} }
@@ -62,7 +76,7 @@ export class FilesService {
): Promise<NewtonFile | undefined | null> { ): Promise<NewtonFile | undefined | null> {
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files[i]; const file = files[i];
const path = window.fs.getPathForFile(file); const path = window?.fs.getPathForFile(file);
if (!file || !path) continue; if (!file || !path) continue;
if ( this.files.get(path) ) continue; if ( this.files.get(path) ) continue;
@@ -87,12 +101,18 @@ export class FilesService {
file.hash = btoa(file.path); file.hash = btoa(file.path);
if (loadFileContents) if (loadFileContents)
data = await window.fs.getFileContents(file.path); data = await window?.fs.getFileContents(file.path);
file.session = new EditSession(data); file.session = new EditSession(data);
file.session["id"] = path; file.session["id"] = path;
file.session.setUndoManager( new UndoManager() ); file.session.setUndoManager( new UndoManager() );
file.session.setMode( getModeForPath( file.path ).mode ); 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); this.files.set(file.path, file);
} catch (error) { } catch (error) {

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class WebsocketService {
private socket$!: WebSocketSubject<any>;
connect(url: string): Observable<any> {
if (!this.socket$ || this.socket$.closed) {
this.socket$ = webSocket(
{
url,
deserializer: msg => msg.data
}
);
}
return this.socket$.asObservable();
}
send(message: any) {
if (this.socket$) {
this.socket$.next(message);
}
}
close() {
if (this.socket$) {
this.socket$.complete();
}
}
}

View File

@@ -1,4 +1,10 @@
import { Directive, ElementRef, Input, ViewChild, inject } from '@angular/core'; import {
Directive,
ElementRef,
Input,
ViewChild,
inject
} from '@angular/core';
import * as uuid from 'uuid'; import * as uuid from 'uuid';
import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.service'; import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.service';
@@ -46,9 +52,8 @@ export class CodeViewBase {
public debounceId: number = -1; public debounceId: number = -1;
public debounceWait: number = 800; public debounceWait: number = 800;
@ViewChild('contextMenu') contextMenu!: ElementRef;
constructor() { public showContextMenu: boolean = false;
}
public selectLeftEditor() { public selectLeftEditor() {
@@ -182,7 +187,7 @@ export class CodeViewBase {
} }
public toggleFullScreen() { public toggleFullScreen() {
window.main.toggleFullScreen(); window?.main.toggleFullScreen();
} }
public setAsReadOnly() { public setAsReadOnly() {
@@ -200,8 +205,8 @@ export class CodeViewBase {
this.editor.setHighlightGutterLine(false); this.editor.setHighlightGutterLine(false);
this.editor.setShowFoldWidgets(false); this.editor.setShowFoldWidgets(false);
this.editor.setShowPrintMargin(false); this.editor.setShowPrintMargin(false);
this.editor.session.setUseWrapMode(true);
this.editorElm.nativeElement.parentElement.classList.remove("scroller");
this.editorElm.nativeElement.parentElement.classList.add("col-1"); this.editorElm.nativeElement.parentElement.classList.add("col-1");
this.editorElm.nativeElement.parentElement.classList.add("zero-margin-padding"); this.editorElm.nativeElement.parentElement.classList.add("zero-margin-padding");
@@ -295,27 +300,30 @@ export class CodeViewBase {
startDir = pathParts.join( '/' ); startDir = pathParts.join( '/' );
} }
window.fs.openFiles(startDir); window?.fs.openFiles(startDir);
} }
protected saveFile() { protected saveFile() {
if (!this.activeFile) { if (!this.activeFile) {
this.saveFileAs(); this.saveFileAs();
this.activeFile.session.getUndoManager().markClean();
return; return;
} }
const text = this.activeFile.session.getValue(); const text = this.activeFile.session.getValue();
window.fs.saveFile(this.activeFile.path, text); window?.fs.saveFile(this.activeFile.path, text);
this.activeFile.session.getUndoManager().markClean();
} }
protected saveFileAs() { protected saveFileAs() {
window.fs.saveFileAs().then((path: string) => { window?.fs.saveFileAs().then((path: string) => {
if (!path) return; if (!path) return;
let file: NewtonFile = new File([""], path, {}); let file: NewtonFile = new File([""], path, {});
const text = this.editor.session.getValue(); const text = this.editor.session.getValue();
window.fs.saveFile(path, text); window?.fs.saveFile(path, text);
this.filesService.addFile( this.filesService.addFile(
path, path,
file, file,
@@ -356,6 +364,6 @@ export class CodeViewBase {
} }
private quit() { private quit() {
window.main.quit(); window?.main.quit();
} }
} }

View File

@@ -1,19 +1,3 @@
/*
.editor {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.editor {
height: 100vh;
width: auto;
}
*/
.editor { .editor {
position: relative; position: relative;
height: 100%; height: 100%;
@@ -25,4 +9,4 @@
border-style: solid; border-style: solid;
border-width: thin; border-width: thin;
border-color: rgba(124, 124, 124, 1); border-color: rgba(124, 124, 124, 1);
} }

View File

@@ -1,2 +1,15 @@
<div class="editor" #editor > <div class="editor" #editor >
</div> </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>

View File

@@ -25,6 +25,7 @@ import { CodeViewBase } from './view.base';
import { NewtonFile } from '../../common/types/file.type'; import { NewtonFile } from '../../common/types/file.type';
import { EditorType } from '../../common/types/editor.type'; import { EditorType } from '../../common/types/editor.type';
import { ServiceMessage } from '../../common/types/service-message.type'; import { ServiceMessage } from '../../common/types/service-message.type';
import { ButtonMap } from '../../common/constants/button.map';
@@ -141,7 +142,7 @@ export class CodeViewComponent extends CodeViewBase {
private loadNewtonEventBindings(): void { private loadNewtonEventBindings(): void {
// Note: https://ajaxorg.github.io/ace-api-docs/interfaces/ace.Ace.EditorEvents.html // Note: https://ajaxorg.github.io/ace-api-docs/interfaces/ace.Ace.EditorEvents.html
this.editor.on("focus", (e) => { this.editor.on("focus", (event) => {
let message = new ServiceMessage(); let message = new ServiceMessage();
message.action = "set-active-editor"; message.action = "set-active-editor";
message.editorUUID = this.uuid; message.editorUUID = this.uuid;
@@ -156,13 +157,38 @@ export class CodeViewComponent extends CodeViewBase {
this.lspManagerService.sendMessage(message); this.lspManagerService.sendMessage(message);
this.markdownPreviewService.sendMessage(message); this.markdownPreviewService.sendMessage(message);
message = new ServiceMessage();
message.action = "highlight-active-tab";
message.filePath = this.activeFile?.path;
this.tabsService.sendMessage(message);
this.updateInfoBar(); this.updateInfoBar();
}); });
this.editor.on("click", () => { this.editor.on("click", (event) => {
this.updateInfoBar(); 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.editor.on("input", () => {
this.updateInfoBar(); this.updateInfoBar();
}); });
@@ -182,17 +208,30 @@ export class CodeViewComponent extends CodeViewBase {
} }
}); });
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", () => { this.editor.on("change", () => {
if (this.debounceId) { clearTimeout(this.debounceId); } if (this.debounceId) { clearTimeout(this.debounceId); }
this.setDebounceTimeout(); this.setDebounceTimeout();
if (!this.activeFile) return; if (!this.activeFile) return;
const isClean = this.activeFile.session.getUndoManager().isClean();
const hasUndo = this.activeFile.session.getUndoManager().hasUndo();
let message = new ServiceMessage(); let message = new ServiceMessage();
message.action = "file-changed"; message.action = (!isClean && hasUndo) ? "file-changed" : "file-unmodified";
message.filePath = this.activeFile.path; message.filePath = this.activeFile.path;
this.tabsService.sendMessage(message); this.tabsService.sendMessage(message);
}); });
this.editor.on("changeSession", (session) => { this.editor.on("changeSession", (session) => {
@@ -202,11 +241,38 @@ export class CodeViewComponent extends CodeViewBase {
this.lspManagerService.sendMessage(message); this.lspManagerService.sendMessage(message);
message = new ServiceMessage();
message.action = "highlight-active-tab";
message.filePath = this.activeFile?.path;
this.tabsService.sendMessage(message);
this.updateInfoBar(); 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) { public assignSession(file: NewtonFile) {
if (!file) return; if (!file) return;
@@ -217,10 +283,17 @@ export class CodeViewComponent extends CodeViewBase {
public cloneSession(file: NewtonFile) { public cloneSession(file: NewtonFile) {
if (!file) return; if (!file) return;
this.activeFile = file; this.activeFile = file;
let session = this.aceApi.createEditSession(file.session.getValue()); let session = this.aceApi.createEditSession(file.session.getValue());
session["$config"] = {
"lsp": {
"filePath": file.path
}
}
session.setMode( file.session.getMode()["$id"] ); session.setMode( file.session.getMode()["$id"] );
session.setUseWrapMode(true);
this.editor.setSession(session); this.editor.setSession(session);
} }
@@ -234,4 +307,4 @@ export class CodeViewComponent extends CodeViewBase {
}, (timeout) ? timeout : this.debounceWait); }, (timeout) ? timeout : this.debounceWait);
} }
} }

View File

@@ -7,4 +7,4 @@
<code-view [mode]="'mini-map'"></code-view> <code-view [mode]="'mini-map'"></code-view>
</div> </div>
<div> </div>

View File

@@ -1,5 +1,5 @@
import { Component, inject } from '@angular/core'; import { Component, DestroyRef, inject } from '@angular/core';
import { Subject, takeUntil } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { EditorsService } from '../common/services/editor/editors.service'; import { EditorsService } from '../common/services/editor/editors.service';
import { TabsService } from '../common/services/editor/tabs/tabs.service'; import { TabsService } from '../common/services/editor/tabs/tabs.service';
@@ -29,7 +29,7 @@ import { ServiceMessage } from '../common/types/service-message.type';
} }
}) })
export class EditorsComponent { export class EditorsComponent {
private unsubscribe: Subject<void> = new Subject(); readonly #destroyRef: DestroyRef = inject(DestroyRef);
private editorsService: EditorsService = inject(EditorsService); private editorsService: EditorsService = inject(EditorsService);
private tabsService: TabsService = inject(TabsService); private tabsService: TabsService = inject(TabsService);
@@ -37,168 +37,46 @@ export class EditorsComponent {
constructor() { constructor() {
}
private ngAfterViewInit(): void {
this.loadSubscribers(); this.loadSubscribers();
this.loadMainSubscribers(); this.loadMainSubscribers();
} }
private ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
private loadSubscribers() { private loadSubscribers() {
this.editorsService.getMessage$().pipe( this.editorsService.getMessage$().pipe(
takeUntil(this.unsubscribe) takeUntilDestroyed(this.#destroyRef)
).subscribe((message: ServiceMessage) => { ).subscribe((message: ServiceMessage) => {
if (message.action === "select-left-editor") { switch ( message.action ) {
let editorComponent = this.editorsService.get(message.editorUUID); case "select-left-editor":
if (!editorComponent.leftSiblingUUID) return; this.selectLeftEditor(message);
let siblingComponent = this.editorsService.get(editorComponent.leftSiblingUUID); break;
siblingComponent.editor.focus() case "select-right-editor":
} else if (message.action === "select-right-editor") { this.selectRightEditor(message);
let editorComponent = this.editorsService.get(message.editorUUID); break;
if (!editorComponent.rightSiblingUUID) return; case "move-session-left":
let siblingComponent = this.editorsService.get(editorComponent.rightSiblingUUID); this.moveSessionLeft(message);
siblingComponent.editor.focus() break;
} else if (message.action === "move-session-left") { case "move-session-right":
let editorComponent = this.editorsService.get(message.editorUUID); this.moveSessionRight(message);
if (!editorComponent.leftSiblingUUID) return; break;
case "set-active-editor":
let siblingComponent = this.editorsService.get(editorComponent.leftSiblingUUID); this.setActiveEditor(message);
let session = editorComponent.editor.getSession(); break;
let siblingSession = siblingComponent.editor.getSession(); case "set-tab-to-editor":
this.setTabToEditor(message);
if (session == siblingSession) return; break;
case "close-tab":
siblingComponent.assignSession(editorComponent.activeFile); this.closeTab(message);
break;
let targetPath = this.tabsService.getRightSiblingTab( default:
editorComponent.activeFile.path break;
)
if (targetPath) {
editorComponent.assignSession(
this.filesService.get(targetPath)
);
} else {
editorComponent.newFile();
}
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.assignSession(editorComponent.activeFile);
let targetPath = this.tabsService.getRightSiblingTab(
editorComponent.activeFile.path
)
if (targetPath) {
editorComponent.assignSession(
this.filesService.get(targetPath)
);
} else {
editorComponent.newFile();
}
siblingComponent.editor.focus()
} else if (message.action === "set-active-editor") {
this.editorsService.getActiveEditorComponent().removeActiveStyling();
this.editorsService.setActiveEditor(message.editorUUID);
this.editorsService.getActiveEditorComponent().addActiveStyling();
} else if (message.action === "set-tab-to-editor") {
let file = this.filesService.get(message.filePath);
let editorComponent = this.editorsService.getActiveEditorComponent();
let editor = editorComponent.editor;
editorComponent.assignSession(file);
this.editorsService.miniMapView.cloneSession(file);
} else if (message.action === "close-tab") {
let activeComponent = this.editorsService.getActiveEditorComponent();
let editors = this.editorsService.getEditorsAsArray();
let file = this.filesService.get(message.filePath);
for (let i = 0; i < editors.length; i++) {
let editorComponent = editors[i];
if (editorComponent.editor.session == file.session) {
if (activeComponent == editorComponent) {
this.editorsService.miniMapView.newFile();
}
editorComponent.newFile();
}
}
activeComponent.lspManagerService.closeDocument(file.session);
this.filesService.delete(file);
} }
}); });
} }
private loadMainSubscribers() { private loadMainSubscribers() {
window.fs.onLoadFiles(async (paths: []) => { window?.main.onMenuActions(async (action: string) => {
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.editorsService.setSession(file);
});
window.fs.onChangedFile(async (path: string, data: string) => {
let file = this.filesService.get(path);
file.session.setValue(data);
// Note: fake 'save' event to not show as changed iven external save happened...
let message = new ServiceMessage();
message.action = "file-saved";
message.filePath = path;
this.tabsService.sendMessage(message);
});
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);
});
window.main.onMenuActions(async (action: string) => {
let editorComponent = this.editorsService.getActiveEditorComponent(); let editorComponent = this.editorsService.getActiveEditorComponent();
let editor = editorComponent.editor; let editor = editorComponent.editor;
@@ -233,10 +111,65 @@ export class EditorsComponent {
editor.showSettingsMenu(); editor.showSettingsMenu();
case "show-about": case "show-about":
break; break;
case "quit":
window?.main.quit();
break;
default: default:
editor.execCommand(action); editor.execCommand(action);
break;
} }
}); });
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.editorsService.setSession(file);
});
window?.fs.onChangedFile(async (path: string, data: string) => {
let file = this.filesService.get(path);
file.session.setValue(data);
// Note: fake 'save' event to not show as changed iven external save happened...
let message = new ServiceMessage();
message.action = "file-saved";
message.filePath = path;
this.tabsService.sendMessage(message);
});
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);
});
} }
protected onFileDropped(files: any) { protected onFileDropped(files: any) {
@@ -249,4 +182,104 @@ export class EditorsComponent {
}); });
} }
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 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;
editorComponent.assignSession(file);
this.editorsService.miniMapView.cloneSession(file);
}
private closeTab(message: ServiceMessage) {
let activeComponent = this.editorsService.getActiveEditorComponent();
let editors = this.editorsService.getEditorsAsArray();
let file = this.filesService.get(message.filePath);
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);
}
} }

View File

@@ -1,5 +1,5 @@
import { Component, inject } from '@angular/core'; import { Component, DestroyRef, inject } from '@angular/core';
import { Subject, takeUntil } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.service'; import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.service';
@@ -17,7 +17,7 @@ import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.s
} }
}) })
export class InfoBarComponent { export class InfoBarComponent {
private unsubscribe: Subject<void> = new Subject(); readonly #destroyRef: DestroyRef = inject(DestroyRef);
private infoBarService: InfoBarService = inject(InfoBarService); private infoBarService: InfoBarService = inject(InfoBarService);
@@ -28,18 +28,14 @@ export class InfoBarComponent {
ftype: string = ""; ftype: string = "";
constructor() {} constructor() {
private ngAfterViewInit(): void {
this.loadSubscribers(); this.loadSubscribers();
} }
private loadSubscribers() { private loadSubscribers() {
this.infoBarService.updateInfoBarFPath$().pipe( this.infoBarService.updateInfoBarFPath$().pipe(
takeUntil(this.unsubscribe) takeUntilDestroyed(this.#destroyRef)
).subscribe((fpath: string) => { ).subscribe((fpath: string) => {
this.fpath = fpath; this.fpath = fpath;
let _path = fpath; let _path = fpath;
@@ -52,19 +48,19 @@ export class InfoBarComponent {
}); });
this.infoBarService.updateInfoBarCursorPos$().pipe( this.infoBarService.updateInfoBarCursorPos$().pipe(
takeUntil(this.unsubscribe) takeUntilDestroyed(this.#destroyRef)
).subscribe((cursorPos: any) => { ).subscribe((cursorPos: any) => {
this.cursorPos = `${cursorPos.row + 1}:${cursorPos.column}`; this.cursorPos = `${cursorPos.row + 1}:${cursorPos.column}`;
}); });
this.infoBarService.updateInfoBarEncodeing$().pipe( this.infoBarService.updateInfoBarEncodeing$().pipe(
takeUntil(this.unsubscribe) takeUntilDestroyed(this.#destroyRef)
).subscribe((encodeing: string) => { ).subscribe((encodeing: string) => {
this.encodeing = encodeing; this.encodeing = encodeing;
}); });
this.infoBarService.updateInfoBarFType$().pipe( this.infoBarService.updateInfoBarFType$().pipe(
takeUntil(this.unsubscribe) takeUntilDestroyed(this.#destroyRef)
).subscribe((ftype: string) => { ).subscribe((ftype: string) => {
let mode = ftype.split("/"); let mode = ftype.split("/");
this.ftype = mode[ mode.length - 1 ]; this.ftype = mode[ mode.length - 1 ];

View File

@@ -3,7 +3,9 @@
<div class="row mt-2 mb-3"> <div class="row mt-2 mb-3">
<div class="col clear-right-padding"> <div class="col clear-right-padding">
<div class="input-group-sm"> <div class="input-group-sm"
(mouseup)="handleActionMouseUp($event)"
>
<label class="form-control" [innerText]="lspManagerService.workspaceFolder || 'Project Path...'"> <label class="form-control" [innerText]="lspManagerService.workspaceFolder || 'Project Path...'">
</label> </label>
</div> </div>
@@ -37,12 +39,12 @@
<div class="row mt-2 md-2"> <div class="row mt-2 md-2">
<div class="col col-sm" [hidden]="!lspManagerService.workspaceFolder"> <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)="createLanguageClient()">Create Language Client</button>
<button class="btn btn-sm btn-dark" (click)="closeLanguageClient()">Close Language Client</button>
</div> </div>
<div class="col"> <div class="col">
Target Editor: <label [innerText]="editor?.id || '...'"></label> Target Editor: <label [innerText]="editor?.id || '...'"></label>
</div> </div>
<div class="col-sm" [hidden]="!lspManagerService.workspaceFolder"> <div class="col-sm" [hidden]="!lspManagerService.workspaceFolder">
<button class="btn btn-sm btn-dark" <button class="btn btn-sm btn-dark"
(click)="registerEditorToLanguageClient()"> (click)="registerEditorToLanguageClient()">
@@ -63,4 +65,13 @@
</div> </div>
</div> </div>
<ul #contextMenu
class="contextMenu"
[hidden]="!showContextMenu"
(blur)="hideContextMenu"
(click)="contextMenuClicked($event)"
>
<li command="pasteText">Paste</li>
</ul>

View File

@@ -1,5 +1,12 @@
import { Component, ChangeDetectorRef, ElementRef, HostBinding, ViewChild, inject } from '@angular/core'; import {
import { Subject, takeUntil } from 'rxjs'; 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 { LspManagerService } from '../../common/services/editor/lsp-manager/lsp-manager.service';
@@ -7,6 +14,8 @@ import { CodeViewComponent } from '../code-view/view.component';
import { ServiceMessage } from '../../common/types/service-message.type'; import { ServiceMessage } from '../../common/types/service-message.type';
import { ButtonMap } from '../../common/constants/button.map';
@Component({ @Component({
@@ -23,8 +32,8 @@ import { ServiceMessage } from '../../common/types/service-message.type';
} }
}) })
export class LspManagerComponent { export class LspManagerComponent {
private unsubscribe: Subject<void> = new Subject(); readonly #destroyRef = inject(DestroyRef);
private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef); private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef);
lspManagerService: LspManagerService = inject(LspManagerService); lspManagerService: LspManagerService = inject(LspManagerService);
@@ -36,27 +45,27 @@ export class LspManagerComponent {
editor: any; editor: any;
activeFile: any; activeFile: any;
@ViewChild('contextMenu') contextMenu!: ElementRef;
public showContextMenu: boolean = false;
constructor() { constructor() {
}
private ngAfterViewInit(): void {
this.mapEditorsAndLoadConfig();
this.loadSubscribers(); this.loadSubscribers();
} }
private ngOnDestroy() { private ngAfterViewInit(): void {
this.unsubscribe.next(); this.mapEditorsAndLoadConfig();
this.unsubscribe.complete();
} }
private mapEditorsAndLoadConfig() { private mapEditorsAndLoadConfig() {
this.lspTextEditor = this.lspEditorComponent.editor; this.lspTextEditor = this.lspEditorComponent.editor;
this.innerEditor = this.sessionEditorComponent.editor; this.innerEditor = this.sessionEditorComponent.editor;
this.lspTextEditor.on("input", () => {
this.lspManagerService.lspConfigDataStr =
this.lspTextEditor.session.getValue();
});
this.lspManagerService.loadLspConfigData().then((lspConfigData) => { this.lspManagerService.loadLspConfigData().then((lspConfigData) => {
this.lspTextEditor.session.setMode("ace/mode/json"); this.lspTextEditor.session.setMode("ace/mode/json");
this.lspTextEditor.session.setValue(lspConfigData); this.lspTextEditor.session.setValue(lspConfigData);
@@ -65,7 +74,7 @@ export class LspManagerComponent {
private loadSubscribers() { private loadSubscribers() {
this.lspManagerService.getMessage$().pipe( this.lspManagerService.getMessage$().pipe(
takeUntil(this.unsubscribe) takeUntilDestroyed(this.#destroyRef)
).subscribe((message: ServiceMessage) => { ).subscribe((message: ServiceMessage) => {
if (message.action === "toggle-lsp-manager") { if (message.action === "toggle-lsp-manager") {
this.toggleLspManager(message); this.toggleLspManager(message);
@@ -79,12 +88,53 @@ export class LspManagerComponent {
}); });
} }
protected handleActionMouseUp(event: any): void {
if (ButtonMap.LEFT === event.button) return;
let target = event.target;
let menuElm = this.contextMenu.nativeElement;
let pageX = event.clientX;
let pageY = event.clientY;
const origin = {
left: pageX + 5,
top: pageY - 5
};
menuElm.style.left = `${origin.left}px`;
menuElm.style.top = `${origin.top}px`;
this.showContextMenu = true;
}
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 pasteText() {
navigator.clipboard.readText().then((pasteText) => {
if (pasteText.includes("\n") || !pasteText.startsWith("/")) return;
this.lspManagerService.workspaceFolder = pasteText;
});
}
public clearWorkspaceFolder() { public clearWorkspaceFolder() {
this.lspManagerService.workspaceFolder = ""; this.lspManagerService.workspaceFolder = "";
} }
public setWorkspaceFolder() { public setWorkspaceFolder() {
window.fs.chooseFolder().then((folder: string) => { window?.fs.chooseFolder().then((folder: string) => {
if (!folder) return; if (!folder) return;
this.lspManagerService.workspaceFolder = folder; this.lspManagerService.workspaceFolder = folder;
@@ -96,14 +146,13 @@ export class LspManagerComponent {
this.lspManagerService.createLanguageProviderWithClientServer(mode); this.lspManagerService.createLanguageProviderWithClientServer(mode);
} }
public closeLanguageClient() {
let mode = this.lspManagerService.getMode(this.editor.session);
this.lspManagerService.closeLanguageProviderWithClientServer(mode);
}
public registerEditorToLanguageClient() { public registerEditorToLanguageClient() {
this.lspManagerService.registerEditorToLSPClient(this.editor); this.lspManagerService.registerEditorToLSPClient(this.editor);
/*
this.lspManagerService.setSessionFilePath(
this.editor.session,
this.activeFile.path
);
*/
} }
@@ -130,8 +179,8 @@ export class LspManagerComponent {
} }
private setActiveEditor(message: ServiceMessage) { private setActiveEditor(message: ServiceMessage) {
this.editor = message.rawData.editor; this.editor = message.rawData.editor;
this.activeFile = message.rawData.activeFile; this.activeFile = message.rawData.activeFile;
// TODO: figure out why this doesn't update the session consistently... // 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. // It seems maybe bound to visible state as change detector ref didn't help either.
@@ -139,18 +188,15 @@ export class LspManagerComponent {
} }
private editorUpdate(message: ServiceMessage) { private editorUpdate(message: ServiceMessage) {
if (!message.rawData.activeFile) return; if (
!this.editor ||
!message.rawData.activeFile
) return;
this.editor.setSession(message.rawData.editor.getSession()) this.editor.setSession(message.rawData.editor.getSession())
this.activeFile = message.rawData.activeFile; this.activeFile = message.rawData.activeFile;
/*
this.lspManagerService.setSessionFilePath(
this.editor.session,
this.activeFile.path
);
*/
this.lspManagerService.registerSession(this.editor);
} }
private closeFile(message: ServiceMessage) { private closeFile(message: ServiceMessage) {

View File

@@ -1,5 +1,12 @@
import { Component, HostBinding, inject } from '@angular/core'; import {
import { Subject, takeUntil } from 'rxjs'; Component,
DestroyRef,
HostBinding,
inject
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { marked } from 'marked';
import { MarkdownPreviewService } from '../../common/services/editor/markdown-preview/markdown-preview.service'; import { MarkdownPreviewService } from '../../common/services/editor/markdown-preview/markdown-preview.service';
@@ -19,12 +26,12 @@ import { ServiceMessage } from '../../common/types/service-message.type';
} }
}) })
export class MarkdownPreviewComponent { export class MarkdownPreviewComponent {
private unsubscribe: Subject<void> = new Subject(); readonly #destroyRef: DestroyRef = inject(DestroyRef);
private markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService); private markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService);
@HostBinding("class.hidden") isHidden: boolean = true; @HostBinding("class.hidden") isHidden: boolean = true;
converter: any = new showdown.Converter(); converter: any = marked;
defaultHtml: string = "<h1>NOT a Markdown file...</h1>" defaultHtml: string = "<h1>NOT a Markdown file...</h1>"
bodyHtml: string = ""; bodyHtml: string = "";
@@ -32,26 +39,22 @@ export class MarkdownPreviewComponent {
constructor() { constructor() {
}
private ngAfterViewInit(): void {
this.loadSubscribers(); this.loadSubscribers();
} }
private ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
private loadSubscribers() { private loadSubscribers() {
this.markdownPreviewService.getMessage$().pipe( this.markdownPreviewService.getMessage$().pipe(
takeUntil(this.unsubscribe) takeUntilDestroyed(this.#destroyRef)
).subscribe((message: ServiceMessage) => { ).subscribe((message: ServiceMessage) => {
if (message.action === "toggle-markdown-preview") { switch ( message.action ) {
this.toggleMarkdownPreview(message); case "toggle-markdown-preview":
} else if (message.action === "set-active-editor") { this.toggleMarkdownPreview(message);
this.setActiveEditor(message); break;
case "set-active-editor":
this.setActiveEditor(message);
break;
default:
break;
} }
}); });
} }
@@ -84,7 +87,7 @@ export class MarkdownPreviewComponent {
let mdStr = this.editorComponent.editor.session.getValue(); let mdStr = this.editorComponent.editor.session.getValue();
let pathParts = this.editorComponent.activeFile.path.split("/"); let pathParts = this.editorComponent.activeFile.path.split("/");
let basePath = "file://" + pathParts.slice(0, -1).join("/"); let basePath = "file://" + pathParts.slice(0, -1).join("/");
this.bodyHtml = this.converter.makeHtml( this.bodyHtml = this.converter.parse(
mdStr.replaceAll("](images", `](${basePath}/images`) mdStr.replaceAll("](images", `](${basePath}/images`)
.replaceAll("](imgs", `](${basePath}/imgs`) .replaceAll("](imgs", `](${basePath}/imgs`)
.replaceAll("](pictures", `](${basePath}/pictures`) .replaceAll("](pictures", `](${basePath}/pictures`)

View File

@@ -1,7 +1,21 @@
<div class="col"> <div class="col">
<div class="row"> <div class="row">
<div class="col col-3"> <div class="col col-3">
<label id="find-status-lbl">Find in Current File</label> @if (isQueryLong) {
<label id="find-status-lbl">
<b class="error">Query exceeds 80 characters...</b>
</label>
} @else if (isQueryNotFound) {
<label id="find-status-lbl">
<b class="warning">Query not found...</b>
</label>
} @else if (query && !isQueryLong && !isQueryNotFound) {
<label id="find-status-lbl">Found in current file:
<b class="success">{{totalCount}}</b>
</label>
} @else {
<label id="find-status-lbl">Find in Current File:</label>
}
</div> </div>
<div class="col col-4"> <div class="col col-4">
@@ -45,15 +59,27 @@
id="find-entry" id="find-entry"
class="form-control" class="form-control"
type="search" type="search"
(keyup)="searchForString()" (focus)="searchForString()"
(keyup)="findEntryKeyUpHandler($event)"
(input)="searchForString()"
placeholder="Find in current file..." placeholder="Find in current file..."
aria-label="Find in current file..." aria-label="Find in current file..."
/> />
</div> </div>
</div> </div>
<div class="col col-auto"> <div class="col col-auto">
<button id="find-btn" class="width-8em btn btn-sm btn-dark" (click)="findNextEntry()">Find</button> <button
<button id="find-all-btn" class="width-8em btn btn-sm btn-dark" (click)="findAllEntries()">Find All</button> [disabled]="!query || isQueryLong || isQueryNotFound"
id="find-btn"
class="width-8em btn btn-sm btn-dark"
(click)="findNextEntry()">Find
</button>
<button
[disabled]="!query || isQueryLong || isQueryNotFound"
id="find-all-btn"
class="width-8em btn btn-sm btn-dark"
(click)="findAllEntries()">Find All
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -68,15 +94,25 @@
id="replace-entry" id="replace-entry"
class="form-control" class="form-control"
type="search" type="search"
(keyup)="replaceEntry($event)" (keyup.enter)="replaceEntry($event)"
title="Replace in current file..." title="Replace in current file..."
placeholder="Replace in current file..." placeholder="Replace in current file..."
/> />
</div> </div>
</div> </div>
<div class="col col-auto"> <div class="col col-auto">
<button id="replace-btn" class="width-8em btn btn-sm btn-dark" (click)="replaceEntry($event)">Replace</button> <button
<button id="replace-all-btn" class="width-8em btn btn-sm btn-dark" (click)="replaceAll()">Replace All</button> [disabled]="!query || isQueryLong || isQueryNotFound"
id="replace-btn"
class="width-8em btn btn-sm btn-dark"
(click)="replaceEntry($event)">Replace
</button>
<button
[disabled]="!query || isQueryLong || isQueryNotFound"
id="replace-all-btn"
class="width-8em btn btn-sm btn-dark"
(click)="replaceAll()">Replace All
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,13 @@
import { Component, ElementRef, HostBinding, Input, ViewChild, inject } from '@angular/core'; import {
import { Subject, takeUntil } from 'rxjs'; 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 { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service';
@@ -20,7 +28,7 @@ import { ServiceMessage } from '../../common/types/service-message.type';
} }
}) })
export class SearchReplaceComponent { export class SearchReplaceComponent {
private unsubscribe: Subject<void> = new Subject(); readonly #destroyRef: DestroyRef = inject(DestroyRef);
private searchReplaceService: SearchReplaceService = inject(SearchReplaceService); private searchReplaceService: SearchReplaceService = inject(SearchReplaceService);
@@ -28,15 +36,19 @@ export class SearchReplaceComponent {
@ViewChild('findEntryElm') findEntryElm!: ElementRef; @ViewChild('findEntryElm') findEntryElm!: ElementRef;
@ViewChild('replaceEntryElm') replaceEntryElm!: ElementRef; @ViewChild('replaceEntryElm') replaceEntryElm!: ElementRef;
@Input() query: string = "";
@Input() findOptions: string = "";
@Input() isQueryLong: boolean = false;
@Input() isQueryNotFound: boolean = false;
@Input() totalCount: number = 0;
private editor!: any; private editor!: any;
@Input() findOptions: string = "";
private useWholeWordSearch: boolean = false; private useWholeWordSearch: boolean = false;
private searchOnlyInSelection: boolean = false; private searchOnlyInSelection: boolean = false;
private useCaseSensitive: boolean = false; private useCaseSensitive: boolean = false;
private useRegex: boolean = false; private useRegex: boolean = false;
private selection: string = ""; private selection: string = "";
private query: string = "";
private toStr: string = ""; private toStr: string = "";
private isBackwards: boolean = false; private isBackwards: boolean = false;
private isWrap: boolean = true; private isWrap: boolean = true;
@@ -45,26 +57,22 @@ export class SearchReplaceComponent {
constructor() { constructor() {
}
private ngAfterViewInit(): void {
this.loadSubscribers(); this.loadSubscribers();
} }
private ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
private loadSubscribers() { private loadSubscribers() {
this.searchReplaceService.getMessage$().pipe( this.searchReplaceService.getMessage$().pipe(
takeUntil(this.unsubscribe) takeUntilDestroyed(this.#destroyRef)
).subscribe((message: ServiceMessage) => { ).subscribe((message: ServiceMessage) => {
if (message.action === "toggle-search-replace") { switch ( message.action ) {
this.toggleSearchReplace(message); case "toggle-search-replace":
} else if (message.action === "set-active-editor") { this.toggleSearchReplace(message);
this.setActiveEditor(message); break;
case "set-active-editor":
this.setActiveEditor(message);
break;
default:
break;
} }
}); });
} }
@@ -175,17 +183,25 @@ export class SearchReplaceComponent {
this.findOptions = findOptionsStr; this.findOptions = findOptionsStr;
} }
public findPreviousEntry() {
this.editor.findPrevious();
}
public findNextEntry() { public findNextEntry() {
this.editor.findNext(); this.editor.findNext();
} }
public findEntryKeyUpHandler(event: KeyboardEvent) {
if (!event.ctrlKey || !this.query) return;
if (event.key === "ArrowUp") this.findPreviousEntry();
if (event.key === "ArrowDown") this.findNextEntry();
}
public findAllEntries() { public findAllEntries() {
this.query = this.findEntryElm.nativeElement.value; this.query = this.findEntryElm.nativeElement.value;
if (!this.query) return; this.totalCount = this.editor.findAll(this.query, {
let totalCount = this.editor.findAll(this.query, {
backwards: this.isBackwards, backwards: this.isBackwards,
wrap: this.isWrap, wrap: this.isWrap,
caseSensitive: this.useCaseSensitive, caseSensitive: this.useCaseSensitive,
@@ -193,25 +209,19 @@ export class SearchReplaceComponent {
regExp: this.useRegex, regExp: this.useRegex,
range: this.searchOnlyInSelection range: this.searchOnlyInSelection
}); });
if (this.totalCount === 0) this.isQueryNotFound = true;
} }
public findPreviousEntry() { public replaceEntry(event: KeyboardEvent) {
this.editor.findPrevious(); if (this.isQueryLong || this.isQueryNotFound) return;
}
public replaceEntry(event: any) {
if (event instanceof KeyboardEvent) {
if (event.key !== "Enter") {
return;
}
}
let fromStr = this.findEntryElm.nativeElement.value; let fromStr = this.findEntryElm.nativeElement.value;
let toStr = this.replaceEntryElm.nativeElement.value; let toStr = this.replaceEntryElm.nativeElement.value;
if (!fromStr) return; if (!fromStr) return;
let totalCount = this.editor.replace(toStr, fromStr, { this.editor.replace(toStr, fromStr, {
backwards: this.isBackwards, backwards: this.isBackwards,
wrap: this.isWrap, wrap: this.isWrap,
caseSensitive: this.useCaseSensitive, caseSensitive: this.useCaseSensitive,
@@ -225,12 +235,14 @@ export class SearchReplaceComponent {
} }
public replaceAll() { public replaceAll() {
if (this.isQueryLong || this.isQueryNotFound) return;
let fromStr = this.findEntryElm.nativeElement.value; let fromStr = this.findEntryElm.nativeElement.value;
let toStr = this.replaceEntryElm.nativeElement.value; let toStr = this.replaceEntryElm.nativeElement.value;
if (!fromStr) return; if (!fromStr) return;
let totalCount = this.editor.replaceAll(toStr, fromStr, { this.editor.replaceAll(toStr, fromStr, {
backwards: this.isBackwards, backwards: this.isBackwards,
wrap: this.isWrap, wrap: this.isWrap,
caseSensitive: this.useCaseSensitive, caseSensitive: this.useCaseSensitive,
@@ -238,23 +250,27 @@ export class SearchReplaceComponent {
regExp: this.useRegex, regExp: this.useRegex,
range: this.searchOnlyInSelection range: this.searchOnlyInSelection
}); });
this.isQueryNotFound = true;
} }
public searchForString() { public searchForString() {
if (event instanceof KeyboardEvent) { if (this.searchTimeoutId) { clearTimeout(this.searchTimeoutId); }
if (event.key !== "Enter") {
return;
}
}
this.query = this.findEntryElm.nativeElement.value; this.query = this.findEntryElm.nativeElement.value;
if (!this.query) return; if (!this.query) {
this.isQueryLong = false;
this.isQueryNotFound = false;
if (this.searchTimeoutId) { clearTimeout(this.searchTimeoutId); } return;
}
this.isQueryLong = (this.query.length > 80);
if (this.isQueryLong) return;
this.searchTimeoutId = setTimeout(() => { this.searchTimeoutId = setTimeout(() => {
let totalCount = this.editor.find(this.query, { this.totalCount = this.editor.findAll(this.query, {
backwards: this.isBackwards, backwards: this.isBackwards,
wrap: this.isWrap, wrap: this.isWrap,
caseSensitive: this.useCaseSensitive, caseSensitive: this.useCaseSensitive,
@@ -262,6 +278,8 @@ export class SearchReplaceComponent {
regExp: this.useRegex, regExp: this.useRegex,
range: this.searchOnlyInSelection range: this.searchOnlyInSelection
}); });
this.isQueryNotFound = (this.totalCount === 0);
}, this.searchTimeout); }, this.searchTimeout);
} }

View File

@@ -22,7 +22,7 @@
} }
.active-tab { .active-tab {
background-color: rgba(255, 255, 255, 0.46); background-color: rgba(144, 144, 144, 0.64);
color: rgba(255, 255, 255, 0.8); color: rgba(255, 255, 255, 0.8);
} }
@@ -42,6 +42,7 @@
margin-left: 2em; margin-left: 2em;
margin-right: 2em; margin-right: 2em;
font-size: 4em; font-size: 4em;
align-self: center;
} }
.close-button { .close-button {
@@ -53,4 +54,4 @@
.close-button:hover { .close-button:hover {
background: rgba(256, 0, 0, 0.64); background: rgba(256, 0, 0, 0.64);
} }

View File

@@ -3,7 +3,8 @@
cdkDropListLockAxis="x" cdkDropListLockAxis="x"
cdkDropListOrientation="horizontal" cdkDropListOrientation="horizontal"
(cdkDropListDropped)="dropped($event)" (cdkDropListDropped)="dropped($event)"
(click)="handleAction($event)" (mousedown)="handleActionMouseDown($event)"
(click)="handleActionClick($event)"
class="display-contents" class="display-contents"
> >
@@ -19,4 +20,16 @@
<button class="close-button">X</button> <button class="close-button">X</button>
</div> </div>
</div> </div>
<ul #contextMenu
class="contextMenu"
[hidden]="!showContextMenu"
(blur)="hideContextMenu()"
(click)="contextMenuClicked($event)"
>
<li command="close">Close</li>
<li command="closeAll">Close All</li>
<li command="closeAllLeft">Close All Left</li>
<li command="closeAllRight">Close All Right</li>
</ul>

View File

@@ -1,12 +1,21 @@
import { Component, ChangeDetectorRef, inject } from '@angular/core'; import {
Component,
ChangeDetectorRef,
DestroyRef,
ElementRef,
ViewChild,
inject
} from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop'; import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
import { Subject, takeUntil } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { TabsService } from '../../common/services/editor/tabs/tabs.service'; import { TabsService } from '../../common/services/editor/tabs/tabs.service';
import { ServiceMessage } from '../../common/types/service-message.type'; import { ServiceMessage } from '../../common/types/service-message.type';
import { ButtonMap } from '../../common/constants/button.map';
@Component({ @Component({
@@ -24,68 +33,129 @@ import { ServiceMessage } from '../../common/types/service-message.type';
} }
}) })
export class TabsComponent { export class TabsComponent {
private unsubscribe: Subject<void> = new Subject(); readonly #destroyRef = inject(DestroyRef);
private tabsService: TabsService = inject(TabsService); private tabsService: TabsService = inject(TabsService);
private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef); private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef);
@ViewChild('contextMenu') contextMenu!: ElementRef;
public showContextMenu: boolean = false;
tabs: any[] = this.tabsService.tabs; tabs: any[] = this.tabsService.tabs;
targetEvent!: any;
activeTab!: any;
constructor() { constructor() {
}
private ngAfterViewInit(): void {
this.loadSubscribers(); this.loadSubscribers();
} }
private ngOnDestroy(): void {
this.unsubscribe.next();
this.unsubscribe.complete();
}
private loadSubscribers() { private loadSubscribers() {
this.tabsService.getMessage$().pipe( this.tabsService.getMessage$().pipe(
takeUntil(this.unsubscribe) takeUntilDestroyed(this.#destroyRef)
).subscribe((message: ServiceMessage) => { ).subscribe((message: ServiceMessage) => {
if (message.action === "create-tab") { let elm = document.querySelector(`.tab[title="${message.filePath}"]`);
this.createTab(message.fileName, message.fileUUID, message.filePath);
} else if (message.action === "file-changed") { switch ( message.action ) {
let elm = document.querySelectorAll(`[title="${message.filePath}"]`)[1]; case "create-tab":
elm.classList.add("file-changed"); this.createTab(message.fileName, message.fileUUID, message.filePath);
elm.classList.remove("file-deleted"); break;
} else if (message.action === "file-deleted") { case "file-unmodified":
let elm = document.querySelectorAll(`[title="${message.filePath}"]`)[1]; elm.classList.remove("file-changed");
elm.classList.add("file-deleted"); break;
elm.classList.remove("file-changed"); case "file-changed":
} else if (message.action === "file-saved") { elm.classList.add("file-changed");
let elm = document.querySelectorAll(`[title="${message.filePath}"]`)[1]; elm.classList.remove("file-deleted");
elm.classList.remove("file-deleted"); break;
elm.classList.remove("file-changed"); 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;
case "highlight-active-tab":
if (!elm) {
if (this.activeTab) {
this.activeTab.classList.remove("active-tab")
this.activeTab = elm;
}
break;
};
if (this.activeTab) {
if (
this.activeTab.getAttribute("title") == elm.getAttribute("title")
) {
break;
}
this.activeTab.classList.remove("active-tab")
}
this.activeTab = elm;
elm.classList.add("active-tab");
elm.scrollIntoView(
{
behavior: "smooth",
block: "center",
inline: "center"
}
);
break;
default:
break;
} }
}); });
} }
protected handleAction(event: any): void { protected handleActionClick(event: any): void {
if (ButtonMap.RIGHT === event.button) return;
let target = event.target; let target = event.target;
if ( target.classList.contains("tab") ) { this.showContextMenu = false;
this.tabsService.sendEditorsServiceAMessage( this.processTargetEvent(event);
"set-tab-to-editor", }
event.srcElement.getAttribute("title")
);
} else if ( target.classList.contains("title") ) { protected handleActionMouseDown(event: any): void {
this.tabsService.sendEditorsServiceAMessage( if (ButtonMap.LEFT === event.button) return;
"set-tab-to-editor",
event.srcElement.parentElement.getAttribute("title")
);
} else if ( target.classList.contains("close-button") ) {
this.tabsService.closeTab(
event.srcElement.parentElement.getAttribute("title")
);
}
let target = event.target;
let menuElm = this.contextMenu.nativeElement;
let pageX = event.clientX;
let pageY = event.clientY;
const origin = {
left: pageX + 5,
top: pageY - 5
};
menuElm.style.left = `${origin.left}px`;
menuElm.style.top = `${origin.top}px`;
this.targetEvent = event;
this.showContextMenu = true;
}
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 createTab(title: string, uuid: string, path: string): void { public createTab(title: string, uuid: string, path: string): void {
@@ -112,4 +182,87 @@ export class TabsComponent {
this.tabsService.move(event.previousIndex); this.tabsService.move(event.previousIndex);
} }
private close(event: any): void {
this.tabsService.closeTab(
this.targetEvent.srcElement.parentElement.getAttribute("title")
);
}
private closeAll(event: any): void {
let elm = this.targetEvent.srcElement.parentElement;
let startElm = elm;
// clear right
while (elm) {
elm = elm.nextSibling;
if (!elm || elm.nodeType == 8) continue;
this.tabsService.closeTab( elm.getAttribute("title") );
}
// clear left
elm = startElm;
while (elm) {
elm = elm.previousSibling;
if (!elm || elm.nodeType == 8) continue;
this.tabsService.closeTab( elm.getAttribute("title") );
}
// clear initial target
elm = startElm;
this.tabsService.closeTab( elm.getAttribute("title") );
}
private closeAllLeft(event: any): void {
let elm = this.targetEvent.srcElement.parentElement;
// clear left
while (elm) {
elm = elm.previousSibling;
if (!elm || elm.nodeType == 8) continue;
this.tabsService.closeTab( elm.getAttribute("title") );
}
}
private closeAllRight(event: any): void {
let elm = this.targetEvent.srcElement.parentElement;
// clear right
while (elm) {
elm = elm.nextSibling;
if (!elm || elm.nodeType == 8) continue;
this.tabsService.closeTab( elm.getAttribute("title") );
}
}
private processTargetEvent(event: any): void {
let target = event.target;
if ( target.classList.contains("tab") ) {
let fpath = event.srcElement.getAttribute("title")
this.tabsService.sendEditorsServiceAMessage("set-tab-to-editor", fpath);
// this.updateActiveTabHighlight(fpath);
} else if ( target.classList.contains("title") ) {
let fpath = event.srcElement.parentElement.getAttribute("title")
this.tabsService.sendEditorsServiceAMessage("set-tab-to-editor", fpath);
// this.updateActiveTabHighlight(fpath);
} else if ( target.classList.contains("close-button") ) {
this.tabsService.closeTab(
event.srcElement.parentElement.getAttribute("title")
);
}
}
private updateActiveTabHighlight(fpath: string): void {
let message = new ServiceMessage();
message.action = "highlight-active-tab";
message.filePath = fpath;
this.tabsService.sendMessage(message);
}
} }

View File

@@ -61,3 +61,15 @@
.ace_sb-h { .ace_sb-h {
width: 0.8em !important; width: 0.8em !important;
} }
.ace_cursor {
color: rgba(249, 148, 6, 0.64);
animation: blinker 1s linear infinite;
}
@keyframes blinker {
50% {
opacity: 0;
}
}

View File

@@ -10,6 +10,10 @@ body {
overflow: hidden; overflow: hidden;
} }
ul, li {
list-style: none;
}
/* IDs */ /* IDs */

View File

@@ -57,6 +57,13 @@
margin-right: 0.2em; margin-right: 0.2em;
} }
.tabs-bar-underline {
border-color: #fff;
margin-top: -.8em;
margin-left: -.2em;
border-width: 0.2em;
}
.hr-pane-handle, .hr-pane-handle,
.vr-pane-handle { .vr-pane-handle {
border: 2px dashed lightblue; border: 2px dashed lightblue;
@@ -72,6 +79,28 @@
.contextMenu {
z-index: 999;
overflow: auto;
position: absolute;
min-width: 2em;
max-width: 8em;
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;
}
.zero-margin-padding { .zero-margin-padding {
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;

File diff suppressed because one or more lines are too long

View File

@@ -11,32 +11,35 @@
import 'zone.js'; // Included with Angular CLI. import 'zone.js'; // Included with Angular CLI.
// Note: Is set to 'any' b/c of desire to set 'render'
// side if running outside of electron mode.
declare global { declare global {
interface Window { interface Window {
electron: { electron: {
node: () => Promise<string>, node: any,
chrome: () => Promise<string>, chrome: any,
electron: () => Promise<string>, electron: any,
}, },
main: { main: {
onMenuActions: (arg0: any) => Promise<string>, onMenuActions: any,
onTerminalActions: any,
quit: any, quit: any,
toggleFullScreen: any, toggleFullScreen: any,
}, },
fs: { fs: {
getLspConfigData: () => Promise<string>, getLspConfigData: any,
getFileContents: (arg0: any) => Promise<string>, getFileContents: any,
openFiles: (arg0) => Promise<string>, openFiles: any,
saveFile: (arg0: any, arg1: any) => Promise<string>, saveFile: any,
saveFileAs: () => Promise<string>, saveFileAs: any,
chooseFolder: () => Promise<string>, chooseFolder: any,
closeFile: (arg0: any) => Promise<string>, closeFile: any,
getPathForFile: any, getPathForFile: any,
onLoadFiles: (arg0: any) => Promise<string>, onLoadFiles: any,
onUpdateFilePath: (arg0: any) => Promise<string>, onUpdateFilePath: any,
onSavedFile: (arg0: any) => Promise<string>, onSavedFile: any,
onChangedFile: (arg0: any) => Promise<string>, onChangedFile: any,
onDeletedFile: (arg0: any) => Promise<string>, onDeletedFile: any,
} }
} }
} }

1
src/typings.d.ts vendored
View File

@@ -1 +0,0 @@
declare var showdown: any;

View File

@@ -16,50 +16,6 @@
"declaration": false, "declaration": false,
"skipLibCheck": true, "skipLibCheck": true,
"strict": false, "strict": false,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true
}, }
"includes": [
"src/typings.d.ts"
]
} }
/*
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./build/app",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": [
"ES2022",
"dom"
]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
},
"includes": [
"src/typings.d.ts"
]
}
*/