Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9aae679d21 | ||
|
|
d179e1c42e | ||
|
|
da4e87d3cd | ||
|
|
f1e3557db1 | ||
|
|
dd969302cf | ||
|
|
21cf5c794a | ||
|
|
a4765952bf | ||
|
|
de5184ce22 | ||
|
|
de0802cb2c | ||
|
|
c4ab66141c | ||
|
|
6bedb69909 | ||
|
|
8b47ff3919 | ||
|
|
fd40340d97 | ||
|
|
ea134caf2b | ||
|
|
88b38370e1 | ||
|
|
ae6c21f8ad | ||
|
|
f94a8ca26c | ||
|
|
ed89f34d40 | ||
|
|
73f25aae1c | ||
|
|
3c3a5d2f50 | ||
|
|
d44e7d4e51 | ||
|
|
60289953ec | ||
|
|
df5f2d481a | ||
|
|
2ede33f3c2 | ||
|
|
822d778008 | ||
|
|
90c8c9b3ee | ||
|
|
79adb86d25 | ||
|
|
a0914e8096 | ||
|
|
a4d602f98a | ||
|
|
d6f766753c | ||
|
|
ae60905eb4 | ||
|
|
080cc22841 | ||
|
|
c5fdab59f6 | ||
|
|
ab17e48338 | ||
|
|
ec841445ed | ||
|
|
41401f7257 | ||
|
|
766e70d766 | ||
|
|
9e01628ffb | ||
|
|
27c4cda01a | ||
|
|
7b4529e8f3 | ||
|
|
23d9bb24f2 | ||
|
|
330817a745 | ||
|
|
f647aba153 | ||
|
|
a99cbc4cad | ||
|
|
ad5d2c9e92 | ||
|
|
857f0ded57 | ||
|
|
e64a18b18b | ||
|
|
5aa6c7ca10 | ||
|
|
d0c73fe4da | ||
|
|
f94ac677a9 | ||
|
|
051e42bfa3 | ||
|
|
a6b0bda263 | ||
|
|
289c061ab6 | ||
|
|
96eaa64b2a | ||
|
|
de2bb27b6d | ||
|
|
c99013df04 | ||
|
|
d1dbb7efcc | ||
|
|
d7c8ea1902 | ||
|
|
9915b98700 | ||
|
|
ad92bfc648 | ||
|
|
5965bb7552 | ||
|
|
dae0cd9516 | ||
|
|
d354a940c4 | ||
|
|
52a3785168 | ||
|
|
598a66f517 | ||
|
|
4193d46d0d | ||
|
|
41f6ea5854 | ||
|
|
3a29e0dcad | ||
|
|
c599013af4 | ||
|
|
5c19d82834 | ||
|
|
3ba44c43d8 | ||
|
|
0eed524575 | ||
|
|
18ac9b323d | ||
|
|
53bbfe2bc7 | ||
|
|
5c7dff5d2b | ||
|
|
64a6e14840 | ||
|
|
b49883f7b2 | ||
|
|
9e4ac75a91 | ||
|
|
ef56b8c84b | ||
|
|
f28dfa84c2 | ||
|
|
7f55d68372 | ||
|
|
c6b2632487 | ||
|
|
805b997483 | ||
|
|
c9c020385d | ||
|
|
7b498d0602 | ||
|
|
9d3eb76960 | ||
|
|
0675985a5e | ||
|
|
dbbc6deaae | ||
|
|
71d3dc2573 | ||
|
|
9156033f22 | ||
|
|
b11e8bb74a | ||
|
|
5b62163c4e | ||
|
|
8b01314f43 | ||
|
|
89b68d0422 | ||
|
|
6e5af8e85d | ||
|
|
dabc089b83 | ||
|
|
0d1fdfa031 | ||
|
|
04e3bbcc77 | ||
|
|
c08e6e2a00 | ||
|
|
146fce2765 | ||
|
|
82e2afa601 | ||
|
|
ae43722881 | ||
|
|
fbf6933102 | ||
|
|
05d48c5878 | ||
|
|
fc7b728fe4 | ||
|
|
2a9be75835 | ||
|
|
61e461e31a | ||
|
|
658443ca77 |
@@ -0,0 +1,134 @@
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
build/
|
||||
jspm_packages/
|
||||
.angular/
|
||||
package-lock.json
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
@@ -0,0 +1,117 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification follow.
|
||||
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
one line to give the program's name and an idea of what it does. Copyright (C) yyyy name of author
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice
|
||||
@@ -1,12 +1,17 @@
|
||||
# Newton Editor
|
||||
# Newton
|
||||
|
||||
### Note
|
||||
This repository is comprised of two branches with the first using gtk and GtkSource.View widgets to handle text editing.
|
||||
The second bramch uses Angular + Electron with Ace Editor component to handle text editing.
|
||||
Both branches have similar design patterns to Vim with 'buffers' loading to views than the traditionally understood one view per one one tab pattern.
|
||||
### Images
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
* [release/newton_gtk](https://code.itdominator.com/itdominator/Newton-Editor/src/branch/release/newton_gtk/)
|
||||
* [release/newton_electron](https://code.itdominator.com/itdominator/Newton-Editor/src/branch/release/newton_electron/)
|
||||
|
||||
# LSP
|
||||
* [Newton-LSP](https://code.itdominator.com/itdominator/Newton-LSP)
|
||||
### 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
|
||||
`
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/* Using older build flow b/c of this issue:
|
||||
https://github.com/issues/created?issue=mkslanc%7Cace-linters%7C172
|
||||
*/
|
||||
|
||||
{
|
||||
"$schema":"./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version":1,
|
||||
"newProjectRoot":"",
|
||||
"cli":{
|
||||
"analytics":false
|
||||
},
|
||||
"projects":{
|
||||
"App":{
|
||||
"projectType":"application",
|
||||
"root":"",
|
||||
"sourceRoot":"src",
|
||||
"prefix":"app",
|
||||
"architect":{
|
||||
"build":{
|
||||
"builder":"@angular-devkit/build-angular:browser",
|
||||
"options":{
|
||||
"outputPath":"build/app",
|
||||
"index":"src/index.html",
|
||||
"main":"src/main.ts",
|
||||
"polyfills":[
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"tsConfig":"tsconfig.app.json",
|
||||
"assets":[
|
||||
{
|
||||
"glob":"**/*",
|
||||
"input":"public",
|
||||
"output":"resources"
|
||||
},
|
||||
{
|
||||
"glob":"**/*",
|
||||
"input":"node_modules/ace-builds/src-min-noconflict",
|
||||
"output":"ace"
|
||||
}
|
||||
],
|
||||
"stylePreprocessorOptions":{
|
||||
},
|
||||
"styles":[
|
||||
"node_modules/bootstrap/scss/bootstrap.scss",
|
||||
"node_modules/bootstrap-icons/font/bootstrap-icons.css",
|
||||
"src/assets/css/overrides.css",
|
||||
"src/assets/css/styles.css",
|
||||
"src/assets/css/ace-overrides.css"
|
||||
],
|
||||
"scripts":[
|
||||
],
|
||||
"optimization": true
|
||||
},
|
||||
"configurations":{
|
||||
"production":{
|
||||
"budgets":[
|
||||
{
|
||||
"type":"initial",
|
||||
"maximumWarning":"15MB",
|
||||
"maximumError":"50MB"
|
||||
},
|
||||
{
|
||||
"type":"anyComponentStyle",
|
||||
"maximumWarning":"4kB",
|
||||
"maximumError":"8kB"
|
||||
}
|
||||
],
|
||||
"optimization": true
|
||||
},
|
||||
"development":{
|
||||
"outputHashing": "all",
|
||||
"sourceMap": true,
|
||||
"extractCss": true,
|
||||
"namedChunks": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
}
|
||||
},
|
||||
"defaultConfiguration":"production"
|
||||
},
|
||||
"serve":{
|
||||
"builder":"@angular-devkit/build-angular:dev-server",
|
||||
"configurations":{
|
||||
"production":{
|
||||
"buildTarget":"App:build:production"
|
||||
},
|
||||
"development":{
|
||||
"buildTarget":"App:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration":"development"
|
||||
},
|
||||
"extract-i18n":{
|
||||
"builder":"@angular-devkit/build-angular:extract-i18n"
|
||||
},
|
||||
"test":{
|
||||
"builder":"@angular-devkit/build-angular:karma",
|
||||
"options":{
|
||||
"polyfills":[
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig":"tsconfig.spec.json",
|
||||
"assets":[
|
||||
{
|
||||
"glob":"**/*",
|
||||
"input":"public"
|
||||
}
|
||||
],
|
||||
"styles":[
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts":[
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 913 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 661 KiB |
@@ -0,0 +1,112 @@
|
||||
const { BrowserWindow } = require('electron');
|
||||
const path = require('node:path');
|
||||
|
||||
const { menu } = require('./menu');
|
||||
const { systemTray } = require('./system-tray');
|
||||
const { argsParser } = require('./args-parser');
|
||||
const { settingsManager } = require('./settings-manager');
|
||||
const { terminal } = require('./terminal');
|
||||
const { newtonFs } = require('./fs');
|
||||
const { newtonIPC } = require('./ipc');
|
||||
|
||||
|
||||
const BASE_PATH = '../build/app';
|
||||
const ICON_PATH = `${BASE_PATH}/resources/newton.png`;
|
||||
|
||||
let args = [];
|
||||
|
||||
|
||||
|
||||
// Note: https://tinydew4.gitbooks.io/electron/content/api/browser-window.html
|
||||
const createWindow = (startType = "build", debug = false, args = []) => {
|
||||
this.args = args;
|
||||
let data = settingsManager.getConfig();
|
||||
|
||||
const window = new BrowserWindow({
|
||||
title: "Newton",
|
||||
x: data["config"]["main_window_x"],
|
||||
y: data["config"]["main_window_y"],
|
||||
width: data["config"]["main_window_width"],
|
||||
height: data["config"]["main_window_height"],
|
||||
minWidth: data["config"]["main_window_min_width"],
|
||||
minHeight: data["config"]["main_window_min_height"],
|
||||
show: false,
|
||||
transparent: true,
|
||||
icon: settingsManager.getIconPath(),
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
backgroundThrottling: false,
|
||||
webSecurity: true,
|
||||
sandbox: true,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
enableRemoteModule: false,
|
||||
plugins: true,
|
||||
}
|
||||
});
|
||||
|
||||
window.once('close', () => {
|
||||
settingsManager.saveConfig(window);
|
||||
});
|
||||
|
||||
window.once('ready-to-show', () => {
|
||||
window.show();
|
||||
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);
|
||||
systemTray.load(menu.menuStruct);
|
||||
terminal.load(window);
|
||||
|
||||
// window.setAutoHideMenuBar(true)
|
||||
|
||||
if (debug == true) {
|
||||
window.webContents.openDevTools();
|
||||
}
|
||||
|
||||
if (startType === "build") {
|
||||
window.loadFile(
|
||||
path.join(__dirname, `${BASE_PATH}/index.html`)
|
||||
);
|
||||
}
|
||||
|
||||
if (startType === "ng-serve") {
|
||||
window.loadURL('http://localhost:4200');
|
||||
}
|
||||
|
||||
// const childWindow = new BrowserWindow({parent: window, modal: true, show: false})
|
||||
// childWindow.loadFile(
|
||||
// path.join(__dirname, `${BASE_PATH}/index.html`)
|
||||
// );
|
||||
// childWindow.once('ready-to-show', () => {
|
||||
// childWindow.show()
|
||||
// });
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
newton: {
|
||||
createWindow: createWindow,
|
||||
args: argsParser,
|
||||
settings: settingsManager,
|
||||
fs: newtonFs,
|
||||
ipc: newtonIPC,
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
const { app } = require('electron');
|
||||
|
||||
|
||||
let startType = "build";
|
||||
let ipcPort = "4563";
|
||||
let isDebug = false;
|
||||
let args = [];
|
||||
|
||||
|
||||
|
||||
const loadKWArgs = () => {
|
||||
console.log("\n\nStart KWArgs:");
|
||||
|
||||
const hasStartAs = app.commandLine.hasSwitch("start-as");
|
||||
if (hasStartAs) {
|
||||
startType = app.commandLine.getSwitchValue("start-as");
|
||||
console.log("Has start-as switch...");
|
||||
console.log(startType);
|
||||
}
|
||||
|
||||
const hasIpcPort = app.commandLine.hasSwitch("ipc-port");
|
||||
if (hasIpcPort) {
|
||||
ipcPort = app.commandLine.getSwitchValue("ipc-port");
|
||||
console.log("Has ipc-port switch...");
|
||||
console.log(ipcPort);
|
||||
}
|
||||
|
||||
const hasDebug = app.commandLine.hasSwitch("app-debug");
|
||||
if (hasDebug) {
|
||||
isDebug = app.commandLine.getSwitchValue("app-debug");
|
||||
console.log("Has app-debug switch...");
|
||||
console.log(isDebug);
|
||||
}
|
||||
}
|
||||
|
||||
const filterOutLaunchAndKWArgs = () => {
|
||||
if (
|
||||
process.argv[0].endsWith("electron")
|
||||
) {
|
||||
process.argv = process.argv.slice(2);
|
||||
}
|
||||
|
||||
do {
|
||||
process.argv = process.argv.slice(1);
|
||||
} while (
|
||||
process.argv.length > 0 &&
|
||||
(
|
||||
process.argv[0].endsWith("/newton") ||
|
||||
process.argv[0].endsWith(".AppImage") ||
|
||||
process.argv[0].includes("--trace-warnings") ||
|
||||
process.argv[0].includes("--start-as") ||
|
||||
process.argv[0].includes("--ipc-port")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const loadVArgs = () => {
|
||||
console.log("\n\nStart VArgs:");
|
||||
args = process.argv;
|
||||
args.forEach((val, index, array) => {
|
||||
console.log(index + ': ' + val);
|
||||
});
|
||||
|
||||
console.log("\n\n");
|
||||
}
|
||||
|
||||
const loadArgs = () => {
|
||||
loadKWArgs();
|
||||
filterOutLaunchAndKWArgs();
|
||||
loadVArgs();
|
||||
}
|
||||
|
||||
|
||||
const getArgs = () => {
|
||||
return args;
|
||||
}
|
||||
|
||||
const getStartType = () => {
|
||||
return startType;
|
||||
}
|
||||
|
||||
const getDebugMode = () => {
|
||||
return isDebug;
|
||||
}
|
||||
|
||||
const getIpcPort = () => {
|
||||
return ipcPort;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
argsParser: {
|
||||
loadArgs: loadArgs,
|
||||
getArgs: getArgs,
|
||||
getStartType: getStartType,
|
||||
getDebugMode: getDebugMode,
|
||||
getIpcPort: getIpcPort,
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,231 @@
|
||||
const { dialog } = require('electron');
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const os = require('os');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
|
||||
const HOME_DIR = os.homedir();
|
||||
const BASE_PATH = '../build/app';
|
||||
const CONFIG_PATH = path.join(HOME_DIR, "/.config/newton/");
|
||||
const SETTINGS_CONFIG_PATH = path.join(CONFIG_PATH, "/settings.json");
|
||||
const LSP_CONFIG_PATH = path.join(BASE_PATH, "/resources/lsp-servers-config.json");
|
||||
let window = null;
|
||||
let watcher = null;
|
||||
let skipOnceFileWatchChange = false;
|
||||
|
||||
|
||||
|
||||
const getIconPath = () => {
|
||||
return path.join(CONFIG_PATH, "./icons/newton.png");
|
||||
}
|
||||
|
||||
const getSettingsConfigData = () => {
|
||||
return getFileContents(
|
||||
SETTINGS_CONFIG_PATH,
|
||||
useRelativePath = false,
|
||||
watchFile = false
|
||||
);
|
||||
}
|
||||
|
||||
const getLspConfigData = () => {
|
||||
return getFileContents(
|
||||
LSP_CONFIG_PATH,
|
||||
useRelativePath = true,
|
||||
watchFile = false
|
||||
).replaceAll("{user.home}", HOME_DIR);
|
||||
}
|
||||
|
||||
const getFileContents = (_path, useRelativePath = false, watchFile = true) => {
|
||||
console.log(`Getting Contents For: ${_path}`);
|
||||
|
||||
try {
|
||||
if (useRelativePath)
|
||||
return fs.readFileSync(
|
||||
path.join(__dirname, _path),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
if (watchFile)
|
||||
watcher.add(_path);
|
||||
|
||||
return fs.readFileSync(_path, 'utf8');
|
||||
} catch(err) {
|
||||
return `{"message": {"type": "error", "text": "Error: Could not read ${_path}"}}`;
|
||||
}
|
||||
}
|
||||
|
||||
const setWindow = (win) => {
|
||||
window = win;
|
||||
}
|
||||
|
||||
const saveSettingsConfigData = (data) => {
|
||||
saveFile(SETTINGS_CONFIG_PATH, data);
|
||||
}
|
||||
|
||||
const saveFile = (fpath, content) => {
|
||||
skipOnceFileWatchChange = true;
|
||||
|
||||
fs.writeFile(fpath, content, (err) => {
|
||||
if (err) {
|
||||
console.error("An error ocurred writing to the file " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
let parentDir = path.dirname(fpath);
|
||||
let watchers = watcher.getWatched();
|
||||
let targetDir = watchers[parentDir];
|
||||
if (
|
||||
!targetDir || !targetDir.includes( path.basename(fpath) )
|
||||
) {
|
||||
skipOnceFileWatchChange = false;
|
||||
watcher.add(fpath);
|
||||
window.webContents.send('update-file-path', fpath);
|
||||
}
|
||||
|
||||
try {
|
||||
window.webContents.send('file-saved', fpath);
|
||||
} catch(e) {}
|
||||
});
|
||||
}
|
||||
|
||||
const saveFileAs = () => {
|
||||
return dialog.showSaveDialog().then((response) => {
|
||||
if (response.canceled) {
|
||||
console.debug("You didn't save the file");
|
||||
return;
|
||||
}
|
||||
|
||||
return response.filePath;
|
||||
});
|
||||
}
|
||||
|
||||
const chooseFolder = () => {
|
||||
return dialog.showOpenDialog(
|
||||
{
|
||||
title: "Choose Folder:",
|
||||
defaultPath: HOME_DIR,
|
||||
properties: [
|
||||
'openDirectory'
|
||||
]
|
||||
}
|
||||
).then((response) => {
|
||||
if (response.canceled) {
|
||||
console.debug("Canceled folder selection...");
|
||||
return "";
|
||||
}
|
||||
|
||||
return response.filePaths[0];
|
||||
});
|
||||
}
|
||||
|
||||
const openFiles = (startPath) => {
|
||||
dialog.showOpenDialog(
|
||||
{
|
||||
title: "Open File(s):",
|
||||
defaultPath: (startPath) ? startPath : HOME_DIR,
|
||||
filters: [
|
||||
{ name: "All Files", extensions: ["*"] },
|
||||
{ name: "All Sub Filters",
|
||||
extensions: [
|
||||
"h", "c", "hpp", "cpp", "js", "css", "scss", "html",
|
||||
"ts", "java", "py", "pyc", "txt", "log", "md", "r",
|
||||
"rc", "go"
|
||||
]
|
||||
},
|
||||
{ name: "C", extensions: ["h", "c"] },
|
||||
{ name: "CPP", extensions: ["hpp", "cpp"] },
|
||||
{ name: "HTML", extensions: ["js", "css", "scss", "html", "ts"] },
|
||||
{ name: "Java", extensions: ["java"] },
|
||||
{ name: "Python", extensions: ["py", "pyc"] },
|
||||
{ name: "Text", extensions: ["txt", "log", "md"] },
|
||||
{ name: "Rust", extensions: ["r", "rc"] },
|
||||
{ name: "Go", extensions: ["go"] }
|
||||
],
|
||||
properties: [
|
||||
'openFile',
|
||||
'multiSelections'
|
||||
]
|
||||
}
|
||||
).then((response) => {
|
||||
if (response.canceled) {
|
||||
console.debug("Canceled file(s) open request...");
|
||||
return;
|
||||
}
|
||||
|
||||
window.webContents.send('load-files', response.filePaths);
|
||||
watcher.add(response.filePaths);
|
||||
});
|
||||
}
|
||||
|
||||
const loadFilesWatcher = () => {
|
||||
watcher = chokidar.watch([], {});
|
||||
|
||||
watcher.on('change', (fpath) => {
|
||||
if (skipOnceFileWatchChange) {
|
||||
skipOnceFileWatchChange = false;
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug("File (changed) : ", fpath);
|
||||
const data = getFileContents(fpath, false, false);
|
||||
window.webContents.send('file-changed', fpath, data);
|
||||
}).on('unlink', (fpath) => {
|
||||
console.debug("File (deleted) : ", fpath);
|
||||
window.webContents.send('file-deleted', fpath);
|
||||
});
|
||||
}
|
||||
|
||||
const unwatchFile = async (fpath) => {
|
||||
console.debug("File (unwatch) : ", fpath);
|
||||
await watcher.unwatch(fpath);
|
||||
}
|
||||
|
||||
const closeFile = (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 = {
|
||||
newtonFs: {
|
||||
CONFIG_PATH: CONFIG_PATH,
|
||||
setWindow: setWindow,
|
||||
chooseFolder: chooseFolder,
|
||||
openFiles: openFiles,
|
||||
saveFile: saveFile,
|
||||
saveFileAs: saveFileAs,
|
||||
closeFile: closeFile,
|
||||
getIconPath: getIconPath,
|
||||
getFileContents: getFileContents,
|
||||
getLspConfigData: getLspConfigData,
|
||||
getSettingsConfigData: getSettingsConfigData,
|
||||
saveSettingsConfigData: saveSettingsConfigData,
|
||||
readAndInjectJS: readAndInjectJS,
|
||||
readAndInjectCSS: readAndInjectCSS,
|
||||
loadFilesWatcher: loadFilesWatcher,
|
||||
unwatchFile: unwatchFile,
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const http = require('http');
|
||||
const socketIO = require('socket.io');
|
||||
const fetch = require('electron-fetch').default
|
||||
|
||||
|
||||
const IPC_SERVER_IP = "127.0.0.1";
|
||||
let window = null;
|
||||
let ipcServer = null;
|
||||
let ipcServerPort = "";
|
||||
let ipcServerURL = "";
|
||||
|
||||
|
||||
const setWindow = (win) => {
|
||||
window = win;
|
||||
}
|
||||
|
||||
const configure = (ipcPort) => {
|
||||
ipcServerPort = ipcPort;
|
||||
ipcServerURL = `http://${IPC_SERVER_IP}:${ipcServerPort}`;
|
||||
}
|
||||
|
||||
const loadIPCServer = (fpath) => {
|
||||
|
||||
const app = express();
|
||||
ipcServer = http.createServer(app);
|
||||
const io = socketIO(ipcServer);
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use(
|
||||
bodyParser.urlencoded({
|
||||
extended: true,
|
||||
}),
|
||||
);
|
||||
|
||||
app.get("/is-up", (req, res) => {
|
||||
res.status(200).send('yes');
|
||||
});
|
||||
|
||||
app.post("/load-files", (req, res) => {
|
||||
console.debug("Load File(s) : ", req.body);
|
||||
|
||||
window.webContents.send('load-files', req.body);
|
||||
window.show();
|
||||
window.focus();
|
||||
|
||||
res.status(200).send('');
|
||||
});
|
||||
|
||||
ipcServer.listen(ipcServerPort, () => {
|
||||
console.debug("IPCServer (start) : Started on port ", ipcServerPort, " .");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const isIPCServerUp = async () => {
|
||||
const response = await fetch(`${ipcServerURL}/is-up`)
|
||||
.catch((err) => {
|
||||
console.debug("IPCServer (status) : Not up; okay to start.");
|
||||
return {
|
||||
text: () => {
|
||||
return "no";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const body = await response.text();
|
||||
return (body == "yes") ? true : false;
|
||||
}
|
||||
|
||||
const sendFilesToIPC = async (files) => {
|
||||
let request = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(files),
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
|
||||
await fetch(`${ipcServerURL}/load-files`, request);
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
newtonIPC: {
|
||||
configure: configure,
|
||||
loadIPCServer: loadIPCServer,
|
||||
isIPCServerUp: isIPCServerUp,
|
||||
sendFilesToIPC: sendFilesToIPC,
|
||||
setWindow: setWindow
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
const { app, ipcMain } = require('electron');
|
||||
|
||||
app.commandLine.appendSwitch('disable-renderer-backgrounding');
|
||||
app.commandLine.appendSwitch('disable-background-timer-throttling');
|
||||
app.commandLine.appendSwitch('disable-backgrounding-occluded-windows');
|
||||
|
||||
const { newton } = require('./app');
|
||||
|
||||
|
||||
|
||||
let window = null;
|
||||
let hasExitSaved = false;
|
||||
|
||||
|
||||
|
||||
const createWindow = () => {
|
||||
window = newton.createWindow(
|
||||
newton.args.getStartType(),
|
||||
newton.args.getDebugMode(),
|
||||
newton.args.getArgs(),
|
||||
);
|
||||
}
|
||||
|
||||
const handleExitSignal = (code) => {
|
||||
if (!hasExitSaved) {
|
||||
newton.settings.saveConfig(window);
|
||||
hasExitSaved = true;
|
||||
}
|
||||
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
|
||||
const loadProcessSignalHandlers = () => {
|
||||
process.on('SIGINT', () => {
|
||||
// https://nodejs.org/docs/v14.16.0/api/process.html#process_signal_events
|
||||
handleExitSignal(128 + 2);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
// https://nodejs.org/docs/v14.16.0/api/process.html#process_signal_events
|
||||
handleExitSignal(128 + 15);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
if (error.message != null) {
|
||||
console.log(error.message);
|
||||
}
|
||||
|
||||
if (error.stack != null) {
|
||||
console.log(error.stack);
|
||||
}
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', function(error = {}) {
|
||||
if (error.message != null) {
|
||||
console.log(error.message);
|
||||
}
|
||||
|
||||
if (error.stack != null) {
|
||||
console.log(error.stack);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const loadHandlers = () => {
|
||||
ipcMain.handle('quit', (eve) => app.quit());
|
||||
ipcMain.handle('toggleFullScreen', (eve) => { window.setFullScreen(!window.isFullScreen()); });
|
||||
ipcMain.handle('getLspConfigData', (eve) => newton.fs.getLspConfigData());
|
||||
ipcMain.handle('getFileContents', (eve, path) => newton.fs.getFileContents(path));
|
||||
ipcMain.handle('openFiles', (eve, startPath) => newton.fs.openFiles(startPath));
|
||||
ipcMain.handle('saveFile', (eve, path, content) => newton.fs.saveFile(path, content));
|
||||
ipcMain.handle('closeFile', (eve, path) => newton.fs.closeFile(path));
|
||||
ipcMain.handle('saveFileAs', (eve) => newton.fs.saveFileAs());
|
||||
ipcMain.handle('chooseFolder', (eve) => newton.fs.chooseFolder());
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
loadProcessSignalHandlers();
|
||||
newton.args.loadArgs();
|
||||
|
||||
newton.ipc.configure( newton.args.getIpcPort() );
|
||||
if ( !await newton.ipc.isIPCServerUp() ) {
|
||||
newton.ipc.loadIPCServer();
|
||||
} else {
|
||||
console.log("IPCServer: Is loaded... sending files to existing app.");
|
||||
newton.ipc.sendFilesToIPC(
|
||||
newton.args.getArgs()
|
||||
);
|
||||
app.quit();
|
||||
}
|
||||
|
||||
newton.settings.loadSettings();
|
||||
newton.fs.loadFilesWatcher();
|
||||
|
||||
loadHandlers();
|
||||
createWindow();
|
||||
|
||||
newton.fs.setWindow(window);
|
||||
newton.ipc.setWindow(window);
|
||||
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
};
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
const { Menu } = require('electron');
|
||||
|
||||
|
||||
|
||||
const load = (win) => {
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: "File",
|
||||
submenu: [
|
||||
{
|
||||
label: 'New',
|
||||
click: () => win.webContents.send('menu-actions', "new-file")
|
||||
}, {
|
||||
label: 'Open',
|
||||
click: () => win.webContents.send('menu-actions', "open-files")
|
||||
}, {
|
||||
label: 'save',
|
||||
click: () => win.webContents.send('menu-actions', "save-file")
|
||||
}, {
|
||||
label: 'Save As',
|
||||
click: () => win.webContents.send('menu-actions', "save-file-as")
|
||||
}, {
|
||||
label: 'Terminal',
|
||||
click: () => {}
|
||||
}, {
|
||||
label: "Quit",
|
||||
click: () => win.webContents.send('menu-actions', "quit")
|
||||
}
|
||||
]
|
||||
}, {
|
||||
label: "Edit",
|
||||
submenu: [
|
||||
{
|
||||
label: 'Undo',
|
||||
click: () => win.webContents.send('menu-actions', "undo")
|
||||
}, {
|
||||
label: 'Redo',
|
||||
click: () => win.webContents.send('menu-actions', "redo")
|
||||
}, {
|
||||
label: 'Cut',
|
||||
click: () => win.webContents.send('menu-actions', "cut")
|
||||
}, {
|
||||
label: 'Copy',
|
||||
click: () => win.webContents.send('menu-actions', "copy")
|
||||
}, {
|
||||
label: 'Paste',
|
||||
click: () => win.webContents.send('menu-actions', "paste")
|
||||
|
||||
}, {
|
||||
label: 'Delete',
|
||||
click: () => win.webContents.send('menu-actions', "delete")
|
||||
}, {
|
||||
label: 'Select All',
|
||||
click: () => win.webContents.send('menu-actions', "select-all")
|
||||
}, {
|
||||
label: 'Indent',
|
||||
click: () => win.webContents.send('menu-actions', "blockindent")
|
||||
}, {
|
||||
label: 'De-Indent',
|
||||
click: () => win.webContents.send('menu-actions', "blockoutdent")
|
||||
}, {
|
||||
label: 'To Upper Case',
|
||||
click: () => win.webContents.send('menu-actions', "touppercase")
|
||||
}, {
|
||||
label: 'To Lower Case',
|
||||
click: () => win.webContents.send('menu-actions', "tolowercase")
|
||||
},
|
||||
]
|
||||
}, {
|
||||
label: "View",
|
||||
submenu: [
|
||||
{
|
||||
label: 'Zoom In',
|
||||
click: () => win.webContents.send('menu-actions', "zoom-in")
|
||||
}, {
|
||||
label: 'Zoom Out',
|
||||
click: () => win.webContents.send('menu-actions', "zoom-out")
|
||||
}, {
|
||||
label: 'Toggle Full Screen',
|
||||
click: () => { win.setFullScreen(!win.fullScreen) }
|
||||
}, {
|
||||
label: 'Toggle Developer Tools',
|
||||
click: () => win.webContents.toggleDevTools()
|
||||
}
|
||||
]
|
||||
}, {
|
||||
label: "Help",
|
||||
submenu: [
|
||||
{
|
||||
label: 'About',
|
||||
click: () => win.webContents.send('menu-actions', "show-about")
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
]);
|
||||
|
||||
Menu.setApplicationMenu(menu)
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
menu: {
|
||||
load: load
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
const { contextBridge, ipcRenderer, webUtils } = require('electron')
|
||||
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
node: () => process.versions.node,
|
||||
chrome: () => process.versions.chrome,
|
||||
electron: () => process.versions.electron,
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('main', {
|
||||
onMenuActions: (callback) => ipcRenderer.on('menu-actions', (_event, action) => callback(action)),
|
||||
onTerminalActions: (callback) => ipcRenderer.on('terminal-actions', (_event, action) => callback(action)),
|
||||
quit: () => ipcRenderer.invoke("quit"),
|
||||
toggleFullScreen: () => ipcRenderer.invoke("toggleFullScreen"),
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('fs', {
|
||||
getLspConfigData: () => ipcRenderer.invoke("getLspConfigData"),
|
||||
getFileContents: (path) => ipcRenderer.invoke("getFileContents", path),
|
||||
openFiles: (startPath) => ipcRenderer.invoke("openFiles", startPath),
|
||||
saveFile: (path, content) => ipcRenderer.invoke("saveFile", path, content),
|
||||
saveFileAs: () => ipcRenderer.invoke("saveFileAs"),
|
||||
chooseFolder: () => ipcRenderer.invoke("chooseFolder"),
|
||||
closeFile: (path) => ipcRenderer.invoke("closeFile", path),
|
||||
getPathForFile: (file) => webUtils.getPathForFile(file),
|
||||
onLoadFiles: (callback) => ipcRenderer.on('load-files', (_event, paths) => callback(paths)),
|
||||
onUpdateFilePath: (callback) => ipcRenderer.on('update-file-path', (_event, paths) => callback(paths)),
|
||||
onSavedFile: (callback) => ipcRenderer.on('file-saved', (_event, path) => callback(path)),
|
||||
onChangedFile: (callback) => ipcRenderer.on('file-changed', (_event, path, data) => callback(path, data)),
|
||||
onDeletedFile: (callback) => ipcRenderer.on('file-deleted', (_event, path) => callback(path)),
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
const { newtonFs } = require('./fs');
|
||||
|
||||
|
||||
let config = {};
|
||||
|
||||
|
||||
const loadSettings = () => {
|
||||
config = JSON.parse(
|
||||
newtonFs.getSettingsConfigData()
|
||||
);
|
||||
}
|
||||
|
||||
const getConfig = () => {
|
||||
return config;
|
||||
}
|
||||
|
||||
const saveConfig = (window) => {
|
||||
if (!window) return;
|
||||
|
||||
const windowBounds = window.getBounds();
|
||||
|
||||
config["config"]["main_window_x"] = windowBounds.x;
|
||||
config["config"]["main_window_y"] = windowBounds.y;
|
||||
config["config"]["main_window_width"] = windowBounds.width;
|
||||
config["config"]["main_window_height"] = windowBounds.height;
|
||||
|
||||
newtonFs.saveSettingsConfigData(
|
||||
JSON.stringify(config, null, 4)
|
||||
);
|
||||
}
|
||||
|
||||
const getIconPath = () => {
|
||||
return newtonFs.getIconPath();
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
settingsManager: {
|
||||
getIconPath: getIconPath,
|
||||
loadSettings: loadSettings,
|
||||
getConfig: getConfig,
|
||||
saveConfig: saveConfig,
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
const { Tray, Menu } = require('electron')
|
||||
const { newtonFs } = require('./fs');
|
||||
|
||||
|
||||
|
||||
const load = (win) => {
|
||||
let trayIcon = new Tray( newtonFs.getIconPath() );
|
||||
|
||||
const trayMenuTemplate = [
|
||||
{
|
||||
label: "File",
|
||||
submenu: [
|
||||
{
|
||||
label: 'New',
|
||||
click: () => win.webContents.send('menu-actions', "new-file")
|
||||
}, {
|
||||
label: 'Open',
|
||||
click: () => win.webContents.send('menu-actions', "open-files")
|
||||
}, {
|
||||
label: 'save',
|
||||
click: () => win.webContents.send('menu-actions', "save-file")
|
||||
}, {
|
||||
label: 'Save As',
|
||||
click: () => win.webContents.send('menu-actions', "save-file-as")
|
||||
}, {
|
||||
label: 'Terminal',
|
||||
click: () => {}
|
||||
}
|
||||
]
|
||||
}, {
|
||||
label: 'Settings',
|
||||
click: () => win.webContents.send('menu-actions', "open-settings")
|
||||
}, {
|
||||
label: 'Help',
|
||||
click: () => win.webContents.send('menu-actions', "show-about")
|
||||
}, {
|
||||
label: 'Quit',
|
||||
click: () => win.webContents.send('menu-actions', "quit")
|
||||
}
|
||||
];
|
||||
|
||||
trayIcon.setContextMenu(
|
||||
Menu.buildFromTemplate(trayMenuTemplate)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
systemTray: {
|
||||
load: load,
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"name": "Newton",
|
||||
"description": "A uniquly simple quasi-IDE for hardcore developers.",
|
||||
"version": "0.0.1",
|
||||
"author": "ITDominator",
|
||||
"license": "GPL-2.0-only",
|
||||
"homepage": "https://www.itdominator.com",
|
||||
"email": "[email protected]",
|
||||
"main": "newton/main.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"app": "ng build --base-href ./ && electron . --trace-warnings --start-as=build --ipc-port=4588",
|
||||
"electron-start": "electron . --trace-warnings --start-as=build --ipc-port=4588",
|
||||
"electron-pack": "ng build --base-href ./ && electron-builder --dir",
|
||||
"electron-dist": "ng build --base-href ./ && electron-builder",
|
||||
"electron-dist-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-concurrently": "concurrently 'ng serve' 'electron . --trace-warnings --start-as=ng-serve'",
|
||||
"ng-serve": "ng serve",
|
||||
"ng-build": "ng build --base-href ./",
|
||||
"ng-watch-build": "ng build --watch --configuration development",
|
||||
"ng-test": "ng test",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"build": {
|
||||
"appId": "newton",
|
||||
"icon": "./icos/",
|
||||
"files": [
|
||||
"newton/",
|
||||
"build/"
|
||||
],
|
||||
"mac": {
|
||||
"category": "public.app-category.developer-tools"
|
||||
},
|
||||
"win": {
|
||||
"target": "portable"
|
||||
},
|
||||
"linux": {
|
||||
"target": "zip",
|
||||
"category": "Development",
|
||||
"maintainer": "ITDominator"
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"glob": "9.0.0",
|
||||
"rimraf": "4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ace-diff": "3.0.3",
|
||||
"ace-layout": "1.5.0",
|
||||
"ace-linters": "1.8.3",
|
||||
"bootstrap": "5.3.6",
|
||||
"bootstrap-icons": "1.12.1",
|
||||
"chokidar": "4.0.3",
|
||||
"electron-fetch": "1.9.1",
|
||||
"express": "4.18.2",
|
||||
"marked": "16.4.0",
|
||||
"socket.io": "4.8.1",
|
||||
"uuid": "11.1.0",
|
||||
"zone.js": "0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ace-builds": "1.43.0",
|
||||
"@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/compiler-cli": "19.2.0",
|
||||
"@angular/forms": "19.2.0",
|
||||
"@angular/platform-browser": "19.2.0",
|
||||
"@types/express": "4.17.17",
|
||||
"@types/jasmine": "5.1.0",
|
||||
"@types/node": "18.18.0",
|
||||
"concurrently": "9.1.2",
|
||||
"electron": "36.2.0",
|
||||
"@electron/remote": "2.1.2",
|
||||
"electron-builder": "22.7.0",
|
||||
"jasmine-core": "5.6.0",
|
||||
"jimp": "1.6.0",
|
||||
"karma": "6.4.0",
|
||||
"karma-chrome-launcher": "3.2.0",
|
||||
"karma-coverage": "2.2.0",
|
||||
"karma-jasmine": "5.1.0",
|
||||
"karma-jasmine-html-reporter": "2.1.0",
|
||||
"nanoevents": "9.1.0",
|
||||
"rxjs": "7.8.0",
|
||||
"tree-sitter": "0.21.1",
|
||||
"tree-sitter-bash": "0.23.2",
|
||||
"tree-sitter-c": "0.23.1",
|
||||
"tree-sitter-cli": "0.25.8",
|
||||
"tree-sitter-cpp": "0.23.4",
|
||||
"tree-sitter-css": "0.23.0",
|
||||
"tree-sitter-go": "0.23.4",
|
||||
"tree-sitter-html": "0.23.2",
|
||||
"tree-sitter-java": "0.23.5",
|
||||
"tree-sitter-javascript": "0.23.1",
|
||||
"tree-sitter-json": "0.24.8",
|
||||
"tree-sitter-lua": "2.1.3",
|
||||
"tree-sitter-php": "0.23.12",
|
||||
"tree-sitter-python": "0.23.2",
|
||||
"tree-sitter-r": "0.0.1-security",
|
||||
"tree-sitter-sql": "0.1.0",
|
||||
"tree-sitter-sqlite": "0.0.1-security",
|
||||
"tree-sitter-toml": "0.5.1",
|
||||
"tree-sitter-typescript": "0.23.2",
|
||||
"tree-sitter-yaml": "0.5.0",
|
||||
"tslib": "2.3.0",
|
||||
"typescript": "5.7.2"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
@@ -0,0 +1,185 @@
|
||||
ace.define("ace/theme/penguins_in_space-css", ["require", "exports", "module"], function(require, exports, module) {
|
||||
module.exports = """
|
||||
.penguins-in-space .ace_gutter {
|
||||
background: #282c34;
|
||||
color: #6a6f7a;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_print-margin {
|
||||
width: 1px;
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
.penguins-in-space {
|
||||
background-color: #282c34;
|
||||
color: #abb2bf;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_cursor {
|
||||
color: #528bff;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_marker-layer .ace_selection {
|
||||
background: #3d4350;
|
||||
}
|
||||
|
||||
.penguins-in-space.ace_multiselect .ace_selection.ace_start {
|
||||
box-shadow: 0 0 3px 0 #282c34;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_marker-layer .ace_step {
|
||||
background: #c6dbae;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_marker-layer .ace_bracket {
|
||||
margin: -1px 0 0 -1px;
|
||||
border: 1px solid #747369;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_marker-layer .ace_active-line {
|
||||
background: rgba(76,87,103,.19);
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_gutter-active-line {
|
||||
background-color: rgba(76,87,103,.19);
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_marker-layer .ace_selected-word {
|
||||
border: 1px solid #3d4350;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_fold {
|
||||
background-color: #61afef;
|
||||
border-color: #abb2bf;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_keyword {
|
||||
color: #00a8c6;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_keyword.ace_operator {
|
||||
color: #00a8c6;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_keyword.ace_other.ace_unit {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_constant.ace_language {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_constant.ace_numeric {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_constant.ace_character {
|
||||
color: #56b6c2;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_constant.ace_other {
|
||||
color: #56b6c2;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_support.ace_function {
|
||||
color: #61afef;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_support.ace_constant {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_support.ace_class {
|
||||
color: #ff5d38;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_support.ace_type {
|
||||
color: #ff5d38;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_storage {
|
||||
color: #c678dd;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_storage.ace_type {
|
||||
color: #E6DB74;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_invalid {
|
||||
color: #fff;
|
||||
background-color: #f2777a;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_invalid.ace_deprecated {
|
||||
color: #272b33;
|
||||
background-color: #d27b53;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_string {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_string.ace_regexp {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_comment {
|
||||
font-style: italic;
|
||||
color: #454a54;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_variable {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_variable.ace_parameter {
|
||||
color: #d19a66;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_meta.ace_tag {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_entity.ace_other.ace_attribute-name {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_entity.ace_name.ace_function {
|
||||
color: #61afef;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_entity.ace_name.ace_tag {
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_markup.ace_heading {
|
||||
color: #98c379;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_indent-guide {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ09NrYAgMjP4PAAtGAwchHMyAAAAAAElFTkSuQmCC) right repeat-y;
|
||||
}
|
||||
|
||||
.penguins-in-space .ace_indent-guide-active {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQIW2PQ1dX9zzBz5sz/ABCcBFFentLlAAAAAElFTkSuQmCC) right repeat-y;
|
||||
}
|
||||
""";
|
||||
|
||||
});
|
||||
|
||||
ace.define("ace/theme/penguins_in_space", ["require", "exports", "module", "ace/theme/penguins_in_space-css", "ace/lib/dom"], function(require, exports, module) {
|
||||
exports.isDark = true;
|
||||
exports.cssClass = "penguins-in-space";
|
||||
exports.cssText = require("./penguins_in_space-css");
|
||||
var dom = require("../lib/dom");
|
||||
dom.importCssString(exports.cssText, exports.cssClass, false);
|
||||
|
||||
});
|
||||
(function() {
|
||||
ace.require(["ace/theme/penguins_in_space"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,363 @@
|
||||
{
|
||||
"java": {
|
||||
"info": "https://download.eclipse.org/jdtls/",
|
||||
"info-init-options": "https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line",
|
||||
"info-import-build": "https://www.javahotchocolate.com/tutorials/build-path.html",
|
||||
"info-external-class-paths": "https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/3291",
|
||||
"link": "https://download.eclipse.org/jdtls/milestones/?d",
|
||||
"command": "lsp-ws-proxy --listen 4114 -- jdtls",
|
||||
"alt-command": "lsp-ws-proxy -- jdtls",
|
||||
"alt-command2": "java-language-server",
|
||||
"socket": "ws://127.0.0.1:9999/java",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=jdtls",
|
||||
"alt-socket": "ws://127.0.0.1:9999/?name=java-language-server",
|
||||
"initialization-options": {
|
||||
"bundles": [
|
||||
],
|
||||
"workspaceFolders": [
|
||||
"file://{workspace.folder}"
|
||||
],
|
||||
"extendedClientCapabilities": {
|
||||
"classFileContentsSupport": true,
|
||||
"executeClientCommandSupport": false
|
||||
},
|
||||
"settings": {
|
||||
"java": {
|
||||
"autobuild": {
|
||||
"enabled": true
|
||||
},
|
||||
"jdt": {
|
||||
"ls": {
|
||||
"javac": {
|
||||
"enabled": true
|
||||
},
|
||||
"java": {
|
||||
"home": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2"
|
||||
},
|
||||
"lombokSupport": {
|
||||
"enabled": true
|
||||
},
|
||||
"protobufSupport":{
|
||||
"enabled": true
|
||||
},
|
||||
"androidSupport": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"updateBuildConfiguration": "automatic",
|
||||
"maven": {
|
||||
"userSettings": "{user.home}/.config/lsps/jdtls/settings.xml",
|
||||
"globalSettings": "{user.home}/.config/lsps/jdtls/settings.xml"
|
||||
},
|
||||
"runtimes": [
|
||||
{
|
||||
"name": "JavaSE-17",
|
||||
"path": "/usr/lib/jvm/java-17-openjdk",
|
||||
"javadoc": "https://docs.oracle.com/en/java/javase/17/docs/api/",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "JavaSE-22",
|
||||
"path": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2",
|
||||
"javadoc": "https://docs.oracle.com/en/java/javase/22/docs/api/",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"classPath": [
|
||||
"{user.home}/.config/lsps/jdtls/m2/repository/**/*-sources.jar",
|
||||
"lib/**/*-sources.jar"
|
||||
],
|
||||
"docPath": [
|
||||
"{user.home}/.config/lsps/jdtls/m2/repository/**/*-javadoc.jar",
|
||||
"lib/**/*-javadoc.jar"
|
||||
],
|
||||
"project": {
|
||||
"encoding": "ignore",
|
||||
"outputPath": "bin",
|
||||
"referencedLibraries": [
|
||||
"{user.home}/.config/lsps/jdtls/m2/repository/**/*.jar",
|
||||
"lib/**/*.jar"
|
||||
],
|
||||
"importOnFirstTimeStartup": "automatic",
|
||||
"importHint": true,
|
||||
"resourceFilters": [
|
||||
"node_modules",
|
||||
"\\.git"
|
||||
],
|
||||
"sourcePaths": [
|
||||
"src",
|
||||
"{user.home}/.config/lsps/jdtls/m2/repository/**/*.jar"
|
||||
]
|
||||
},
|
||||
"sources": {
|
||||
"organizeImports": {
|
||||
"starThreshold": 99,
|
||||
"staticStarThreshold": 99
|
||||
}
|
||||
},
|
||||
"imports": {
|
||||
"gradle": {
|
||||
"wrapper": {
|
||||
"checksums": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
"maven": {
|
||||
"enabled": true,
|
||||
"offline": {
|
||||
"enabled": false
|
||||
},
|
||||
"disableTestClasspathFlag": false
|
||||
},
|
||||
"gradle": {
|
||||
"enabled": true,
|
||||
"wrapper": {
|
||||
"enabled": true
|
||||
},
|
||||
"version": "",
|
||||
"home": "{user.home}/Portable_Apps/sdks/gradle/gradle-9.0.0",
|
||||
"java": {
|
||||
"home": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2"
|
||||
},
|
||||
"offline": {
|
||||
"enabled": false
|
||||
},
|
||||
"arguments": [],
|
||||
"jvmArguments": [],
|
||||
"user": {
|
||||
"home": ""
|
||||
},
|
||||
"annotationProcessing": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"exclusions": [
|
||||
"**/node_modules/**",
|
||||
"**/.metadata/**",
|
||||
"**/archetype-resources/**",
|
||||
"**/META-INF/maven/**"
|
||||
],
|
||||
"generatesMetadataFilesAtProjectRoot": false
|
||||
},
|
||||
"maven": {
|
||||
"downloadSources": true,
|
||||
"updateSnapshots": true
|
||||
},
|
||||
"silentNotification": true,
|
||||
"contentProvider": {
|
||||
"preferred": "fernflower"
|
||||
},
|
||||
"signatureHelp": {
|
||||
"enabled": true,
|
||||
"description": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"completion": {
|
||||
"enabled": true,
|
||||
"matchCase": "firstletter",
|
||||
"maxResults": 25,
|
||||
"guessMethodArguments": true,
|
||||
"lazyResolveTextEdit": {
|
||||
"enabled": true
|
||||
},
|
||||
"postfix": {
|
||||
"enabled": true
|
||||
},
|
||||
"favoriteStaticMembers": [
|
||||
"org.junit.Assert.*",
|
||||
"org.junit.Assume.*",
|
||||
"org.junit.jupiter.api.Assertions.*",
|
||||
"org.junit.jupiter.api.Assumptions.*",
|
||||
"org.junit.jupiter.api.DynamicContainer.*",
|
||||
"org.junit.jupiter.api.DynamicTest.*"
|
||||
],
|
||||
"importOrder": [
|
||||
"#",
|
||||
"java",
|
||||
"javax",
|
||||
"org",
|
||||
"com"
|
||||
]
|
||||
},
|
||||
"references": {
|
||||
"includeAccessors": true,
|
||||
"includeDecompiledSources": true
|
||||
},
|
||||
"codeGeneration": {
|
||||
"toString": {
|
||||
"template": "${object.className}{${member.name()}=${member.value}, ${otherMembers}}"
|
||||
},
|
||||
"insertionLocation": "afterCursor",
|
||||
"useBlocks": true
|
||||
},
|
||||
"implementationsCodeLens": {
|
||||
"enabled": true
|
||||
},
|
||||
"referencesCodeLens": {
|
||||
"enabled": true
|
||||
},
|
||||
"progressReports": {
|
||||
"enabled": false
|
||||
},
|
||||
"saveActions": {
|
||||
"organizeImports": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"python": {
|
||||
"info": "https://github.com/python-lsp/python-lsp-server",
|
||||
"command": "lsp-ws-proxy -- pylsp",
|
||||
"alt-command": "pylsp",
|
||||
"alt-command2": "lsp-ws-proxy --listen 4114 -- pylsp",
|
||||
"alt-command3": "pylsp --ws --port 4114",
|
||||
"socket": "ws://127.0.0.1:9999/python",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=pylsp",
|
||||
"initialization-options": {
|
||||
"pylsp": {
|
||||
"rope": {
|
||||
"ropeFolder": "{user.home}/.config/lsps/ropeproject"
|
||||
},
|
||||
"plugins": {
|
||||
"ruff": {
|
||||
"enabled": true,
|
||||
"extendSelect": ["I"],
|
||||
"lineLength": 80
|
||||
},
|
||||
"pycodestyle": {
|
||||
"enabled": false
|
||||
},
|
||||
"pyflakes": {
|
||||
"enabled": false
|
||||
},
|
||||
"pylint": {
|
||||
"enabled": true
|
||||
},
|
||||
"mccabe": {
|
||||
"enabled": false
|
||||
},
|
||||
"pylsp_rope": {
|
||||
"rename": false
|
||||
},
|
||||
"rope_rename": {
|
||||
"enabled": false
|
||||
},
|
||||
"rope_autoimport": {
|
||||
"enabled": false
|
||||
},
|
||||
"rope_completion": {
|
||||
"enabled": false,
|
||||
"eager": false
|
||||
},
|
||||
"jedi_rename": {
|
||||
"enabled": true
|
||||
},
|
||||
"jedi_completion": {
|
||||
"enabled": true,
|
||||
"include_class_objects": true,
|
||||
"include_function_objects": true,
|
||||
"fuzzy": false
|
||||
},
|
||||
"jedi": {
|
||||
"root_dir": "file://{workspace.folder}",
|
||||
"extra_paths": [
|
||||
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"python - jedi-language-server": {
|
||||
"hidden": true,
|
||||
"info": "https://pypi.org/project/jedi-language-server/",
|
||||
"command": "jedi-language-server",
|
||||
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
|
||||
"socket": "ws://127.0.0.1:9999/python",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
|
||||
"initialization-options": {
|
||||
"jediSettings": {
|
||||
"autoImportModules": [],
|
||||
"caseInsensitiveCompletion": true,
|
||||
"debug": false
|
||||
},
|
||||
"completion": {
|
||||
"disableSnippets": false,
|
||||
"resolveEagerly": false,
|
||||
"ignorePatterns": []
|
||||
},
|
||||
"markupKindPreferred": "markdown",
|
||||
"workspace": {
|
||||
"extraPaths": [
|
||||
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
|
||||
],
|
||||
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
|
||||
"symbols": {
|
||||
"ignoreFolders": [
|
||||
".nox",
|
||||
".tox",
|
||||
".venv",
|
||||
"__pycache__",
|
||||
"venv"
|
||||
],
|
||||
"maxSymbols": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cpp": {
|
||||
"info": "https://clangd.llvm.org/",
|
||||
"command": "lsp-ws-proxy -- clangd",
|
||||
"alt-command": "clangd",
|
||||
"socket": "ws://127.0.0.1:9999/cpp",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=clangd",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"c": {
|
||||
"hidden": true,
|
||||
"info": "https://clangd.llvm.org/",
|
||||
"command": "lsp-ws-proxy -- clangd",
|
||||
"alt-command": "clangd",
|
||||
"socket": "ws://127.0.0.1:9999/c",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=clangd",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"go": {
|
||||
"info": "https://pkg.go.dev/golang.org/x/tools/gopls#section-readme",
|
||||
"command": "lsp-ws-proxy -- gopls",
|
||||
"alt-command": "gopls",
|
||||
"socket": "ws://127.0.0.1:9999/go",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=gopls",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"typescript": {
|
||||
"info": "https://github.com/typescript-language-server/typescript-language-server",
|
||||
"command": "lsp-ws-proxy -- typescript-language-server",
|
||||
"alt-command": "typescript-language-server --stdio",
|
||||
"socket": "ws://127.0.0.1:9999/typescript",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=ts",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"sh": {
|
||||
"info": "",
|
||||
"command": "",
|
||||
"alt-command": "",
|
||||
"socket": "ws://127.0.0.1:9999/bash",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=shell",
|
||||
"initialization-options": {}
|
||||
},
|
||||
"lua": {
|
||||
"info": "https://github.com/LuaLS/lua-language-server",
|
||||
"command": "lsp-ws-proxy -- lua-language-server",
|
||||
"alt-command": "lua-language-server",
|
||||
"socket": "ws://127.0.0.1:9999/lua",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=lua",
|
||||
"initialization-options": {}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
@@ -0,0 +1,10 @@
|
||||
<div class="col">
|
||||
<info-bar></info-bar>
|
||||
<tabs></tabs>
|
||||
<hr class="tabs-bar-underline"/>
|
||||
<editors></editors>
|
||||
<search-replace></search-replace>
|
||||
<markdown-preview></markdown-preview>
|
||||
|
||||
<lsp-manager></lsp-manager>
|
||||
</div>
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
|
||||
import { WebsocketService } from './common/services/websocket.service';
|
||||
|
||||
import { InfoBarComponent } from './editor/info-bar/info-bar.component';
|
||||
import { TabsComponent } from './editor/tabs/tabs.component';
|
||||
import { EditorsComponent } from './editor/editors.component';
|
||||
import { SearchReplaceComponent } from "./editor/search-replace/search-replace.component";
|
||||
import { MarkdownPreviewComponent } from "./editor/markdown-preview/markdown-preview.component";
|
||||
import { LspManagerComponent } from "./editor/lsp-manager/lsp-manager.component";
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [
|
||||
InfoBarComponent,
|
||||
TabsComponent,
|
||||
EditorsComponent,
|
||||
SearchReplaceComponent,
|
||||
MarkdownPreviewComponent,
|
||||
LspManagerComponent,
|
||||
],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
host: {
|
||||
'class': 'row'
|
||||
}
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Newton';
|
||||
|
||||
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!' }");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||
import { provideServerRendering } from '@angular/platform-server';
|
||||
import { appConfig } from './app.config';
|
||||
|
||||
const serverConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideServerRendering(),
|
||||
]
|
||||
};
|
||||
|
||||
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
|
||||
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideClientHydration(withEventReplay())]
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
.modal-column {
|
||||
min-height: 24em;
|
||||
max-height: 24em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
background: rgba(116, 0, 0, 0.64);
|
||||
border-style: solid;
|
||||
border-color: rgba(0, 0, 0, 0.64);
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
background: rgba(256, 0, 0, 0.64);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<div #diffModal
|
||||
id="diffModal" class="modal fade" tabindex="-1" role="dialog"
|
||||
>
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Diff:</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
|
||||
<div class="diff-view"></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
inject
|
||||
} from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
|
||||
import AceDiff from 'ace-diff';
|
||||
|
||||
import 'ace-diff/dist/ace-diff.min.css';
|
||||
import 'ace-diff/dist/ace-diff-dark.min.css';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'diff-modal',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
templateUrl: './diff-modal.component.html',
|
||||
styleUrl: './diff-modal.component.css',
|
||||
host: {
|
||||
'class': ''
|
||||
}
|
||||
})
|
||||
export class DiffModalComponent {
|
||||
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
||||
|
||||
diffModal!: bootstrap.Modal;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
|
||||
private ngAfterViewInit(): void {
|
||||
this.loadDiffView();
|
||||
}
|
||||
|
||||
private loadDiffView() {
|
||||
// Notes: https://github.com/ace-diff/ace-diff
|
||||
// https://ajaxorg.github.io/ace-api-docs/classes/src_ext_diff_diff_view.DiffView.html#scrollB
|
||||
/*
|
||||
const differ = new AceDiff({
|
||||
ace: window.ace
|
||||
element: '.diff-view',
|
||||
left: {
|
||||
content: 'your first file content here',
|
||||
},
|
||||
right: {
|
||||
content: 'your second file content here',
|
||||
},
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
}
|
||||
|
||||
private createModal() {
|
||||
this.diffModal = new bootstrap.Modal("#diffModal", {});
|
||||
}
|
||||
|
||||
public showModal() {
|
||||
this.diffModal?.toggle();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Keybindings } from './keybindings.config';
|
||||
|
||||
export const EditorSettings: any = {
|
||||
// BASE_PATH: 'https:cdnjs.cloudflare.com/ajax/libs/ace/1.40.1/',
|
||||
BASE_PATH: 'ace',
|
||||
KEYBINDINGS: Keybindings,
|
||||
CONFIG: {
|
||||
behavioursEnabled: true,
|
||||
wrapBehavioursEnabled: true,
|
||||
fontSize: "12px",
|
||||
theme: "ace/theme/gruvbox",
|
||||
mode: "ace/mode/text",
|
||||
printMarginColumn: 80,
|
||||
enableCodeLens: true,
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
enableSnippets: true,
|
||||
highlightActiveLine: true,
|
||||
enableMultiselect: true,
|
||||
useSoftTabs: true,
|
||||
tabSize: 4,
|
||||
navigateWithinSoftTabs: true,
|
||||
tooltipFollowsMouse: true,
|
||||
scrollPastEnd: 0.5,
|
||||
mergeUndoDeltas: false,
|
||||
showGutter: true,
|
||||
// customScrollbar: true,
|
||||
scrollSpeed: 5
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
export const Keybindings: Array<{}> = [
|
||||
{
|
||||
name: "quit",
|
||||
bindKey: {win: "ctrl-q", mac: "ctrl-q"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "toggleFullScreen",
|
||||
bindKey: {win: "F11", mac: "F11"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "showKeyShortcuts",
|
||||
bindKey: {win: "ctrl-shift-k", mac: "command-shift-k"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "openCommandPalette2",
|
||||
bindKey: {linux: "command-shift-/|F1", win: "ctrl-shift-/|F1"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "showFilesList",
|
||||
bindKey: {win: "ctrl-b", mac: "ctrl-b"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "lspManagerPopup",
|
||||
bindKey: {win: "ctrl-shift-l", mac: "ctrl-shift-l"},
|
||||
service: "",
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "markdownPreviewPopup",
|
||||
bindKey: {win: "ctrl-shift-m", mac: "ctrl-shift-m"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "searchPopup",
|
||||
bindKey: {win: "ctrl-f", mac: "ctrl-f"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "replacePopup",
|
||||
bindKey: {win: "ctrl-r", mac: "ctrl-r"},
|
||||
readOnly: false
|
||||
}, {
|
||||
|
||||
name: "terminalPopup",
|
||||
bindKey: {win: "ctrl-shift-.", mac: "ctrl-shift-."},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "newFile",
|
||||
bindKey: {win: "ctrl-t", mac: "ctrl-t"},
|
||||
service: "editorsService",
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "openFiles",
|
||||
bindKey: {win: "ctrl-o", mac: "ctrl-o"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "saveFile",
|
||||
bindKey: {win: "ctrl-s", mac: "ctrl-s"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "saveFileAs",
|
||||
bindKey: {win: "ctrl-shift-s", mac: "ctrl-shift-s"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "closeFile",
|
||||
bindKey: {win: "ctrl-w", mac: "ctrl-w"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "selectLeftEditor",
|
||||
bindKey: {win: "ctrl-pageup", mac: "ctrl-pageup"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "selectRightEditor",
|
||||
bindKey: {win: "ctrl-pagedown", mac: "ctrl-pagedown"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "moveSessionLeft",
|
||||
bindKey: {win: "ctrl-shift-up", mac: "ctrl-shift-up"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "moveSessionRight",
|
||||
bindKey: {win: "ctrl-shift-down", mac: "ctrl-shift-down"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "cutToBuffer",
|
||||
bindKey: {win: "ctrl-k", mac: "ctrl-k"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "pasteCutBuffer",
|
||||
bindKey: {win: "ctrl-u", mac: "ctrl-u"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "movelinesUp",
|
||||
bindKey: {win: "ctrl-up", mac: "ctrl-up"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "movelinesDown",
|
||||
bindKey: {win: "ctrl-down", mac: "ctrl-down"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "duplicateLines",
|
||||
bindKey: {win: "ctrl-d", mac: "ctrl-d"},
|
||||
readOnly: false
|
||||
}, {
|
||||
name: "zoomIn",
|
||||
bindKey: {win: "ctrl-=", mac: "ctrl-="},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "zoomOut",
|
||||
bindKey: {win: "ctrl--", mac: "ctrl--"},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "toggleLineHighlight",
|
||||
bindKey: {win: "ctrl-h", mac: "ctrl-h"},
|
||||
readOnly: true
|
||||
}, {
|
||||
name: "gotoDefinition",
|
||||
bindKey: {win: "ctrl-g", mac: "ctrl-g"},
|
||||
readOnly: true
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
export abstract class ButtonMap {
|
||||
static LEFT: number = 0;
|
||||
static MIDDLE: number = 1;
|
||||
static RIGHT: number = 2;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import {
|
||||
Directive,
|
||||
Output,
|
||||
Input,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
HostListener
|
||||
} from '@angular/core';
|
||||
|
||||
import { NewtonFile } from '../types/file.type';
|
||||
|
||||
|
||||
|
||||
@Directive({
|
||||
selector: '[dropzone]'
|
||||
})
|
||||
export class DndDirective {
|
||||
@HostBinding('class.fileover') fileOver!: boolean;
|
||||
@Output() fileDropped: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@HostListener('dragover', ['$event'])
|
||||
onDragOver(evt: any) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
this.fileOver = true;
|
||||
}
|
||||
|
||||
@HostListener('dragleave', ['$event'])
|
||||
public onDragLeave(evt: any) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
this.fileOver = false;
|
||||
}
|
||||
|
||||
@HostListener('drop', ['$event'])
|
||||
publicondrop(evt: any) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
this.fileOver = false;
|
||||
let files: Array<NewtonFile> = evt.dataTransfer.files;
|
||||
|
||||
if (files.length == 0) return;
|
||||
|
||||
this.fileDropped.emit(files);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
Directive,
|
||||
Output,
|
||||
EventEmitter,
|
||||
HostListener
|
||||
} from '@angular/core';
|
||||
|
||||
|
||||
|
||||
@Directive({
|
||||
selector: '[draggable-item]'
|
||||
})
|
||||
export class DraggableDirective {
|
||||
@Output() dragStart: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
@Output() dragMove: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
@Output() dragEnd: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
|
||||
private dragging: boolean = false;
|
||||
selected: any;
|
||||
|
||||
|
||||
@HostListener('pointerdown', ['$event'])
|
||||
onPointerDown(event: PointerEvent): void {
|
||||
console.log("pointerdown");
|
||||
this.dragging = true;
|
||||
|
||||
this.dragStart.emit(event);
|
||||
}
|
||||
|
||||
@HostListener('document:pointermove', ['$event'])
|
||||
onPointerMove(event: PointerEvent): void {
|
||||
if (!this.dragging) return;
|
||||
console.log("pointermove");
|
||||
|
||||
this.dragMove.emit(event);
|
||||
}
|
||||
|
||||
@HostListener('document:pointerup', ['$event'])
|
||||
onPointerUp(event: PointerEvent): void {
|
||||
if (!this.dragging) return;
|
||||
console.log("pointerup");
|
||||
|
||||
this.dragging = false;
|
||||
this.dragEnd.emit(event);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import {
|
||||
Directive,
|
||||
Output,
|
||||
EventEmitter,
|
||||
HostListener
|
||||
} from '@angular/core';
|
||||
|
||||
|
||||
|
||||
@Directive({
|
||||
selector: '[pane-handle]'
|
||||
})
|
||||
export class PaneHandleDirective {
|
||||
@Output() dragStart: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
@Output() dragMove: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
@Output() dragEnd: EventEmitter<PointerEvent> = new EventEmitter();
|
||||
|
||||
private dragging: boolean = false;
|
||||
private isHrPane: boolean = false;
|
||||
private parentElm: any;
|
||||
private leftSiblingElm: any;
|
||||
private rightSiblingElm: any;
|
||||
|
||||
|
||||
@HostListener('pointerdown', ['$event'])
|
||||
onPointerDown(event: PointerEvent): void {
|
||||
event.preventDefault();
|
||||
|
||||
let target = event.target as Element;
|
||||
if (
|
||||
!target.classList.contains("hr-pane-handle") &&
|
||||
!target.classList.contains("vr-pane-handle")
|
||||
) {
|
||||
console.error("Must have 'hr-pane-handle' or 'vr-pane-handle' in classList!");
|
||||
return;
|
||||
}
|
||||
|
||||
target.requestPointerLock();
|
||||
|
||||
this.dragging = true;
|
||||
this.isHrPane = ( target.classList.contains("hr-pane-handle") ) ? true : false ;
|
||||
this.parentElm = target.parentElement as Element;
|
||||
this.leftSiblingElm = target.previousElementSibling as Element;
|
||||
this.rightSiblingElm = target.nextElementSibling as Element;
|
||||
|
||||
this.dragStart.emit(event);
|
||||
}
|
||||
|
||||
@HostListener('pointermove', ['$event'])
|
||||
onPointerMove(event: PointerEvent): void {
|
||||
if (!this.dragging) return;
|
||||
|
||||
let x = event.movementX;
|
||||
let y = event.movementY;
|
||||
let parentBounds = this.parentElm.getBoundingClientRect();
|
||||
let leftBounds = this.leftSiblingElm.getBoundingClientRect();
|
||||
let rightBounds = this.rightSiblingElm.getBoundingClientRect();
|
||||
|
||||
if (this.isHrPane) {
|
||||
this.leftSiblingElm.style.maxHeight = `${leftBounds.height + y}px`;
|
||||
this.rightSiblingElm.style.maxHeight = `${rightBounds.height - y}px`;
|
||||
} else {
|
||||
this.leftSiblingElm.style.maxWidth = `${leftBounds.width + x}px`;
|
||||
this.rightSiblingElm.style.maxWidth = `${rightBounds.width - x}px`;
|
||||
}
|
||||
|
||||
this.dragMove.emit(event);
|
||||
}
|
||||
|
||||
@HostListener('pointerup', ['$event'])
|
||||
onPointerUp(event: PointerEvent): void {
|
||||
if (!this.dragging) return;
|
||||
|
||||
this.dragging = false;
|
||||
this.leftSiblingElm = null;
|
||||
this.rightSiblingElm = null;
|
||||
|
||||
document.exitPointerLock();
|
||||
this.dragEnd.emit(event);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { CodeViewComponent } from "../../../editor/code-view/view.component";
|
||||
|
||||
import { ServiceMessage } from '../../types/service-message.type';
|
||||
import { EditorSettings } from "../../configs/editor.config";
|
||||
|
||||
import { NewtonFile } from '../../types/file.type';
|
||||
import { EditorType } from '../../types/editor.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EditorsService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
|
||||
editors: Map<string, CodeViewComponent> = new Map();
|
||||
editorSettings: typeof EditorSettings = EditorSettings;
|
||||
|
||||
activeEditor: string = "";
|
||||
miniMapView!: CodeViewComponent;
|
||||
|
||||
|
||||
public getEditorsAsArray(): CodeViewComponent[] {
|
||||
return [...this.editors.values()];
|
||||
}
|
||||
|
||||
public get(uuid: string): CodeViewComponent {
|
||||
return this.editors.get(uuid);
|
||||
}
|
||||
|
||||
public set(uuid: string, component: CodeViewComponent) {
|
||||
if (component.mode == EditorType.MiniMap) {
|
||||
this.miniMapView = component;
|
||||
return;
|
||||
}
|
||||
|
||||
this.editors.set(uuid, component);
|
||||
|
||||
if (Array.from(this.editors.keys()).length <= 1) return;
|
||||
|
||||
let _editors = this.getEditorsAsArray();
|
||||
let leftEditor = null;
|
||||
let rightEditor = null;
|
||||
|
||||
for (let i = 0; i < _editors.length; i++) {
|
||||
if (_editors[i].uuid !== uuid) continue;
|
||||
|
||||
leftEditor = _editors[i - 1];
|
||||
rightEditor = _editors[i];
|
||||
|
||||
}
|
||||
|
||||
leftEditor.rightSiblingUUID = rightEditor.uuid;
|
||||
rightEditor.leftSiblingUUID = leftEditor.uuid;
|
||||
}
|
||||
|
||||
public setSession(file: NewtonFile | undefined | null) {
|
||||
if ( !file ) return;
|
||||
|
||||
let editorComponent = this.getActiveEditorComponent();
|
||||
editorComponent.assignSession(file);
|
||||
this.miniMapView.cloneSession(file);
|
||||
}
|
||||
|
||||
public newFile() {
|
||||
let editorComponent = this.getActiveEditorComponent();
|
||||
|
||||
editorComponent.newFile();
|
||||
this.miniMapView.newFile();
|
||||
}
|
||||
|
||||
public getSession() {
|
||||
let editorComponent = this.get(this.activeEditor);
|
||||
let editor = editorComponent.editor;
|
||||
|
||||
return editor.getSession();
|
||||
}
|
||||
|
||||
public async setActiveEditor(activeEditor: string) {
|
||||
this.activeEditor = activeEditor;
|
||||
let editorComponent = this.getActiveEditorComponent();
|
||||
|
||||
if (!this.miniMapView) return;
|
||||
|
||||
if (editorComponent.activeFile) {
|
||||
this.miniMapView.cloneSession(editorComponent.activeFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: likely a new file/buffer
|
||||
this.miniMapView.editor.session.setValue(
|
||||
editorComponent.editor.session.getValue()
|
||||
);
|
||||
}
|
||||
|
||||
public getActiveEditorComponent(): any {
|
||||
return this.get(this.activeEditor);
|
||||
}
|
||||
|
||||
protected getActiveEditor(): any {
|
||||
let editorComponent = this.get(this.activeEditor);
|
||||
let editor = editorComponent.editor;
|
||||
return editor;
|
||||
}
|
||||
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class InfoBarService {
|
||||
private dataSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
private fpathSubject: ReplaySubject<string> = new ReplaySubject(1);
|
||||
private cursorPosSubject: ReplaySubject<any> = new ReplaySubject(1);
|
||||
private encodeingSubject: ReplaySubject<string> = new ReplaySubject(1);
|
||||
private ftypeSubject: ReplaySubject<string> = new ReplaySubject(1);
|
||||
|
||||
|
||||
public setData(data: ServiceMessage): void {
|
||||
this.dataSubject.next(data);
|
||||
}
|
||||
|
||||
public getData$(): Observable<ServiceMessage> {
|
||||
return this.dataSubject.asObservable();
|
||||
}
|
||||
|
||||
public setInfoBarFPath(data: string): void {
|
||||
this.fpathSubject.next(data);
|
||||
}
|
||||
|
||||
public updateInfoBarFPath$(): Observable<string> {
|
||||
return this.fpathSubject.asObservable();
|
||||
}
|
||||
|
||||
public setInfoBarCursorPos(data: any): void {
|
||||
this.cursorPosSubject.next(data);
|
||||
}
|
||||
|
||||
public updateInfoBarCursorPos$(): Observable<any> {
|
||||
return this.cursorPosSubject.asObservable();
|
||||
}
|
||||
|
||||
public setInfoBarEncodeing(data: string): void {
|
||||
this.encodeingSubject.next(data);
|
||||
}
|
||||
|
||||
public updateInfoBarEncodeing$(): Observable<string> {
|
||||
return this.encodeingSubject.asObservable();
|
||||
}
|
||||
|
||||
public setInfoBarFType(data: string): void {
|
||||
this.ftypeSubject.next(data);
|
||||
}
|
||||
|
||||
public updateInfoBarFType$(): Observable<string> {
|
||||
return this.ftypeSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { CompletionProvider } from "ace-builds/src-min-noconflict/ace";
|
||||
import { CommandBarTooltip } from "ace-builds/src-min-noconflict/ext-command_bar";
|
||||
import { InlineAutocomplete } from "ace-builds/src-min-noconflict/ext-inline_autocomplete";
|
||||
|
||||
import { AceLanguageClient, LanguageClientConfig } from 'ace-linters/build/ace-language-client';
|
||||
import { LanguageProvider } from "ace-linters";
|
||||
|
||||
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LspManagerService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
|
||||
workspaceFolder: string = "";
|
||||
lspConfigDataStr: string = "";
|
||||
languageProviders: {} = {};
|
||||
|
||||
|
||||
public loadLspConfigData(): Promise<string | void> {
|
||||
return this.getLspConfigData().then((lspConfigData: string) => {
|
||||
this.lspConfigDataStr = lspConfigData;
|
||||
return lspConfigData;
|
||||
});
|
||||
}
|
||||
|
||||
public registerEditorToLSPClient(editor: any) {
|
||||
let mode = this.getMode(editor.session);
|
||||
|
||||
this.languageProviders[mode]?.registerEditor(
|
||||
editor,
|
||||
editor.session.lspConfig
|
||||
);
|
||||
}
|
||||
|
||||
private getLspConfigData(): Promise<string> {
|
||||
return window?.fs.getLspConfigData();
|
||||
}
|
||||
|
||||
private parseAndReturnLSPConfigData(): {} {
|
||||
let configData = JSON.parse(
|
||||
this.lspConfigDataStr.replaceAll("{workspace.folder}", this.workspaceFolder)
|
||||
);
|
||||
|
||||
if (configData["message"]) {
|
||||
console.warn(
|
||||
"Warning: LSP this.lspConfigDataStr is a 'message'",
|
||||
this.lspConfigDataStr
|
||||
);
|
||||
|
||||
configData = {};
|
||||
}
|
||||
|
||||
return configData;
|
||||
}
|
||||
|
||||
private getInitializationOptions(mode: string, configData: {}): {} {
|
||||
let initializationOptions = {};
|
||||
|
||||
if ( Object.keys(configData).length !== 0 && configData[mode] ) {
|
||||
initializationOptions = configData[mode]["initialization-options"];
|
||||
}
|
||||
|
||||
return initializationOptions;
|
||||
}
|
||||
|
||||
public createLanguageProviderWithClientServer(mode: string): LanguageProvider {
|
||||
if ( this.languageProviders[mode] ) return;
|
||||
let servers: LanguageClientConfig[] = [];
|
||||
|
||||
try {
|
||||
let lspConfigData = this.parseAndReturnLSPConfigData();
|
||||
let initializationOptions = this.getInitializationOptions(mode, lspConfigData);
|
||||
servers = [
|
||||
{
|
||||
module: () => import("ace-linters/build/language-client"),
|
||||
modes: mode,
|
||||
type: "socket",
|
||||
socket: new WebSocket( lspConfigData[mode]["socket"] ),
|
||||
initializationOptions: initializationOptions
|
||||
}
|
||||
];
|
||||
} catch(error) {
|
||||
console.error(
|
||||
"Error: Language Server could not be loaded OR doesn't exist in Newton-LSP config setup...",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.languageProviders[mode] = AceLanguageClient.for(
|
||||
servers,
|
||||
{
|
||||
workspacePath: this.workspaceFolder,
|
||||
functionality: {
|
||||
hover: true,
|
||||
completion: {
|
||||
overwriteCompleters: true,
|
||||
lspCompleterOptions: {
|
||||
triggerCharacters: {
|
||||
add: [
|
||||
" ",
|
||||
".",
|
||||
"@"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
// inlineCompletion: {
|
||||
// overwriteCompleters: true
|
||||
// },
|
||||
completionResolve: true,
|
||||
format: true,
|
||||
documentHighlights: true,
|
||||
signatureHelp: true,
|
||||
semanticTokens: true,
|
||||
codeActions: true
|
||||
},
|
||||
// aceComponents: {
|
||||
// InlineAutocomplete,
|
||||
// CommandBarTooltip,
|
||||
// CompletionProvider
|
||||
// },
|
||||
manualSessionControl: true
|
||||
}
|
||||
);
|
||||
|
||||
return this.languageProviders[mode];
|
||||
}
|
||||
|
||||
public closeLanguageProviderWithClientServer(mode: string): LanguageProvider {
|
||||
if ( !this.languageProviders[mode] ) return;
|
||||
|
||||
let connection = this.languageProviders[mode];
|
||||
delete this.languageProviders[mode];
|
||||
connection.closeConnection();
|
||||
}
|
||||
|
||||
private getLanguageProviderWithWebWorker(): LanguageProvider {
|
||||
let worker = new Worker(new URL('./webworker.js', import.meta.url));
|
||||
return LanguageProvider.create(worker);
|
||||
}
|
||||
|
||||
public registerSession(editor: any) {
|
||||
let mode = this.getMode(editor.session);
|
||||
if ( !this.languageProviders[mode] ) return;
|
||||
|
||||
this.languageProviders[mode].registerSession(
|
||||
editor.session,
|
||||
editor,
|
||||
editor.session.lspConfig
|
||||
);
|
||||
}
|
||||
|
||||
public getMode(session: any): string {
|
||||
return session.getMode()["$id"].replace("ace/mode/", "");
|
||||
}
|
||||
|
||||
public closeDocument(session: any) {
|
||||
if ( !session ) return;
|
||||
let mode = this.getMode(session);
|
||||
if ( !this.languageProviders[mode] ) return;
|
||||
this.languageProviders[mode].closeDocument(session);
|
||||
}
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import { ServiceManager } from "ace-linters/build/service-manager";
|
||||
|
||||
let manager = new ServiceManager(self);
|
||||
|
||||
manager.registerService("html", {
|
||||
features: {signatureHelp: false},
|
||||
module: () => import("ace-linters/build/html-service"),
|
||||
className: "HtmlService",
|
||||
modes: "html"
|
||||
});
|
||||
|
||||
manager.registerService("css", {
|
||||
features: {signatureHelp: false},
|
||||
module: () => import("ace-linters/build/css-service"),
|
||||
className: "CssService",
|
||||
modes: "css"
|
||||
});
|
||||
|
||||
manager.registerService("less", {
|
||||
features: {signatureHelp: false},
|
||||
module: () => import("ace-linters/build/css-service"),
|
||||
className: "CssService",
|
||||
modes: "less"
|
||||
});
|
||||
|
||||
manager.registerService("scss", {
|
||||
features: {signatureHelp: false},
|
||||
module: () => import("ace-linters/build/css-service"),
|
||||
className: "CssService",
|
||||
modes: "scss"
|
||||
});
|
||||
|
||||
manager.registerService("json", {
|
||||
features: {signatureHelp: false, documentHighlight: false},
|
||||
module: () => import("ace-linters/build/json-service"),
|
||||
className: "JsonService",
|
||||
modes: "json",
|
||||
});
|
||||
|
||||
manager.registerService("json5", {
|
||||
features: {signatureHelp: false, documentHighlight: false},
|
||||
module: () => import("ace-linters/build/json-service"),
|
||||
className: "JsonService",
|
||||
modes: "json5",
|
||||
});
|
||||
|
||||
manager.registerService("typescript", {
|
||||
module: () => import("ace-linters/build/typescript-service"),
|
||||
className: "TypescriptService",
|
||||
modes: "typescript|tsx|javascript|jsx",
|
||||
});
|
||||
|
||||
manager.registerService("yaml", {
|
||||
features: {signatureHelp: false, documentHighlight: false},
|
||||
module: () => import("ace-linters/build/yaml-service"),
|
||||
className: "YamlService",
|
||||
modes: "yaml",
|
||||
});
|
||||
|
||||
manager.registerService("xml", {
|
||||
features: {completion: false, completionResolve: false, diagnostics: true, format: false, hover: false, documentHighlight: false, signatureHelp: false},
|
||||
module: () => import("ace-linters/build/xml-service"),
|
||||
className: "XmlService",
|
||||
modes: "xml",
|
||||
});
|
||||
|
||||
manager.registerService("php", {
|
||||
features: {completion: false, completionResolve: false, diagnostics: true, format: false, hover: false, documentHighlight: false, signatureHelp: false},
|
||||
module: () => import("ace-linters/build/php-service"),
|
||||
className: "PhpService",
|
||||
modes: "php"
|
||||
});
|
||||
|
||||
manager.registerService("javascript", {
|
||||
features: {completion: false, completionResolve: false, diagnostics: true, format: false, hover: false, documentHighlight: false, signatureHelp: false},
|
||||
module: () => import("ace-linters/build/javascript-service"),
|
||||
className: "JavascriptService",
|
||||
modes: "javascript",
|
||||
});
|
||||
|
||||
|
||||
manager.registerServer("pythonls", {
|
||||
module: () => import("ace-linters/build/language-client"),
|
||||
modes: "python",
|
||||
type: "socket",
|
||||
socket: new WebSocket("ws://127.0.0.1:9999/?name=pylsp")
|
||||
});
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
|
||||
manager.registerService("clang", {
|
||||
module: () => import("ace-clang-linter/build/ace-clang-linter"),
|
||||
className: "AceClangLinter",
|
||||
modes: "c_cpp",
|
||||
});
|
||||
|
||||
manager.registerService("lua", {
|
||||
features: {completion: false, completionResolve: false, diagnostics: true, format: true, hover: false, documentHighlight: false, signatureHelp: false},
|
||||
module: () => import("ace-lua-linter/build/ace-lua-linter"),
|
||||
className: "AceLuaLinter",
|
||||
modes: "lua",
|
||||
});
|
||||
|
||||
manager.registerService("mysql", {
|
||||
module: () => import("ace-sql-linter/build/mysql-service"),
|
||||
className: "MySQLService",
|
||||
modes: "mysql",
|
||||
});
|
||||
|
||||
manager.registerService("zig", {
|
||||
module: () => import("ace-zig-linter/build/ace-zig-linter"),
|
||||
className: "AceZigLinter",
|
||||
modes: "zig",
|
||||
});
|
||||
|
||||
manager.registerService("python", {
|
||||
features: {completion: false, completionResolve: false, diagnostics: true, format: true, hover: false, documentHighlight: false, signatureHelp: false},
|
||||
module: () => import("ace-python-ruff-linter/build/python-service"),
|
||||
className: "PythonService",
|
||||
modes: "python",
|
||||
});
|
||||
|
||||
manager.registerServer("svelte", {
|
||||
module: () => import("ace-linters/build/language-client"),
|
||||
modes: "html",
|
||||
type: "socket",
|
||||
socket: new WebSocket("ws://127.0.0.1:3030/svelte")
|
||||
});
|
||||
|
||||
manager.registerServer("astro", {
|
||||
module: () => import("ace-linters/build/language-client"),
|
||||
modes: "astro",
|
||||
type: "socket",
|
||||
socket: new WebSocket("ws://127.0.0.1:3030/astro"),
|
||||
initializationOptions: {
|
||||
typescript: {
|
||||
tsdk: "node_modules/typescript/lib", //path to typescript server
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
manager.registerServer("go", {
|
||||
module: () => import("ace-linters/build/language-client"),
|
||||
modes: "golang",
|
||||
type: "socket",
|
||||
socket: new WebSocket("ws://127.0.0.1:3030/go")
|
||||
});
|
||||
|
||||
manager.registerService("dart", {
|
||||
module: () => import("ace-dart-linter/build/ace-dart-linter"),
|
||||
className: "AceDartLinter",
|
||||
modes: "dart",
|
||||
});
|
||||
|
||||
manager.registerService("golang", {
|
||||
module: () => import("ace-go-linter/build/ace-go-linter"),
|
||||
className: "AceGoLinter",
|
||||
modes: "go",
|
||||
});
|
||||
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MarkdownPreviewService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FilesModalService {
|
||||
private showFilesModalSubject: ReplaySubject<null> = new ReplaySubject<null>(1);
|
||||
private addFileSubject: ReplaySubject<string> = new ReplaySubject<string>(1);
|
||||
|
||||
|
||||
public showFilesModal(): void {
|
||||
this.showFilesModalSubject.next(null);
|
||||
}
|
||||
|
||||
public showFilesModalRequested$(): Observable<null> {
|
||||
return this.showFilesModalSubject.asObservable();
|
||||
}
|
||||
|
||||
public addFileToModal(data: string): void {
|
||||
this.addFileSubject.next(data);
|
||||
}
|
||||
|
||||
public addFileToModalRequested$(): Observable<string> {
|
||||
return this.addFileSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SearchReplaceService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject<ServiceMessage>(1);
|
||||
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { EditorsService } from '../editors.service';
|
||||
|
||||
import { ServiceMessage } from '../../../types/service-message.type';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TabsService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
|
||||
private editorsService: EditorsService = inject(EditorsService);
|
||||
|
||||
tabs: any[] = [];
|
||||
newIndex: number = -1;
|
||||
|
||||
|
||||
public push(tabData: {}): void {
|
||||
this.tabs.push(tabData);
|
||||
}
|
||||
|
||||
public closeTab(fpath: string): void {
|
||||
this.sendEditorsServiceAMessage("close-tab", fpath);
|
||||
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i].path == fpath) {
|
||||
this.splice(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sendEditorsServiceAMessage(action: string, fpath: string) {
|
||||
let message = new ServiceMessage();
|
||||
message.action = action;
|
||||
message.filePath = fpath;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public getLeftSiblingTab(fpath: string): string {
|
||||
if (this.tabs.length === 0 ) return;
|
||||
|
||||
let i = this.tabs.indexOf(fpath);
|
||||
|
||||
(i === 0) ? i = this.tabs.length - 1 : i -= 1;
|
||||
return this.tabs[i].path;
|
||||
}
|
||||
|
||||
public getRightSiblingTab(fpath: string): string {
|
||||
if (this.tabs.length === 0 ) return;
|
||||
|
||||
let i = this.tabs.indexOf(fpath);
|
||||
|
||||
(i === (this.tabs.length - 1)) ? i = 0 : i += 1;
|
||||
return this.tabs[i].path;
|
||||
}
|
||||
|
||||
public setNewTargetIndex(fpath: string): void {
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i].path == fpath) {
|
||||
this.newIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public move(oldIndex: number): void {
|
||||
if (this.newIndex == -1) return;
|
||||
|
||||
moveItemInArray(this.tabs, oldIndex, this.newIndex);
|
||||
this.newIndex = -1;
|
||||
|
||||
// event.currentIndex not updating for some reason...
|
||||
// moveItemInArray(this.tabs, event.previousIndex, event.currentIndex);
|
||||
}
|
||||
|
||||
public splice(index: number): void {
|
||||
this.tabs.splice(index, 1);
|
||||
}
|
||||
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { ReplaySubject, Observable } from 'rxjs';
|
||||
|
||||
import { EditSession, UndoManager } from 'ace-builds';
|
||||
import { getModeForPath } from 'ace-builds/src-noconflict/ext-modelist';
|
||||
|
||||
import { TabsService } from './editor/tabs/tabs.service';
|
||||
import { ColorTokenizerService } from './color-tokenizer.service';
|
||||
|
||||
import { NewtonFile } from '../types/file.type';
|
||||
import { ServiceMessage } from '../types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FilesService {
|
||||
private messageSubject: ReplaySubject<ServiceMessage> = new ReplaySubject(1);
|
||||
|
||||
private tabsService: TabsService = inject(TabsService);
|
||||
|
||||
files: Map<string, NewtonFile> = new Map();
|
||||
|
||||
|
||||
public get(path: string): NewtonFile {
|
||||
return this.files.get(path);
|
||||
}
|
||||
|
||||
public getAllPaths(): string[] {
|
||||
return [...this.files.keys()];
|
||||
}
|
||||
|
||||
public getAllFiles(): NewtonFile[] {
|
||||
return [...this.files.values()];
|
||||
}
|
||||
|
||||
public getPreviousFile(path: string): NewtonFile {
|
||||
let paths = this.getAllPaths();
|
||||
if (paths.length === 0 ) return;
|
||||
|
||||
let i = paths.indexOf(path);
|
||||
(i === 0) ? i = paths.length - 1 : i -= 1;
|
||||
return this.files.get( paths[i] );
|
||||
}
|
||||
|
||||
public getNextFile(path: string): NewtonFile {
|
||||
let paths = this.getAllPaths();
|
||||
if (paths.length === 0 ) return;
|
||||
|
||||
let i = paths.indexOf(path);
|
||||
(i === (paths.length - 1)) ? i = 0 : i += 1;
|
||||
return this.files.get( paths[i] );
|
||||
}
|
||||
|
||||
public unset(file: NewtonFile) {
|
||||
file.session.destroy();
|
||||
window?.fs.closeFile(file.path);
|
||||
this.files.delete(file.path);
|
||||
}
|
||||
|
||||
public set(file: NewtonFile) {
|
||||
this.files.set(file.path, file);
|
||||
}
|
||||
|
||||
public sendMessage(data: ServiceMessage): void {
|
||||
this.messageSubject.next(data);
|
||||
}
|
||||
|
||||
public getMessage$(): Observable<ServiceMessage> {
|
||||
return this.messageSubject.asObservable();
|
||||
}
|
||||
|
||||
public async loadFilesList(
|
||||
files: Array<NewtonFile>
|
||||
): Promise<NewtonFile | undefined | null> {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const path = window?.fs.getPathForFile(file);
|
||||
|
||||
if (!file || !path) continue;
|
||||
if ( this.files.get(path) ) continue;
|
||||
|
||||
await this.addFile(path, file);
|
||||
this.addTab(file);
|
||||
}
|
||||
|
||||
return files[ files.length - 1 ];
|
||||
}
|
||||
|
||||
public async addFile(
|
||||
path: string,
|
||||
file: NewtonFile,
|
||||
loadFileContents: boolean = true,
|
||||
data: string = ""
|
||||
): Promise<void> {
|
||||
try {
|
||||
let pathParts = path.split("/");
|
||||
file.fname = pathParts[ pathParts.length - 1 ];
|
||||
file.path = path;
|
||||
file.hash = btoa(file.path);
|
||||
|
||||
if (loadFileContents)
|
||||
data = await window?.fs.getFileContents(file.path);
|
||||
|
||||
file.session = new EditSession(data);
|
||||
file.session["id"] = path;
|
||||
file.session.setUndoManager( new UndoManager() );
|
||||
file.session.setMode( getModeForPath( file.path ).mode );
|
||||
file.session["lspConfig"] = {
|
||||
filePath: path,
|
||||
joinWorkspaceURI: false
|
||||
}
|
||||
file.session["colorTokenizer"] = new ColorTokenizerService();
|
||||
file.session["colorTokenizer"].parse(data);
|
||||
|
||||
this.files.set(file.path, file);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`---- Error ----\nPath: ${path}\nMessage: ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async addTab(file: NewtonFile) {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "create-tab";
|
||||
message.fileName = file.fname;
|
||||
message.fileUUID = file.hash;
|
||||
message.filePath = file.path;
|
||||
|
||||
this.tabsService.sendMessage(message);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export abstract class EditorType {
|
||||
static MiniMap: string = "mini-map";
|
||||
static ReadOnly: string = "read-only";
|
||||
static Standalone: string = "standalone";
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { EditSession } from 'ace-builds';
|
||||
|
||||
|
||||
export interface NewtonFile extends File {
|
||||
fname?: string,
|
||||
path?: string,
|
||||
hash?: string,
|
||||
session?: EditSession,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export class ServiceMessage {
|
||||
action: string = "";
|
||||
message: string = "";
|
||||
editorUUID: string;
|
||||
fileName: string;
|
||||
fileUUID: string;
|
||||
filePath: string;
|
||||
rawData: any;
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
import {
|
||||
Directive,
|
||||
ElementRef,
|
||||
Input,
|
||||
ViewChild,
|
||||
inject
|
||||
} from '@angular/core';
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.service';
|
||||
import { FilesModalService } from '../../common/services/editor/modals/files-modal.service';
|
||||
import { TabsService } from '../../common/services/editor/tabs/tabs.service';
|
||||
import { EditorsService } from '../../common/services/editor/editors.service';
|
||||
import { FilesService } from '../../common/services/files.service';
|
||||
import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service';
|
||||
import { MarkdownPreviewService } from '../../common/services/editor/markdown-preview/markdown-preview.service';
|
||||
import { LspManagerService } from '../../common/services/editor/lsp-manager/lsp-manager.service';
|
||||
|
||||
import { EditorSettings } from "../../common/configs/editor.config";
|
||||
import { NewtonFile } from '../../common/types/file.type';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Directive()
|
||||
export class CodeViewBase {
|
||||
public uuid: string = uuid.v4();
|
||||
@Input() public isDefault: boolean = false;
|
||||
@Input() public mode: string = "";
|
||||
public leftSiblingUUID!: string;
|
||||
public rightSiblingUUID!: string;
|
||||
|
||||
protected infoBarService: InfoBarService = inject(InfoBarService);
|
||||
protected filesModalService: FilesModalService = inject(FilesModalService);
|
||||
protected tabsService: TabsService = inject(TabsService);
|
||||
protected editorsService: EditorsService = inject(EditorsService);
|
||||
protected filesService: FilesService = inject(FilesService);
|
||||
protected searchReplaceService: SearchReplaceService = inject(SearchReplaceService);
|
||||
protected markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService);
|
||||
protected lspManagerService: LspManagerService = inject(LspManagerService);
|
||||
|
||||
@ViewChild('editor') editorElm!: ElementRef;
|
||||
@Input() editorSettings!: typeof EditorSettings;
|
||||
|
||||
public editor!: any;
|
||||
public aceApi!: any;
|
||||
public activeFile!: NewtonFile;
|
||||
|
||||
public cutBuffer: string = "";
|
||||
public timerId: number = -1;
|
||||
public debounceId: number = -1;
|
||||
public debounceWait: number = 800;
|
||||
|
||||
@ViewChild('contextMenu') contextMenu!: ElementRef;
|
||||
public showContextMenu: boolean = false;
|
||||
|
||||
|
||||
public selectLeftEditor() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "select-left-editor";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public selectRightEditor() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "select-right-editor";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public moveSessionLeft() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "move-session-left";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public moveSessionRight() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "move-session-right";
|
||||
message.editorUUID = this.uuid;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
}
|
||||
|
||||
public addActiveStyling() {
|
||||
this.editorElm.nativeElement.classList.add("active-editor")
|
||||
}
|
||||
|
||||
public removeActiveStyling() {
|
||||
this.editorElm.nativeElement.classList.remove("active-editor")
|
||||
}
|
||||
|
||||
public openCommandPalette2() {
|
||||
this.editor.execCommand("openCommandPalette");
|
||||
}
|
||||
|
||||
public showKeyShortcuts() {
|
||||
this.editor.showKeyboardShortcuts();
|
||||
}
|
||||
|
||||
public lspManagerPopup() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-lsp-manager";
|
||||
this.lspManagerService.sendMessage(message);
|
||||
}
|
||||
|
||||
public markdownPreviewPopup() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-markdown-preview";
|
||||
this.markdownPreviewService.sendMessage(message);
|
||||
}
|
||||
|
||||
public searchPopup() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-search-replace";
|
||||
this.searchReplaceService.sendMessage(message);
|
||||
|
||||
// this.editor.execCommand("find");
|
||||
}
|
||||
|
||||
public replacePopup() {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "toggle-search-replace";
|
||||
this.searchReplaceService.sendMessage(message);
|
||||
|
||||
// this.editor.execCommand("replace");
|
||||
}
|
||||
|
||||
public showFilesList() {
|
||||
let paths = this.filesService.getAllPaths();
|
||||
let stubPaths = [];
|
||||
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
let fpath = paths[i];
|
||||
if (fpath.length > 67) {
|
||||
fpath = "..." + fpath.slice(fpath.length - 67, fpath.length);
|
||||
}
|
||||
stubPaths.push(fpath);
|
||||
}
|
||||
|
||||
this.editor.prompt("",
|
||||
{
|
||||
name: "Files:",
|
||||
placeholder: "Search...",
|
||||
getCompletions: (search) => {
|
||||
let query = search.getValue();
|
||||
let result = [];
|
||||
|
||||
if (!query) return stubPaths;
|
||||
|
||||
for (let i = 0; i < stubPaths.length; i++) {
|
||||
if (stubPaths[i].includes(query)) {
|
||||
result.push(stubPaths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
onAccept: (data) => {
|
||||
let fpath = (data.value) ? data.value : data.item;
|
||||
let path = "";
|
||||
|
||||
for (let i = 0; i < stubPaths.length; i++) {
|
||||
if (stubPaths[i] === fpath) {
|
||||
path = paths[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!path) return;
|
||||
|
||||
this.editorsService.setSession(
|
||||
this.filesService.get(path)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public closeFile() {
|
||||
this.tabsService.closeTab(this.activeFile.path);
|
||||
}
|
||||
|
||||
public toggleFullScreen() {
|
||||
window?.main.toggleFullScreen();
|
||||
}
|
||||
|
||||
public setAsReadOnly() {
|
||||
this.editor.setReadOnly(true);
|
||||
this.editor.setShowPrintMargin(false);
|
||||
}
|
||||
|
||||
public setAsMiniMapView() {
|
||||
this.editor.renderer.showLineNumbers = false;
|
||||
this.editor.renderer.setShowGutter(false);
|
||||
|
||||
this.editor.setReadOnly(true);
|
||||
this.editor.setFontSize(2);
|
||||
this.editor.setHighlightActiveLine(false);
|
||||
this.editor.setHighlightGutterLine(false);
|
||||
this.editor.setShowFoldWidgets(false);
|
||||
this.editor.setShowPrintMargin(false);
|
||||
this.editor.session.setUseWrapMode(true);
|
||||
|
||||
this.editorElm.nativeElement.parentElement.classList.add("col-1");
|
||||
this.editorElm.nativeElement.parentElement.classList.add("zero-margin-padding");
|
||||
|
||||
this.editor.on("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let editorComponent = this.editorsService.getActiveEditorComponent();
|
||||
let pos = this.editor.getCursorPosition();
|
||||
|
||||
editorComponent.editor.moveCursorToPosition(pos);
|
||||
editorComponent.editor.clearSelection();
|
||||
editorComponent.editor.renderer.scrollCursorIntoView(null, 0.5);
|
||||
});
|
||||
|
||||
this.editor.on("mousewheel", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let editorComponent = this.editorsService.getActiveEditorComponent();
|
||||
editorComponent.editor.renderer.scrollBy(null, event.domEvent.deltaY);
|
||||
});
|
||||
}
|
||||
|
||||
public zoomIn() {
|
||||
this.editor.setFontSize(
|
||||
parseInt(this.editor.getFontSize()) + 1
|
||||
);
|
||||
}
|
||||
|
||||
public zoomOut() {
|
||||
this.editor.setFontSize(
|
||||
parseInt(this.editor.getFontSize()) - 1
|
||||
);
|
||||
}
|
||||
|
||||
public movelinesUp() {
|
||||
this.editor.execCommand("movelinesup");
|
||||
}
|
||||
|
||||
public movelinesDown() {
|
||||
this.editor.execCommand("movelinesdown");
|
||||
}
|
||||
|
||||
public duplicateLines() {
|
||||
this.editor.execCommand("copylinesdown");
|
||||
}
|
||||
|
||||
public cutText() {
|
||||
let cutText = this.editor.getSelectedText();
|
||||
this.editor.remove();
|
||||
navigator.clipboard.writeText(cutText).catch(() => {
|
||||
console.error("Unable to cut text...");
|
||||
});
|
||||
}
|
||||
|
||||
public copyText() {
|
||||
let copyText = this.editor.getSelectedText();
|
||||
navigator.clipboard.writeText(copyText).catch(() => {
|
||||
console.error("Unable to copy text...");
|
||||
});
|
||||
}
|
||||
|
||||
public pasteText() {
|
||||
navigator.clipboard.readText().then((pasteText) => {
|
||||
this.editor.insert(pasteText, true);
|
||||
});
|
||||
}
|
||||
|
||||
protected updateInfoBar() {
|
||||
this.infoBarService.setInfoBarFPath(this.activeFile?.path)
|
||||
this.infoBarService.setInfoBarCursorPos(
|
||||
this.editor.getCursorPosition()
|
||||
);
|
||||
this.infoBarService.setInfoBarFType(
|
||||
this.editor.session.getMode()["$id"]
|
||||
);
|
||||
}
|
||||
|
||||
public newFile() {
|
||||
this.activeFile = null;
|
||||
let session = this.aceApi.createEditSession([""]);
|
||||
this.editor.setSession(session);
|
||||
}
|
||||
|
||||
protected openFiles() {
|
||||
let startDir = "";
|
||||
if (this.activeFile) {
|
||||
let pathParts = this.activeFile.path.split("/");
|
||||
pathParts.pop();
|
||||
startDir = pathParts.join( '/' );
|
||||
}
|
||||
|
||||
window?.fs.openFiles(startDir);
|
||||
}
|
||||
|
||||
protected saveFile() {
|
||||
if (!this.activeFile) {
|
||||
this.saveFileAs();
|
||||
this.activeFile.session.getUndoManager().markClean();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const text = this.activeFile.session.getValue();
|
||||
window?.fs.saveFile(this.activeFile.path, text);
|
||||
this.activeFile.session.getUndoManager().markClean();
|
||||
}
|
||||
|
||||
protected saveFileAs() {
|
||||
window?.fs.saveFileAs().then((path: string) => {
|
||||
if (!path) return;
|
||||
|
||||
let file: NewtonFile = new File([""], path, {});
|
||||
const text = this.editor.session.getValue();
|
||||
|
||||
window?.fs.saveFile(path, text);
|
||||
this.filesService.addFile(
|
||||
path,
|
||||
file,
|
||||
false,
|
||||
text
|
||||
).then(() => {
|
||||
this.activeFile = this.filesService.get(path);
|
||||
this.editor.setSession(this.activeFile.session);
|
||||
this.filesService.addTab(this.activeFile);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
protected cutToBuffer() {
|
||||
if (this.timerId) { clearTimeout(this.timerId); }
|
||||
|
||||
const cursorPosition = this.editor.getCursorPosition();
|
||||
let lineText = this.editor.session.getLine(cursorPosition.row);
|
||||
this.cutBuffer += `${lineText}\n`;
|
||||
|
||||
this.editor.session.removeFullLines(cursorPosition.row, cursorPosition.row)
|
||||
this.setBufferClearTimeout();
|
||||
}
|
||||
|
||||
protected pasteCutBuffer() {
|
||||
if (this.timerId) { clearTimeout(this.timerId); }
|
||||
|
||||
this.editor.insert(this.cutBuffer, true);
|
||||
this.setBufferClearTimeout();
|
||||
}
|
||||
|
||||
private setBufferClearTimeout(timeout: number = 5000) {
|
||||
this.timerId = setTimeout(() => {
|
||||
this.cutBuffer = "";
|
||||
this.timerId = -1;
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
private quit() {
|
||||
window?.main.quit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
.editor {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.active-editor {
|
||||
border-style: solid;
|
||||
border-width: thin;
|
||||
border-color: rgba(124, 124, 124, 1);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="editor" #editor >
|
||||
</div>
|
||||
|
||||
<ul #contextMenu
|
||||
class="contextMenu"
|
||||
[hidden]="!showContextMenu"
|
||||
(blur)="hideContextMenu()"
|
||||
(click)="contextMenuClicked($event)"
|
||||
>
|
||||
<li command="cutText">Cut</li>
|
||||
<li command="copyText">Copy</li>
|
||||
<li command="pasteText">Paste</li>
|
||||
<hr/>
|
||||
<li command="prettyJSON">Prettify JSON</li>
|
||||
</ul>
|
||||
@@ -0,0 +1,310 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
// Import Ace and its modes/themes so that `ace` global is defined
|
||||
import * as ace from "ace-builds/src-min-noconflict/ace";
|
||||
|
||||
// Note: https://github.com/mkslanc/ace-linters/blob/c286d85c558530aa1b0597d02108bc782abd4736/packages/demo/file-api-websockets/client.ts#L27
|
||||
// import { AceLayout, Box, TabManager, Button, dom, AceTreeWrapper, FileSystemWeb, Pane, AceEditor, Tab } from "ace-layout";
|
||||
|
||||
import "ace-builds/src-min-noconflict/ext-settings_menu";
|
||||
import "ace-builds/src-min-noconflict/ext-keybinding_menu";
|
||||
import "ace-builds/src-min-noconflict/ext-command_bar";
|
||||
import "ace-builds/src-min-noconflict/ext-prompt";
|
||||
import "ace-builds/src-min-noconflict/ext-code_lens";
|
||||
// import "ace-builds/src-min-noconflict/ext-searchbox";
|
||||
import "ace-builds/src-min-noconflict/ext-language_tools";
|
||||
// import "ace-builds/src-min-noconflict/theme-one_dark";
|
||||
// import "ace-builds/src-min-noconflict/theme-penguins_in_space";
|
||||
import "ace-builds/src-min-noconflict/theme-gruvbox";
|
||||
|
||||
// https://www.npmjs.com/package/web-tree-sitter
|
||||
// import { Language, Parser } from 'web-tree-sitter';
|
||||
|
||||
import { CodeViewBase } from './view.base';
|
||||
|
||||
import { NewtonFile } from '../../common/types/file.type';
|
||||
import { EditorType } from '../../common/types/editor.type';
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
import { ButtonMap } from '../../common/constants/button.map';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'code-view',
|
||||
standalone: true,
|
||||
imports: [
|
||||
],
|
||||
templateUrl: './view.component.html',
|
||||
styleUrl: './view.component.css',
|
||||
host: {
|
||||
'class': 'col zero-margin-padding scroller'
|
||||
}
|
||||
})
|
||||
export class CodeViewComponent extends CodeViewBase {
|
||||
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// const { Parser } = window.TreeSitter;
|
||||
// const { Parser } = TreeSitter;
|
||||
// console.log(treeSitter);
|
||||
|
||||
// treeSitter.Parser.init().then(() => {
|
||||
// console.log("Parser ready...");
|
||||
// });
|
||||
|
||||
// const parser = new Parser();
|
||||
// const JavaScript = await Language.load('/path/to/tree-sitter-javascript.wasm');
|
||||
// Language.load('resources/wasm/tree-sitter-javascript.wasm').then((language) => {
|
||||
// console.log(language);
|
||||
// });
|
||||
|
||||
this.aceApi = ace;
|
||||
}
|
||||
|
||||
|
||||
private ngAfterViewInit(): void {
|
||||
this.loadAce();
|
||||
}
|
||||
|
||||
private loadAce(): void {
|
||||
this.configAceAndBindToElement()
|
||||
this.setupRequestedMode();
|
||||
}
|
||||
|
||||
private configAceAndBindToElement(): void {
|
||||
this.editorSettings = this.editorsService.editorSettings;
|
||||
|
||||
ace.config.set('basePath', this.editorSettings.BASE_PATH);
|
||||
|
||||
this.editor = ace.edit( this.editorElm.nativeElement );
|
||||
this.editor.setOptions( this.editorSettings.CONFIG );
|
||||
|
||||
this.editorsService.set(this.uuid, this);
|
||||
if (this.isDefault) {
|
||||
this.editorsService.setActiveEditor(this.uuid);
|
||||
this.addActiveStyling();
|
||||
this.editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private setupRequestedMode() {
|
||||
switch(this.mode) {
|
||||
case EditorType.Standalone:
|
||||
// Note: Ace editor without any additional Newton setup...
|
||||
break;
|
||||
case EditorType.MiniMap:
|
||||
this.setAsMiniMapView();
|
||||
break;
|
||||
case EditorType.ReadOnly:
|
||||
this.setAsReadOnly();
|
||||
break;
|
||||
default:
|
||||
this.loadNewtonKeyBindings();
|
||||
this.loadNewtonEventBindings();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private loadNewtonKeyBindings(): void {
|
||||
let keyBindings = [];
|
||||
for (let i = 0; i < this.editorSettings.KEYBINDINGS.length; i++) {
|
||||
let keyBinding = this.editorSettings.KEYBINDINGS[i];
|
||||
keyBindings.push(
|
||||
{
|
||||
name: keyBinding.name,
|
||||
bindKey: keyBinding.bindKey,
|
||||
exec: (keyBinding.name && keyBinding?.service) ?
|
||||
() => (
|
||||
this[keyBinding?.service][keyBinding.name]()
|
||||
)
|
||||
:
|
||||
(this[keyBinding.name]) ?
|
||||
() => (
|
||||
this[keyBinding.name]()
|
||||
)
|
||||
:
|
||||
() => (
|
||||
console.log(
|
||||
`Name: ${keyBinding.name}, is not mapping to a method OR mapping to a Service: ${keyBinding?.service} and Name: ${keyBinding.name}.`
|
||||
)
|
||||
)
|
||||
,
|
||||
readOnly: keyBinding.readOnly
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.editor.commands.addCommands( keyBindings );
|
||||
}
|
||||
|
||||
private loadNewtonEventBindings(): void {
|
||||
|
||||
// Note: https://ajaxorg.github.io/ace-api-docs/interfaces/ace.Ace.EditorEvents.html
|
||||
this.editor.on("focus", (event) => {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "set-active-editor";
|
||||
message.editorUUID = this.uuid;
|
||||
message.rawData = this.editor;
|
||||
|
||||
this.editorsService.sendMessage(message);
|
||||
this.searchReplaceService.sendMessage(message);
|
||||
|
||||
message = new ServiceMessage();
|
||||
message.action = "set-active-editor";
|
||||
message.rawData = this;
|
||||
this.lspManagerService.sendMessage(message);
|
||||
this.markdownPreviewService.sendMessage(message);
|
||||
|
||||
message = new ServiceMessage();
|
||||
message.action = "highlight-active-tab";
|
||||
message.filePath = this.activeFile?.path;
|
||||
this.tabsService.sendMessage(message);
|
||||
|
||||
this.updateInfoBar();
|
||||
});
|
||||
|
||||
this.editor.on("click", (event) => {
|
||||
this.updateInfoBar();
|
||||
});
|
||||
|
||||
this.editor.addEventListener("mousedown", (event) => {
|
||||
if (ButtonMap.LEFT === event.domEvent.button) {
|
||||
this.showContextMenu = false;
|
||||
} else if (ButtonMap.RIGHT === event.domEvent.button) {
|
||||
let menuElm = this.contextMenu.nativeElement;
|
||||
let pageX = event.domEvent.pageX;
|
||||
let pageY = event.domEvent.pageY;
|
||||
|
||||
const origin = {
|
||||
left: pageX + 5,
|
||||
top: pageY - 5
|
||||
};
|
||||
|
||||
menuElm.style.left = `${origin.left}px`;
|
||||
menuElm.style.top = `${origin.top}px`;
|
||||
this.showContextMenu = true;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.on("input", () => {
|
||||
this.updateInfoBar();
|
||||
});
|
||||
|
||||
this.editor.on("keyboardActivity", (e) => {
|
||||
switch(e.command.name) {
|
||||
case "golineup":
|
||||
case "golinedown":
|
||||
case "gotoleft":
|
||||
case "gotoright":
|
||||
this.infoBarService.setInfoBarCursorPos(
|
||||
this.editor.getCursorPosition()
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.on("mousewheel", (event) => {
|
||||
if (event.domEvent.ctrlKey && event.domEvent.deltaY < 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.zoomIn();
|
||||
} else if (event.domEvent.ctrlKey && event.domEvent.deltaY > 0) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.zoomOut();
|
||||
}
|
||||
});
|
||||
|
||||
this.editor.on("change", () => {
|
||||
if (this.debounceId) { clearTimeout(this.debounceId); }
|
||||
this.setDebounceTimeout();
|
||||
|
||||
if (!this.activeFile) return;
|
||||
|
||||
const isClean = this.activeFile.session.getUndoManager().isClean();
|
||||
const hasUndo = this.activeFile.session.getUndoManager().hasUndo();
|
||||
let message = new ServiceMessage();
|
||||
message.action = (!isClean && hasUndo) ? "file-changed" : "file-unmodified";
|
||||
message.filePath = this.activeFile.path;
|
||||
this.tabsService.sendMessage(message);
|
||||
});
|
||||
|
||||
this.editor.on("changeSession", (session) => {
|
||||
let message = new ServiceMessage();
|
||||
message.action = "editor-update";
|
||||
message.rawData = this;
|
||||
|
||||
this.lspManagerService.sendMessage(message);
|
||||
|
||||
message = new ServiceMessage();
|
||||
message.action = "highlight-active-tab";
|
||||
message.filePath = this.activeFile?.path;
|
||||
this.tabsService.sendMessage(message);
|
||||
|
||||
this.updateInfoBar();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public prettyJSON() {
|
||||
let data = JSON.parse(this.editor.session.getValue());
|
||||
this.editor.session.setValue(
|
||||
JSON.stringify(data, null, 4)
|
||||
);
|
||||
}
|
||||
|
||||
public hideContextMenu() {
|
||||
this.showContextMenu = false;
|
||||
}
|
||||
|
||||
public contextMenuClicked(event: any) {
|
||||
this.showContextMenu = false;
|
||||
|
||||
const command = event.target.getAttribute("command");
|
||||
const args = event.target.getAttribute("args");
|
||||
|
||||
if (!command) return;
|
||||
|
||||
this[command]( (args) ? args : null );
|
||||
}
|
||||
|
||||
public assignSession(file: NewtonFile) {
|
||||
if (!file) return;
|
||||
|
||||
this.activeFile = file;
|
||||
this.editor.setSession(file.session);
|
||||
}
|
||||
|
||||
public cloneSession(file: NewtonFile) {
|
||||
if (!file) return;
|
||||
|
||||
this.activeFile = file;
|
||||
let session = this.aceApi.createEditSession(file.session.getValue());
|
||||
session["$config"] = {
|
||||
"lsp": {
|
||||
"filePath": file.path
|
||||
}
|
||||
}
|
||||
|
||||
session.setMode( file.session.getMode()["$id"] );
|
||||
session.setUseWrapMode(true);
|
||||
|
||||
this.editor.setSession(session);
|
||||
}
|
||||
|
||||
private setDebounceTimeout(timeout: number = null) {
|
||||
this.debounceId = setTimeout(() => {
|
||||
this.editorsService.miniMapView.editor.session.setValue(
|
||||
this.editor.session.getValue()
|
||||
);
|
||||
|
||||
this.debounceId = -1;
|
||||
}, (timeout) ? timeout : this.debounceWait);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.dropzone {
|
||||
height: 92vh;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="col">
|
||||
|
||||
<div class="row dropzone" #dropzone dropzone (fileDropped)="onFileDropped($event)">
|
||||
<code-view [isDefault]="true"></code-view>
|
||||
<hr class="col vr-pane-handle" pane-handle />
|
||||
<code-view></code-view>
|
||||
<code-view [mode]="'mini-map'"></code-view>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,285 @@
|
||||
import { Component, DestroyRef, inject } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { EditorsService } from '../common/services/editor/editors.service';
|
||||
import { TabsService } from '../common/services/editor/tabs/tabs.service';
|
||||
import { FilesService } from '../common/services/files.service';
|
||||
|
||||
import { CodeViewComponent } from "./code-view/view.component";
|
||||
|
||||
import { DndDirective } from '../common/directives/dnd.directive';
|
||||
import { PaneHandleDirective } from '../common/directives/pane-handle.directive';
|
||||
import { NewtonFile } from '../common/types/file.type';
|
||||
import { ServiceMessage } from '../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'editors',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DndDirective,
|
||||
PaneHandleDirective,
|
||||
CodeViewComponent
|
||||
],
|
||||
templateUrl: './editors.component.html',
|
||||
styleUrl: './editors.component.css',
|
||||
host: {
|
||||
'class': 'row'
|
||||
}
|
||||
})
|
||||
export class EditorsComponent {
|
||||
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
||||
|
||||
private editorsService: EditorsService = inject(EditorsService);
|
||||
private tabsService: TabsService = inject(TabsService);
|
||||
private filesService: FilesService = inject(FilesService);
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
this.loadMainSubscribers();
|
||||
}
|
||||
|
||||
|
||||
private loadSubscribers() {
|
||||
this.editorsService.getMessage$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
switch ( message.action ) {
|
||||
case "select-left-editor":
|
||||
this.selectLeftEditor(message);
|
||||
break;
|
||||
case "select-right-editor":
|
||||
this.selectRightEditor(message);
|
||||
break;
|
||||
case "move-session-left":
|
||||
this.moveSessionLeft(message);
|
||||
break;
|
||||
case "move-session-right":
|
||||
this.moveSessionRight(message);
|
||||
break;
|
||||
case "set-active-editor":
|
||||
this.setActiveEditor(message);
|
||||
break;
|
||||
case "set-tab-to-editor":
|
||||
this.setTabToEditor(message);
|
||||
break;
|
||||
case "close-tab":
|
||||
this.closeTab(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private loadMainSubscribers() {
|
||||
window?.main.onMenuActions(async (action: string) => {
|
||||
let editorComponent = this.editorsService.getActiveEditorComponent();
|
||||
let editor = editorComponent.editor;
|
||||
|
||||
switch ( action ) {
|
||||
case "new-file":
|
||||
break;
|
||||
case "open-files":
|
||||
editorComponent.openFiles();
|
||||
break;
|
||||
case "save-file":
|
||||
editorComponent.saveFile();
|
||||
break;
|
||||
case "save-file-as":
|
||||
editorComponent.saveFileAs();
|
||||
break;
|
||||
case "cut":
|
||||
editorComponent.cutText();
|
||||
break;
|
||||
case "copy":
|
||||
editorComponent.copyText();
|
||||
break;
|
||||
case "paste":
|
||||
editorComponent.pasteText();
|
||||
break;
|
||||
case "zoom-in":
|
||||
editorComponent.zoomIn()
|
||||
break;
|
||||
case "zoom-out":
|
||||
editorComponent.zoomOut()
|
||||
break;
|
||||
case "open-settings":
|
||||
editor.showSettingsMenu();
|
||||
case "show-about":
|
||||
break;
|
||||
case "quit":
|
||||
window?.main.quit();
|
||||
break;
|
||||
default:
|
||||
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) {
|
||||
this.filesService.loadFilesList(files).then((file: NewtonFile | undefined | null) => {
|
||||
// Note: if we drop an already loaded file the path doesn't get set and
|
||||
// therefor the last file in drop list might get returned without path.
|
||||
if (!file.path) return;
|
||||
|
||||
this.editorsService.setSession(file);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="col col-md-6" title="{{fpath || '...'}}">
|
||||
{{path || "..."}}
|
||||
</div>
|
||||
|
||||
<div class="col col-md-2">
|
||||
{{cursorPos || "1:1"}}
|
||||
</div>
|
||||
|
||||
<div class="col col-md-2">
|
||||
{{encodeing || "utf-8"}}
|
||||
</div>
|
||||
|
||||
<div class="col col-md-2">
|
||||
{{ftype || "text"}}
|
||||
</div>
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Component, DestroyRef, inject } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.service';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'info-bar',
|
||||
standalone: true,
|
||||
imports: [
|
||||
],
|
||||
templateUrl: './info-bar.component.html',
|
||||
styleUrl: './info-bar.component.css',
|
||||
host: {
|
||||
'class': 'row info-bar'
|
||||
}
|
||||
})
|
||||
export class InfoBarComponent {
|
||||
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
||||
|
||||
private infoBarService: InfoBarService = inject(InfoBarService);
|
||||
|
||||
fpath: string = "";
|
||||
path: string = "";
|
||||
cursorPos: string = "";
|
||||
encodeing: string = "";
|
||||
ftype: string = "";
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
|
||||
private loadSubscribers() {
|
||||
this.infoBarService.updateInfoBarFPath$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((fpath: string) => {
|
||||
this.fpath = fpath;
|
||||
let _path = fpath;
|
||||
|
||||
if (fpath?.length > 67) {
|
||||
_path = "..." + fpath.slice(fpath.length - 67, fpath.length);
|
||||
}
|
||||
|
||||
this.path = _path;
|
||||
});
|
||||
|
||||
this.infoBarService.updateInfoBarCursorPos$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((cursorPos: any) => {
|
||||
this.cursorPos = `${cursorPos.row + 1}:${cursorPos.column}`;
|
||||
});
|
||||
|
||||
this.infoBarService.updateInfoBarEncodeing$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((encodeing: string) => {
|
||||
this.encodeing = encodeing;
|
||||
});
|
||||
|
||||
this.infoBarService.updateInfoBarFType$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((ftype: string) => {
|
||||
let mode = ftype.split("/");
|
||||
this.ftype = mode[ mode.length - 1 ];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.lsp-config-text {
|
||||
display: grid;
|
||||
min-height: 25em;
|
||||
}
|
||||
|
||||
.clear-left-padding {
|
||||
padding-left: 0px;
|
||||
}
|
||||
.clear-right-padding {
|
||||
padding-right: 0px;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row mt-2 mb-3">
|
||||
|
||||
<div class="col clear-right-padding">
|
||||
<div class="input-group-sm"
|
||||
(mouseup)="handleActionMouseUp($event)"
|
||||
>
|
||||
<label class="form-control" [innerText]="lspManagerService.workspaceFolder || 'Project Path...'">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-auto clear-left-padding">
|
||||
<button class="btn btn-sm btn-dark" (click)="clearWorkspaceFolder()">x</button>
|
||||
</div>
|
||||
|
||||
<div class="col col-auto">
|
||||
<div class="input-group-sm">
|
||||
<button class="btn btn-sm btn-dark" (click)="setWorkspaceFolder()">Workspace Folder</button>
|
||||
<button class="btn btn-sm btn-danger ms-5" (click)="hideLspManager()">X</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row mt-2 md-2">
|
||||
<div class="col">
|
||||
LSP Configs:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<code-view #lspEditorComponent [mode]="'standalone'" class="lsp-config-text"></code-view>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2 md-2">
|
||||
<div class="col col-sm" [hidden]="!lspManagerService.workspaceFolder">
|
||||
<button class="btn btn-sm btn-dark" (click)="createLanguageClient()">Create Language Client</button>
|
||||
<button class="btn btn-sm btn-dark" (click)="closeLanguageClient()">Close Language Client</button>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
Target Editor: <label [innerText]="editor?.id || '...'"></label>
|
||||
</div>
|
||||
<div class="col-sm" [hidden]="!lspManagerService.workspaceFolder">
|
||||
<button class="btn btn-sm btn-dark"
|
||||
(click)="registerEditorToLanguageClient()">
|
||||
Register Editor To LSP
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
Active Editor Session:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
<code-view #sessionEditorComponent [mode]="'read-only'" class="lsp-config-text"></code-view>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ul #contextMenu
|
||||
class="contextMenu"
|
||||
[hidden]="!showContextMenu"
|
||||
(blur)="hideContextMenu"
|
||||
(click)="contextMenuClicked($event)"
|
||||
>
|
||||
<li command="pasteText">Paste</li>
|
||||
</ul>
|
||||
@@ -0,0 +1,206 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectorRef,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
ViewChild,
|
||||
inject } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { LspManagerService } from '../../common/services/editor/lsp-manager/lsp-manager.service';
|
||||
|
||||
import { CodeViewComponent } from '../code-view/view.component';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
import { ButtonMap } from '../../common/constants/button.map';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'lsp-manager',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CodeViewComponent
|
||||
],
|
||||
templateUrl: './lsp-manager.component.html',
|
||||
styleUrl: './lsp-manager.component.css',
|
||||
host: {
|
||||
'class': 'lsp-manager',
|
||||
"(keyup)": "globalLspManagerKeyHandler($event)"
|
||||
}
|
||||
})
|
||||
export class LspManagerComponent {
|
||||
readonly #destroyRef = inject(DestroyRef);
|
||||
private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef);
|
||||
|
||||
lspManagerService: LspManagerService = inject(LspManagerService);
|
||||
|
||||
@HostBinding("class.hidden") isHidden: boolean = true;
|
||||
@ViewChild('lspEditorComponent') lspEditorComponent!: CodeViewComponent;
|
||||
@ViewChild('sessionEditorComponent') sessionEditorComponent!: CodeViewComponent;
|
||||
lspTextEditor: any;
|
||||
innerEditor: any;
|
||||
editor: any;
|
||||
activeFile: any;
|
||||
|
||||
@ViewChild('contextMenu') contextMenu!: ElementRef;
|
||||
public showContextMenu: boolean = false;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
private ngAfterViewInit(): void {
|
||||
this.mapEditorsAndLoadConfig();
|
||||
}
|
||||
|
||||
private mapEditorsAndLoadConfig() {
|
||||
this.lspTextEditor = this.lspEditorComponent.editor;
|
||||
this.innerEditor = this.sessionEditorComponent.editor;
|
||||
|
||||
this.lspTextEditor.on("input", () => {
|
||||
this.lspManagerService.lspConfigDataStr =
|
||||
this.lspTextEditor.session.getValue();
|
||||
});
|
||||
|
||||
this.lspManagerService.loadLspConfigData().then((lspConfigData) => {
|
||||
this.lspTextEditor.session.setMode("ace/mode/json");
|
||||
this.lspTextEditor.session.setValue(lspConfigData);
|
||||
});
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
this.lspManagerService.getMessage$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
if (message.action === "toggle-lsp-manager") {
|
||||
this.toggleLspManager(message);
|
||||
} else if (message.action === "set-active-editor") {
|
||||
this.setActiveEditor(message);
|
||||
} else if (message.action === "editor-update") {
|
||||
this.editorUpdate(message);
|
||||
} else if (message.action === "close-file") {
|
||||
this.closeFile(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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() {
|
||||
this.lspManagerService.workspaceFolder = "";
|
||||
}
|
||||
|
||||
public setWorkspaceFolder() {
|
||||
window?.fs.chooseFolder().then((folder: string) => {
|
||||
if (!folder) return;
|
||||
|
||||
this.lspManagerService.workspaceFolder = folder;
|
||||
});
|
||||
}
|
||||
|
||||
public createLanguageClient() {
|
||||
let mode = this.lspManagerService.getMode(this.editor.session);
|
||||
this.lspManagerService.createLanguageProviderWithClientServer(mode);
|
||||
}
|
||||
|
||||
public closeLanguageClient() {
|
||||
let mode = this.lspManagerService.getMode(this.editor.session);
|
||||
this.lspManagerService.closeLanguageProviderWithClientServer(mode);
|
||||
}
|
||||
|
||||
public registerEditorToLanguageClient() {
|
||||
this.lspManagerService.registerEditorToLSPClient(this.editor);
|
||||
}
|
||||
|
||||
|
||||
public globalLspManagerKeyHandler(event: any) {
|
||||
if (event.ctrlKey && event.shiftKey && event.key === "l") {
|
||||
this.hideLspManager();
|
||||
}
|
||||
}
|
||||
|
||||
public hideLspManager() {
|
||||
this.isHidden = true;
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
private toggleLspManager(message: ServiceMessage) {
|
||||
this.isHidden = !this.isHidden;
|
||||
|
||||
if (this.isHidden) return;
|
||||
|
||||
// Note: hack for issue with setActiveEditor TODO
|
||||
setTimeout(() => {
|
||||
this.innerEditor.setSession(this.editor.getSession());
|
||||
}, 10);
|
||||
}
|
||||
|
||||
private setActiveEditor(message: ServiceMessage) {
|
||||
this.editor = message.rawData.editor;
|
||||
this.activeFile = message.rawData.activeFile;
|
||||
|
||||
// TODO: figure out why this doesn't update the session consistently...
|
||||
// It seems maybe bound to visible state as change detector ref didn't help either.
|
||||
// this.innerEditor.setSession(this.editor.session);
|
||||
}
|
||||
|
||||
private editorUpdate(message: ServiceMessage) {
|
||||
if (
|
||||
!this.editor ||
|
||||
!message.rawData.activeFile
|
||||
) return;
|
||||
|
||||
this.editor.setSession(message.rawData.editor.getSession())
|
||||
this.activeFile = message.rawData.activeFile;
|
||||
|
||||
this.lspManagerService.registerSession(this.editor);
|
||||
}
|
||||
|
||||
private closeFile(message: ServiceMessage) {
|
||||
this.lspManagerService.closeDocument(message.rawData);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div [innerHtml]="bodyHtml || defaultHtml">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
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 { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'markdown-preview',
|
||||
standalone: true,
|
||||
imports: [
|
||||
],
|
||||
templateUrl: './markdown-preview.component.html',
|
||||
styleUrl: './markdown-preview.component.css',
|
||||
host: {
|
||||
'class': 'container-fluid markdown-preview'
|
||||
}
|
||||
})
|
||||
export class MarkdownPreviewComponent {
|
||||
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
||||
|
||||
private markdownPreviewService: MarkdownPreviewService = inject(MarkdownPreviewService);
|
||||
|
||||
@HostBinding("class.hidden") isHidden: boolean = true;
|
||||
converter: any = marked;
|
||||
defaultHtml: string = "<h1>NOT a Markdown file...</h1>"
|
||||
bodyHtml: string = "";
|
||||
|
||||
private editorComponent!: any;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
this.markdownPreviewService.getMessage$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
switch ( message.action ) {
|
||||
case "toggle-markdown-preview":
|
||||
this.toggleMarkdownPreview(message);
|
||||
break;
|
||||
case "set-active-editor":
|
||||
this.setActiveEditor(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private toggleMarkdownPreview(message: ServiceMessage) {
|
||||
this.isHidden = !this.isHidden;
|
||||
|
||||
setTimeout(() => {
|
||||
this.updatePreview();
|
||||
}, 10);
|
||||
}
|
||||
|
||||
private setActiveEditor(message: ServiceMessage) {
|
||||
if (this.editorComponent == message.rawData) return;
|
||||
|
||||
this.editorComponent = message.rawData;
|
||||
|
||||
if (this.isHidden) return;
|
||||
|
||||
this.updatePreview();
|
||||
}
|
||||
|
||||
public updatePreview() {
|
||||
let fileMode = this.editorComponent.editor.session.getMode()["$id"];
|
||||
let isMdFile = (fileMode.includes("markdown"));
|
||||
this.bodyHtml = "";
|
||||
|
||||
if (!isMdFile) return;
|
||||
|
||||
let mdStr = this.editorComponent.editor.session.getValue();
|
||||
let pathParts = this.editorComponent.activeFile.path.split("/");
|
||||
let basePath = "file://" + pathParts.slice(0, -1).join("/");
|
||||
this.bodyHtml = this.converter.parse(
|
||||
mdStr.replaceAll("](images", `](${basePath}/images`)
|
||||
.replaceAll("](imgs", `](${basePath}/imgs`)
|
||||
.replaceAll("](pictures", `](${basePath}/pictures`)
|
||||
.replaceAll("](pics", `](${basePath}/pics`)
|
||||
.replaceAll("](screenshots", `](${basePath}/screenshots`)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
.buttons > button {
|
||||
min-width: 3.5em;
|
||||
min-height: 2.5em;
|
||||
}
|
||||
|
||||
.width-8em {
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
.margin-tb-1em {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: rgba(125, 125, 125, 1);
|
||||
color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.searching,
|
||||
.search-success,
|
||||
.search-fail {
|
||||
border-style: solid;
|
||||
color: rgba(125, 125, 125, 1) !important;
|
||||
}
|
||||
|
||||
.searching {
|
||||
border-color: rgba(0, 225, 225, 0.64) !important;
|
||||
}
|
||||
|
||||
.search-success {
|
||||
background: rgba(136, 204, 39, 0.12) !important;
|
||||
border-color: rgba(136, 204, 39, 1) !important;
|
||||
}
|
||||
|
||||
.search-fail {
|
||||
background: rgba(170, 18, 18, 0.12) !important;
|
||||
border-color: rgba(200, 18, 18, 1) !important;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col col-3">
|
||||
@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 class="col col-4">
|
||||
<label id="find-options-lbl">Finding with Options: {{findOptions || "Case Insensitive"}}</label>
|
||||
</div>
|
||||
|
||||
<div class="col col-5 line-height-32px buttons">
|
||||
<button title="Close Panel"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="hideSearchReplace()">X
|
||||
</button>
|
||||
<button id="whole-word-btn" title="Whole Word"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleWholeWordSearch($event)">
|
||||
<img src="resources/imgs/whole-word.png" />
|
||||
</button>
|
||||
<button id="only-in-selection-btn" title="Only In Selection"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleSelectionOnlyScan($event)">
|
||||
<img src="resources/imgs/only-in-selection.png" />
|
||||
</button>
|
||||
<button id="match-case-btn" title="Match Case"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleCaseSensitive($event)">Aa
|
||||
</button>
|
||||
<button id="use-regex-btn" title="Use Regex"
|
||||
class="float-end btn btn-sm btn-dark"
|
||||
(click)="toggleRegex($event)">.*
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="margin-tb-1em"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group-sm mb-3">
|
||||
<input #findEntryElm
|
||||
id="find-entry"
|
||||
class="form-control"
|
||||
type="search"
|
||||
(focus)="searchForString()"
|
||||
(keyup)="findEntryKeyUpHandler($event)"
|
||||
(input)="searchForString()"
|
||||
placeholder="Find in current file..."
|
||||
aria-label="Find in current file..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-auto">
|
||||
<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 class="row">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group-sm mb-3">
|
||||
<input #replaceEntryElm
|
||||
id="replace-entry"
|
||||
class="form-control"
|
||||
type="search"
|
||||
(keyup.enter)="replaceEntry($event)"
|
||||
title="Replace in current file..."
|
||||
placeholder="Replace in current file..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col col-auto">
|
||||
<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>
|
||||
@@ -0,0 +1,286 @@
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
Input,
|
||||
ViewChild,
|
||||
inject
|
||||
} from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'search-replace',
|
||||
standalone: true,
|
||||
imports: [
|
||||
],
|
||||
templateUrl: './search-replace.component.html',
|
||||
styleUrl: './search-replace.component.css',
|
||||
host: {
|
||||
'class': 'row search-replace',
|
||||
"(keyup)": "globalSearchReplaceKeyHandler($event)"
|
||||
}
|
||||
})
|
||||
export class SearchReplaceComponent {
|
||||
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
||||
|
||||
private searchReplaceService: SearchReplaceService = inject(SearchReplaceService);
|
||||
|
||||
@HostBinding("class.hidden") isHidden: boolean = true;
|
||||
@ViewChild('findEntryElm') findEntryElm!: ElementRef;
|
||||
@ViewChild('replaceEntryElm') replaceEntryElm!: ElementRef;
|
||||
|
||||
@Input() query: string = "";
|
||||
@Input() findOptions: string = "";
|
||||
@Input() isQueryLong: boolean = false;
|
||||
@Input() isQueryNotFound: boolean = false;
|
||||
@Input() totalCount: number = 0;
|
||||
|
||||
private editor!: any;
|
||||
|
||||
private useWholeWordSearch: boolean = false;
|
||||
private searchOnlyInSelection: boolean = false;
|
||||
private useCaseSensitive: boolean = false;
|
||||
private useRegex: boolean = false;
|
||||
private selection: string = "";
|
||||
private toStr: string = "";
|
||||
private isBackwards: boolean = false;
|
||||
private isWrap: boolean = true;
|
||||
private searchTimeoutId: number = -1;
|
||||
private searchTimeout: number = 400;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
private loadSubscribers() {
|
||||
this.searchReplaceService.getMessage$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
switch ( message.action ) {
|
||||
case "toggle-search-replace":
|
||||
this.toggleSearchReplace(message);
|
||||
break;
|
||||
case "set-active-editor":
|
||||
this.setActiveEditor(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private toggleSearchReplace(message: ServiceMessage) {
|
||||
this.selection = this.editor.getSelectedText();
|
||||
this.findEntryElm.nativeElement.value = this.selection;
|
||||
|
||||
if (this.selection && !this.isHidden) {
|
||||
this.findEntryElm.nativeElement.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isHidden = !this.isHidden;
|
||||
|
||||
if (this.isHidden) {
|
||||
this.editor.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.findEntryElm.nativeElement.focus();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
private setActiveEditor(message: ServiceMessage) {
|
||||
if (this.editor == message.rawData) return;
|
||||
|
||||
this.editor = message.rawData;
|
||||
|
||||
if (this.isHidden) return;
|
||||
|
||||
|
||||
this.searchForString();
|
||||
}
|
||||
|
||||
public hideSearchReplace() {
|
||||
if (this.selection) {
|
||||
this.selection = "";
|
||||
return;
|
||||
}
|
||||
|
||||
this.isHidden = true;
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
public globalSearchReplaceKeyHandler(event: any) {
|
||||
if (event.ctrlKey && event.key === "f") {
|
||||
this.hideSearchReplace();
|
||||
} else if (event.ctrlKey && event.key === "l") {
|
||||
this.findEntryElm.nativeElement.focus();
|
||||
} else if (event.ctrlKey && event.key === "r") {
|
||||
this.replaceEntryElm.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public toggleWholeWordSearch(event: any) {
|
||||
let target = event.target;
|
||||
if (target.nodeName === "IMG")
|
||||
target = target.parentElement;
|
||||
|
||||
this.useWholeWordSearch = !this.useWholeWordSearch;
|
||||
target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
public toggleSelectionOnlyScan(event: any) {
|
||||
let target = event.target;
|
||||
if (target.nodeName === "IMG")
|
||||
target = target.parentElement;
|
||||
|
||||
this.searchOnlyInSelection = !this.searchOnlyInSelection;
|
||||
target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
public toggleCaseSensitive(event: any) {
|
||||
this.useCaseSensitive = !this.useCaseSensitive;
|
||||
event.target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
public toggleRegex(event: any) {
|
||||
this.useRegex = !this.useRegex;
|
||||
event.target.classList.toggle("selected");
|
||||
this.setFindOptionsLbl();
|
||||
this.findAllEntries();
|
||||
}
|
||||
|
||||
private setFindOptionsLbl() {
|
||||
let findOptionsStr = "";
|
||||
|
||||
if (this.useRegex)
|
||||
findOptionsStr += "Regex"
|
||||
|
||||
findOptionsStr += (findOptionsStr) ? ", " : "";
|
||||
findOptionsStr += (this.useCaseSensitive) ? "Case Sensitive" : "Case InSensitive";
|
||||
|
||||
if (this.searchOnlyInSelection)
|
||||
findOptionsStr += ", Within Current Selection"
|
||||
|
||||
if (this.useWholeWordSearch)
|
||||
findOptionsStr += ", Whole Word"
|
||||
|
||||
this.findOptions = findOptionsStr;
|
||||
}
|
||||
|
||||
public findPreviousEntry() {
|
||||
this.editor.findPrevious();
|
||||
}
|
||||
|
||||
public findNextEntry() {
|
||||
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() {
|
||||
this.query = this.findEntryElm.nativeElement.value;
|
||||
|
||||
this.totalCount = this.editor.findAll(this.query, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
|
||||
if (this.totalCount === 0) this.isQueryNotFound = true;
|
||||
}
|
||||
|
||||
public replaceEntry(event: KeyboardEvent) {
|
||||
if (this.isQueryLong || this.isQueryNotFound) return;
|
||||
|
||||
let fromStr = this.findEntryElm.nativeElement.value;
|
||||
let toStr = this.replaceEntryElm.nativeElement.value;
|
||||
|
||||
if (!fromStr) return;
|
||||
|
||||
this.editor.replace(toStr, fromStr, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
|
||||
this.editor.clearSelection();
|
||||
this.editor.findNext();
|
||||
}
|
||||
|
||||
public replaceAll() {
|
||||
if (this.isQueryLong || this.isQueryNotFound) return;
|
||||
|
||||
let fromStr = this.findEntryElm.nativeElement.value;
|
||||
let toStr = this.replaceEntryElm.nativeElement.value;
|
||||
|
||||
if (!fromStr) return;
|
||||
|
||||
this.editor.replaceAll(toStr, fromStr, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
|
||||
this.isQueryNotFound = true;
|
||||
}
|
||||
|
||||
public searchForString() {
|
||||
if (this.searchTimeoutId) { clearTimeout(this.searchTimeoutId); }
|
||||
|
||||
this.query = this.findEntryElm.nativeElement.value;
|
||||
|
||||
if (!this.query) {
|
||||
this.isQueryLong = false;
|
||||
this.isQueryNotFound = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isQueryLong = (this.query.length > 80);
|
||||
if (this.isQueryLong) return;
|
||||
|
||||
this.searchTimeoutId = setTimeout(() => {
|
||||
this.totalCount = this.editor.findAll(this.query, {
|
||||
backwards: this.isBackwards,
|
||||
wrap: this.isWrap,
|
||||
caseSensitive: this.useCaseSensitive,
|
||||
wholeWord: this.useWholeWordSearch,
|
||||
regExp: this.useRegex,
|
||||
range: this.searchOnlyInSelection
|
||||
});
|
||||
|
||||
this.isQueryNotFound = (this.totalCount === 0);
|
||||
}, this.searchTimeout);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
.display-contents {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
float: left;
|
||||
margin-right: 2em;
|
||||
font-size: 0.2em;
|
||||
|
||||
border-top-style: solid;
|
||||
border-top-color: #ffffff64;
|
||||
border-top-width: 2px;
|
||||
|
||||
border-left-style: solid;
|
||||
border-left-color: #ffffff64;
|
||||
border-left-width: 2px;
|
||||
|
||||
border-right-style: solid;
|
||||
border-right-color: #ffffff64;
|
||||
border-right-width: 2px;
|
||||
}
|
||||
|
||||
.active-tab {
|
||||
background-color: rgba(144, 144, 144, 0.64);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-changed {
|
||||
color: rgba(255, 168, 0, 0.64);
|
||||
}
|
||||
|
||||
.file-deleted {
|
||||
color: rgba(255, 0, 0, 0.64);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 2em;
|
||||
margin-right: 2em;
|
||||
font-size: 4em;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
background: rgba(116, 0, 0, 0.64);
|
||||
border-style: solid;
|
||||
border-color: rgba(0, 0, 0, 0.64);
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
background: rgba(256, 0, 0, 0.64);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<div cdkDropList
|
||||
cdkDropListSortingDisabled
|
||||
cdkDropListLockAxis="x"
|
||||
cdkDropListOrientation="horizontal"
|
||||
(cdkDropListDropped)="dropped($event)"
|
||||
(mousedown)="handleActionMouseDown($event)"
|
||||
(click)="handleActionClick($event)"
|
||||
class="display-contents"
|
||||
>
|
||||
|
||||
<div *ngFor="let tab of tabs"
|
||||
cdkDrag
|
||||
(cdkDragMoved)="moved($event)"
|
||||
title="{{tab.path}}"
|
||||
uuid="{{tab.uuid}}"
|
||||
path="{{tab.path}}"
|
||||
class="tab noselect"
|
||||
>
|
||||
<span class="title">{{tab.title}}</span>
|
||||
<button class="close-button">X</button>
|
||||
</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>
|
||||
@@ -0,0 +1,268 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectorRef,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
ViewChild,
|
||||
inject
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CdkDrag, CdkDragDrop, CdkDropList } from '@angular/cdk/drag-drop';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
import { TabsService } from '../../common/services/editor/tabs/tabs.service';
|
||||
|
||||
import { ServiceMessage } from '../../common/types/service-message.type';
|
||||
|
||||
import { ButtonMap } from '../../common/constants/button.map';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'tabs',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
CdkDropList,
|
||||
CdkDrag,
|
||||
],
|
||||
templateUrl: './tabs.component.html',
|
||||
styleUrl: './tabs.component.css',
|
||||
host: {
|
||||
'class': 'tabs scroller'
|
||||
}
|
||||
})
|
||||
export class TabsComponent {
|
||||
readonly #destroyRef = inject(DestroyRef);
|
||||
|
||||
private tabsService: TabsService = inject(TabsService);
|
||||
private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef);
|
||||
|
||||
@ViewChild('contextMenu') contextMenu!: ElementRef;
|
||||
public showContextMenu: boolean = false;
|
||||
|
||||
tabs: any[] = this.tabsService.tabs;
|
||||
targetEvent!: any;
|
||||
activeTab!: any;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.loadSubscribers();
|
||||
}
|
||||
|
||||
|
||||
private loadSubscribers() {
|
||||
this.tabsService.getMessage$().pipe(
|
||||
takeUntilDestroyed(this.#destroyRef)
|
||||
).subscribe((message: ServiceMessage) => {
|
||||
let elm = document.querySelector(`.tab[title="${message.filePath}"]`);
|
||||
|
||||
switch ( message.action ) {
|
||||
case "create-tab":
|
||||
this.createTab(message.fileName, message.fileUUID, message.filePath);
|
||||
break;
|
||||
case "file-unmodified":
|
||||
elm.classList.remove("file-changed");
|
||||
break;
|
||||
case "file-changed":
|
||||
elm.classList.add("file-changed");
|
||||
elm.classList.remove("file-deleted");
|
||||
break;
|
||||
case "file-deleted":
|
||||
elm.classList.add("file-deleted");
|
||||
elm.classList.remove("file-changed");
|
||||
break;
|
||||
case "file-saved":
|
||||
elm.classList.remove("file-deleted");
|
||||
elm.classList.remove("file-changed");
|
||||
break;
|
||||
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 handleActionClick(event: any): void {
|
||||
if (ButtonMap.RIGHT === event.button) return;
|
||||
|
||||
let target = event.target;
|
||||
|
||||
this.showContextMenu = false;
|
||||
this.processTargetEvent(event);
|
||||
}
|
||||
|
||||
protected handleActionMouseDown(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.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 {
|
||||
this.tabsService.push({title: title, uuid: uuid, path: path});
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
private moved(event: any): void {
|
||||
let target = event.event.target;
|
||||
let fpath = "";
|
||||
|
||||
if ( target.classList.contains("title") ||
|
||||
target.classList.contains("close-button")
|
||||
) {
|
||||
fpath = target.parentElement.getAttribute("title")
|
||||
} else (
|
||||
fpath = target.getAttribute("title")
|
||||
)
|
||||
|
||||
this.tabsService.setNewTargetIndex(fpath);
|
||||
}
|
||||
|
||||
protected dropped(event: CdkDragDrop<any>): void {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/* TAGS */
|
||||
|
||||
|
||||
/* IDs */
|
||||
#ace_settingsmenu, #kbshortcutmenu {
|
||||
background-color: rgba(64, 64, 64, 0.84);
|
||||
color: rgba(255, 255, 255, 1.0);
|
||||
box-shadow: -1px 4px 5px rgba(124, 124, 124, 0.64);
|
||||
padding: 1em 0.5em 2em 1em;
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 9991;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
||||
/* CLASSES */
|
||||
.ace_editor, .ace_gutter {
|
||||
background-color: rgba(0, 0, 0, 0.0) !important;
|
||||
}
|
||||
|
||||
.ace_autocomplete {
|
||||
background-color: #25282c !important;
|
||||
}
|
||||
|
||||
.ace_editor.ace_autocomplete {
|
||||
width: 300px;
|
||||
z-index: 200000;
|
||||
border: 1px lightgray solid;
|
||||
position: fixed;
|
||||
box-shadow: 2px 3px 5px rgba(0,0,0,.2);
|
||||
line-height: 1.4;
|
||||
background-color: rgba(0, 0, 0, 0.0);
|
||||
color: rgba(255, 255, 255, 1.0);
|
||||
}
|
||||
|
||||
.ace_optionsMenuEntry button {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.ace_optionsMenuEntry button[ace_selected_button=true] {
|
||||
background: #00e877;
|
||||
}
|
||||
|
||||
.ace_print-margin {
|
||||
width: 1px !important;
|
||||
background-color: rgba(255, 0, 0, 0.84) !important;
|
||||
}
|
||||
|
||||
.ace_selected {
|
||||
background: rgba(249, 148, 6, 0.64);
|
||||
color: rgba(0, 0, 0, 1.0);
|
||||
/* color: rgba(255, 255, 255, 1.0); */
|
||||
}
|
||||
|
||||
.ace_sb-v,
|
||||
.ace_sb-h {
|
||||
width: 0.8em !important;
|
||||
}
|
||||
|
||||
|
||||
.ace_cursor {
|
||||
color: rgba(249, 148, 6, 0.64);
|
||||
animation: blinker 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blinker {
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* TAGS */
|
||||
|
||||
html {
|
||||
background-color: rgba(40, 44, 52, 0.64);
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: rgba(64, 64, 64, 0.0);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ul, li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
|
||||
/* IDs */
|
||||
|
||||
|
||||
/* CLASSES */
|
||||
@@ -0,0 +1,160 @@
|
||||
/* TAGS */
|
||||
|
||||
|
||||
/* IDs */
|
||||
|
||||
|
||||
/* CLASSES */
|
||||
|
||||
.search-replace,
|
||||
.markdown-preview,
|
||||
.lsp-manager {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
background-color: rgba(64, 64, 64, 0.84);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.search-replace {
|
||||
bottom: 2em;
|
||||
left: 2em;
|
||||
right: 2em;
|
||||
z-index: 900;
|
||||
}
|
||||
|
||||
.markdown-preview {
|
||||
top: 2em;
|
||||
bottom: 2em;
|
||||
right: 2em;
|
||||
z-index: 700;
|
||||
width: 50vw;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.lsp-manager {
|
||||
top: 2em;
|
||||
bottom: 2em;
|
||||
left: 2em;
|
||||
right: 2em;
|
||||
z-index: 800;
|
||||
}
|
||||
|
||||
|
||||
.info-bar {
|
||||
font-size: 0.8em;
|
||||
color: rgba(255, 255, 255, 0.84);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
margin-bottom: 0.5em;
|
||||
padding-bottom: 0.4em;
|
||||
padding-top: 0.4em;
|
||||
color: rgba(255, 255, 255, 0.64);
|
||||
margin-left: 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,
|
||||
.vr-pane-handle {
|
||||
border: 2px dashed lightblue;
|
||||
}
|
||||
|
||||
.vr-pane-handle {
|
||||
max-width: max-content;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.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 {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scroller {
|
||||
-webkit-scrollbar-color: #00000084 #ffffff06;
|
||||
-webkit-scrollbar-width: thin;
|
||||
scrollbar-color: #00000084 #ffffff06;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.noselect {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Old versions of Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently
|
||||
supported by Chrome, Edge, Opera and Firefox */
|
||||
}
|
||||
|
||||
.page-alert-zone-container {
|
||||
position: sticky;
|
||||
top: 0em;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.searching,
|
||||
.search-success,
|
||||
.search-fail {
|
||||
border-style: solid;
|
||||
color: rgba(125, 125, 125, 1) !important;
|
||||
}
|
||||
|
||||
.searching {
|
||||
border-color: rgba(0, 225, 225, 0.64) !important;
|
||||
}
|
||||
.search-success {
|
||||
background: rgba(136, 204, 39, 0.12) !important;
|
||||
border-color: rgba(136, 204, 39, 1) !important;
|
||||
}
|
||||
.search-fail {
|
||||
background: rgba(170, 18, 18, 0.12) !important;
|
||||
border-color: rgba(200, 18, 18, 1) !important;
|
||||
}
|
||||
|
||||
/* Other message text colors */
|
||||
.error { color: rgb(170, 18, 18); }
|
||||
.warning { color: rgb(255, 168, 0); }
|
||||
.success { color: rgb(136, 204, 39); }
|
||||
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Newton</title>
|
||||
<base href="./">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="resources/newton.png">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container-fluid">
|
||||
<app-root></app-root>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
|
||||
import { AppComponent } from './app/app.component';
|
||||
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig).catch(
|
||||
(err) => console.error(err)
|
||||
);
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by Angular itself.
|
||||
*/
|
||||
|
||||
|
||||
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 {
|
||||
interface Window {
|
||||
electron: {
|
||||
node: any,
|
||||
chrome: any,
|
||||
electron: any,
|
||||
},
|
||||
main: {
|
||||
onMenuActions: any,
|
||||
onTerminalActions: any,
|
||||
quit: any,
|
||||
toggleFullScreen: any,
|
||||
},
|
||||
fs: {
|
||||
getLspConfigData: any,
|
||||
getFileContents: any,
|
||||
openFiles: any,
|
||||
saveFile: any,
|
||||
saveFileAs: any,
|
||||
chooseFolder: any,
|
||||
closeFile: any,
|
||||
getPathForFile: any,
|
||||
onLoadFiles: any,
|
||||
onUpdateFilePath: any,
|
||||
onSavedFile: any,
|
||||
onChangedFile: any,
|
||||
onDeletedFile: any,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
@@ -0,0 +1,16 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/polyfills.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"downlevelIteration": true,
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||