├── projects ├── dev-app │ ├── src │ │ ├── assets │ │ │ ├── .gitkeep │ │ │ └── lang_samples │ │ │ │ ├── c.txt │ │ │ │ ├── clojurescript.txt │ │ │ │ ├── diff.txt │ │ │ │ ├── css.txt │ │ │ │ ├── html.txt │ │ │ │ ├── jsx.txt │ │ │ │ ├── ruby.txt │ │ │ │ ├── javascript.txt │ │ │ │ ├── objective-c.txt │ │ │ │ ├── json.txt │ │ │ │ ├── http.txt │ │ │ │ ├── tsx.txt │ │ │ │ ├── bash.txt │ │ │ │ ├── clojure.txt │ │ │ │ ├── python.txt │ │ │ │ ├── mysql.txt │ │ │ │ ├── coffeescript.txt │ │ │ │ ├── swift.txt │ │ │ │ ├── sql.txt │ │ │ │ ├── julia.txt │ │ │ │ ├── pug.txt │ │ │ │ ├── angular_template.txt │ │ │ │ ├── php.txt │ │ │ │ ├── csharp.txt │ │ │ │ ├── common_lisp.txt │ │ │ │ ├── liquid.txt │ │ │ │ ├── shell.txt │ │ │ │ ├── lua.txt │ │ │ │ ├── apl.txt │ │ │ │ ├── c++.txt │ │ │ │ ├── nginx.txt │ │ │ │ ├── dart.txt │ │ │ │ ├── elm.txt │ │ │ │ ├── haskell.txt │ │ │ │ ├── less.txt │ │ │ │ ├── erlang.txt │ │ │ │ ├── r.txt │ │ │ │ ├── d.txt │ │ │ │ ├── asn.1.txt │ │ │ │ ├── sass.txt │ │ │ │ ├── go.txt │ │ │ │ ├── perl.txt │ │ │ │ ├── scss.txt │ │ │ │ ├── vue.txt │ │ │ │ ├── yaml.txt │ │ │ │ ├── vbscript.txt │ │ │ │ ├── kotlin.txt │ │ │ │ ├── markdown.txt │ │ │ │ ├── java.txt │ │ │ │ ├── groovy.txt │ │ │ │ ├── pascal.txt │ │ │ │ ├── latex.txt │ │ │ │ ├── scala.txt │ │ │ │ ├── typescript.txt │ │ │ │ ├── rust.txt │ │ │ │ ├── dockerfile.txt │ │ │ │ ├── xml.txt │ │ │ │ └── plaintext.txt │ │ ├── app │ │ │ ├── diff │ │ │ │ ├── diff.component.scss │ │ │ │ ├── diff.component.html │ │ │ │ └── diff.component.ts │ │ │ ├── layout │ │ │ │ ├── layout.component.scss │ │ │ │ ├── layout.component.ts │ │ │ │ └── layout.component.html │ │ │ ├── app.component.html │ │ │ ├── app.component.scss │ │ │ ├── app.config.ts │ │ │ ├── app.component.ts │ │ │ ├── app.routes.ts │ │ │ └── home │ │ │ │ ├── home.component.scss │ │ │ │ ├── home.component.html │ │ │ │ └── home.component.ts │ │ ├── favicon.ico │ │ ├── main.ts │ │ ├── index.html │ │ └── styles.scss │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── eslint.config.js └── code-editor │ ├── public-api.ts │ ├── tsconfig.lib.prod.json │ ├── code-editor-module.ts │ ├── ng-package.json │ ├── tsconfig.spec.json │ ├── tsconfig.lib.json │ ├── eslint.config.js │ ├── package.json │ ├── diff-editor.ts │ └── code-editor.ts ├── .husky ├── pre-commit └── commit-msg ├── lint-staged.config.js ├── .editorconfig ├── .prettierrc ├── .vscode ├── extensions.json ├── settings.json ├── launch.json └── tasks.json ├── commitlint.config.js ├── .gitignore ├── .github └── workflows │ └── deploy.yml ├── LICENSE ├── .stylelintrc ├── tsconfig.json ├── eslint.config.js ├── package.json ├── angular.json └── README.md /projects/dev-app/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx --no-install lint-staged 2 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/diff/diff.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/layout/layout.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no-install commitlint --edit "$1" 2 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /projects/dev-app/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acrodata/code-editor/HEAD/projects/dev-app/src/favicon.ico -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/c.txt: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | printf("Hello World!"); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/clojurescript.txt: -------------------------------------------------------------------------------- 1 | (ns hello-world.core) 2 | 3 | (println "Hello world!") 4 | 5 | ;; ADDED 6 | (defn average [a b] 7 | (/ (+ a b) 2.0)) 8 | -------------------------------------------------------------------------------- /projects/code-editor/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of code-editor 3 | */ 4 | 5 | export * from './code-editor-module'; 6 | export * from './code-editor'; 7 | export * from './diff-editor'; 8 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/diff.txt: -------------------------------------------------------------------------------- 1 | @@ -4,6 +4,5 @@ 2 | - let foo = bar.baz([1, 2, 3]); 3 | - foo = foo + 1; 4 | + const foo = bar.baz([1, 2, 3]) + 1; 5 | console.log(`foo: ${foo}`); 6 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | } 6 | 7 | html, 8 | body { 9 | margin: 0; 10 | height: 100%; 11 | } 12 | 13 | .spacer { 14 | flex: 1; 15 | } 16 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/css.txt: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: lightblue; 3 | } 4 | 5 | h1 { 6 | color: white; 7 | text-align: center; 8 | } 9 | 10 | p { 11 | font-family: verdana; 12 | font-size: 20px; 13 | } 14 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/html.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Page Title 5 | 6 | 7 | 8 |

My First Heading

9 |

My first paragraph.

10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/jsx.txt: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export default () => { 4 | return ( 5 |
6 | Keywords here are not highlighted, for example class or instance. 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/ruby.txt: -------------------------------------------------------------------------------- 1 | # The Greeter class 2 | class Greeter 3 | def initialize(name) 4 | @name = name.capitalize 5 | end 6 | 7 | def salute 8 | puts "Hello #{@name}!" 9 | end 10 | end 11 | 12 | g = Greeter.new("world") 13 | g.salute 14 | -------------------------------------------------------------------------------- /projects/dev-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { appConfig } from './app/app.config'; 3 | import { AppComponent } from './app/app.component'; 4 | 5 | bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)); 6 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig } from '@angular/core'; 2 | import { provideRouter } from '@angular/router'; 3 | 4 | import { routes } from './app.routes'; 5 | 6 | export const appConfig: ApplicationConfig = { 7 | providers: [provideRouter(routes)], 8 | }; 9 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/javascript.txt: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * foo 5 | * 6 | * @param {string[]} items 7 | * @param nada 8 | */ 9 | function foo(items, nada) { 10 | for (var i=0; i [ 3 | `eslint --fix ${filenames.join(' ')}`, 4 | `prettier --write ${filenames.join(' ')}`, 5 | ], 6 | '*.scss': filenames => `stylelint --fix ${filenames.join(' ')}`, 7 | '*.{html,css,js,json,md,yml}': filenames => `git add ${filenames.join(' ')}`, 8 | }; 9 | -------------------------------------------------------------------------------- /projects/code-editor/code-editor-module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CodeEditor } from './code-editor'; 3 | import { DiffEditor } from './diff-editor'; 4 | 5 | @NgModule({ 6 | imports: [CodeEditor, DiffEditor], 7 | exports: [CodeEditor, DiffEditor], 8 | }) 9 | export class CodeEditorModule {} 10 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/objective-c.txt: -------------------------------------------------------------------------------- 1 | #import 2 | #import "Dependency.h" 3 | 4 | @protocol WorldDataSource 5 | @optional 6 | - (NSString*)worldName; 7 | @required 8 | - (BOOL)allowsToLive; 9 | @end 10 | 11 | @property (nonatomic, readonly) NSString *title; 12 | - (IBAction) show; 13 | @end 14 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/json.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "apples", 4 | "count": [12000, 20000], 5 | "description": {"text": "...", "sensitive": false} 6 | }, 7 | { 8 | "title": "oranges", 9 | "count": [17500, null], 10 | "description": {"text": "...", "sensitive": false} 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/http.txt: -------------------------------------------------------------------------------- 1 | POST /task?id=1 HTTP/1.1 2 | Host: example.org 3 | Content-Type: application/json; charset=utf-8 4 | Content-Length: 137 5 | 6 | { 7 | "status": "ok", 8 | "extended": true, 9 | "results": [ 10 | {"value": 0, "type": "int64"}, 11 | {"value": 1.0e+3, "type": "decimal"} 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/tsx.txt: -------------------------------------------------------------------------------- 1 | function MyButton({ title }: { title: string }) { 2 | return ( 3 | 4 | ); 5 | } 6 | 7 | export default function MyApp() { 8 | return ( 9 |
10 |

Welcome to my app

11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /projects/code-editor/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/code-editor", 4 | "lib": { 5 | "entryFile": "public-api.ts" 6 | }, 7 | "allowedNonPeerDependencies": [ 8 | "@codemirror/merge", 9 | "@codemirror/theme-one-dark", 10 | "codemirror" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "htmlWhitespaceSensitivity": "ignore", 4 | "quoteProps": "consistent", 5 | "trailingComma": "es5", 6 | "printWidth": 100, 7 | "singleQuote": true, 8 | "overrides": [ 9 | { 10 | "files": "*.html", 11 | "options": { 12 | "parser": "angular" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /projects/dev-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /projects/code-editor/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "**/*.spec.ts", 12 | "**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /projects/dev-app/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/bash.txt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ###### CONFIG 4 | ACCEPTED_HOSTS="/root/.hag_accepted.conf" 5 | BE_VERBOSE=false 6 | 7 | if [ "$UID" -ne 0 ] 8 | then 9 | echo "Superuser rights required" 10 | exit 2 11 | fi 12 | 13 | genApacheConf(){ 14 | echo -e "# Host ${HOME_DIR}$1/$2 :" 15 | } 16 | 17 | echo '"quoted"' | tr -d \" > text.txt 18 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/clojure.txt: -------------------------------------------------------------------------------- 1 | (def ^:dynamic chunk-size 17) 2 | 3 | (defn next-chunk [rdr] 4 | (let [buf (char-array chunk-size) 5 | s (.read rdr buf)] 6 | (when (pos? s) 7 | (java.nio.CharBuffer/wrap buf 0 s)))) 8 | 9 | (defn chunk-seq [rdr] 10 | (when-let [chunk (next-chunk rdr)] 11 | (cons chunk (lazy-seq (chunk-seq rdr))))) 12 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/python.txt: -------------------------------------------------------------------------------- 1 | @requires_authorization(roles=["ADMIN"]) 2 | def somefunc(param1='', param2=0): 3 | r'''A docstring''' 4 | if param1 > param2: # interesting 5 | print 'Gre\'ater' 6 | return (param2 - param1 + 1 + 0b10l) or None 7 | 8 | class SomeClass: 9 | pass 10 | 11 | >>> message = '''interpreter 12 | ... prompt''' 13 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/mysql.txt: -------------------------------------------------------------------------------- 1 | CREATE TABLE students ( 2 | id INT AUTO_INCREMENT PRIMARY KEY, 3 | name VARCHAR(50) NOT NULL, 4 | age INT NOT NULL, 5 | grade VARCHAR(10) 6 | ); 7 | 8 | INSERT INTO students (name, age, grade) VALUES 9 | ('Alice', 18, 'Sophomore'), 10 | ('Bob', 19, 'Junior'), 11 | ('Charlie', 20, 'Senior'); 12 | 13 | SELECT * FROM students; 14 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": [ 4 | "angular.ng-template", 5 | "cyrilletuzi.angular-schematics", 6 | "esbenp.prettier-vscode", 7 | "stylelint.vscode-stylelint", 8 | "syler.sass-indented", 9 | "editorconfig.editorconfig", 10 | "mrmlnc.vscode-scss" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/coffeescript.txt: -------------------------------------------------------------------------------- 1 | grade = (student, period=(if b? then 7 else 6)) -> 2 | if student.excellentWork 3 | "A+" 4 | else if student.okayStuff 5 | if student.triedHard then "B" else "B-" 6 | else 7 | "C" 8 | 9 | class Animal extends Being 10 | constructor: (@name) -> 11 | 12 | move: (meters) -> 13 | alert @name + " moved #{meters}m." 14 | -------------------------------------------------------------------------------- /projects/code-editor/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/lib", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | import { RouterOutlet } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | imports: [RouterOutlet], 7 | templateUrl: './app.component.html', 8 | styleUrl: './app.component.scss', 9 | encapsulation: ViewEncapsulation.None, 10 | }) 11 | export class AppComponent {} 12 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/swift.txt: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @objc class Person: Entity { 4 | var name: String! 5 | var age: Int! 6 | 7 | init(name: String, age: Int) { 8 | /* /* ... */ */ 9 | } 10 | 11 | // Return a descriptive string for this person 12 | func description(offset: Int = 0) -> String { 13 | return "\(name) is \(age + offset) years old" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/sql.txt: -------------------------------------------------------------------------------- 1 | CREATE TABLE "topic" ( 2 | "id" integer NOT NULL PRIMARY KEY, 3 | "forum_id" integer NOT NULL, 4 | "subject" varchar(255) NOT NULL 5 | ); 6 | ALTER TABLE "topic" 7 | ADD CONSTRAINT forum_id FOREIGN KEY ("forum_id") 8 | REFERENCES "forum" ("id"); 9 | 10 | -- Initials 11 | insert into "topic" ("forum_id", "subject") 12 | values (2, 'D''artagnian'); 13 | -------------------------------------------------------------------------------- /projects/dev-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Codemirror 6 wrapper for Angular 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/julia.txt: -------------------------------------------------------------------------------- 1 | function f(x, y) 2 | x[1] = 42 # mutates x 3 | y = 7 + y # new binding for y, no mutation 4 | return y 5 | end 6 | 7 | fib(n::Integer) = n ≤ 2 ? one(n) : fib(n-1) + fib(n-2) 8 | 9 | map([A, B, C]) do x 10 | if x < 0 && iseven(x) 11 | return 0 12 | elseif x == 0 13 | return 1 14 | else 15 | return x 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /projects/dev-app/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @use '@angular/material' as mat; 3 | @use '@ng-matero/extensions' as mtx; 4 | 5 | html { 6 | color-scheme: light; 7 | 8 | @include mat.theme(( 9 | color: mat.$azure-palette, 10 | typography: Roboto, 11 | density: 0 12 | )); 13 | } 14 | 15 | .theme-dark { 16 | color-scheme: dark; 17 | } 18 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/pug.txt: -------------------------------------------------------------------------------- 1 | a(href='//google.com') Google 2 | | 3 | | 4 | a(class='button' href='//google.com') Google 5 | | 6 | | 7 | a(class='button', href='//google.com') Google 8 | 9 | - var friends = 10 10 | case friends 11 | when 0 12 | p you have no friends 13 | when 1 14 | p you have a friend 15 | default 16 | p you have #{friends} friends 17 | 18 | - for (var x = 0; x < 3; x++) 19 | li item 20 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', 7 | [ 8 | 'build', 9 | 'ci', 10 | 'chore', 11 | 'docs', 12 | 'feat', 13 | 'fix', 14 | 'perf', 15 | 'refactor', 16 | 'revert', 17 | 'style', 18 | 'test', 19 | ], 20 | ], 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/angular_template.txt: -------------------------------------------------------------------------------- 1 |

Todos

2 | 3 | 4 | 5 | @for (todo of todos; track $index) { 6 |

7 | 8 | @if (todo.done) { 9 | {{ todo.text }} 10 | } @else { 11 | {{ todo.text }} 12 | } 13 |

14 | } @empty { 15 |

No todos

16 | } 17 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/php.txt: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/csharp.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | 3 | #pragma warning disable 414, 3021 4 | 5 | namespace MyApplication 6 | { 7 | [Obsolete("...")] 8 | class Program : IInterface 9 | { 10 | public static List JustDoIt(int count) 11 | { 12 | Span numbers = stackalloc int[length]; 13 | Console.WriteLine($"Hello {Name}!"); 14 | return new List(new int[] { 1, 2, 3 }) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/common_lisp.txt: -------------------------------------------------------------------------------- 1 | (defclass bicycle (vehicle) 2 | ((mass :reader bicycle-mass 3 | :initarg :mass 4 | :type real 5 | :documentation "The bike's mass.")) 6 | (:documentation "A bicycle.")) 7 | 8 | (defclass canoe (vehicle) 9 | ((rowers :reader canoe-rowers 10 | :initarg :rowers 11 | :initform 0 12 | :type (integer 0) 13 | :documentation "The number of rowers.")) 14 | (:documentation "A canoe.")) 15 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/liquid.txt: -------------------------------------------------------------------------------- 1 | 2 | {% if customer.name == "kevin" %} 3 | Hey Kevin! 4 | {% elsif customer.name == "anonymous" %} 5 | Hey Anonymous! 6 | {% else %} 7 | Hi Stranger! 8 | {% endif %} 9 | 10 | {% assign handle = "cake" %} 11 | {% case handle %} 12 | {% when "cake" %} 13 | This is a cake 14 | {% when "cookie", "biscuit" %} 15 | This is a cookie 16 | {% else %} 17 | This is not a cake nor a cookie 18 | {% endcase %} 19 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/shell.txt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Counting the number of lines in a list of files 3 | # for loop over arguments 4 | 5 | if [ $# -lt 1 ] 6 | then 7 | echo "Usage: $0 file ..." 8 | exit 1 9 | fi 10 | 11 | echo "$0 counts the lines of code" 12 | l=0 13 | n=0 14 | s=0 15 | for f in $* 16 | do 17 | l=`wc -l $f | sed 's/^\([0-9]*\).*$/\1/'` 18 | echo "$f: $l" 19 | n=$[ $n + 1 ] 20 | s=$[ $s + $l ] 21 | done 22 | 23 | echo "$n files in total, with $s lines in total" 24 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/layout/layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { MatButtonModule } from '@angular/material/button'; 3 | import { MatToolbarModule } from '@angular/material/toolbar'; 4 | import { RouterOutlet } from '@angular/router'; 5 | 6 | @Component({ 7 | selector: 'app-layout', 8 | imports: [RouterOutlet, MatToolbarModule, MatButtonModule], 9 | templateUrl: './layout.component.html', 10 | styleUrl: './layout.component.scss', 11 | }) 12 | export class LayoutComponent {} 13 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/lua.txt: -------------------------------------------------------------------------------- 1 | --[[ function returning the max between two numbers --]] 2 | function max(num1, num2) 3 | 4 | if (num1 > num2) then 5 | result = num1; 6 | else 7 | result = num2; 8 | end 9 | 10 | return result; 11 | end 12 | 13 | function average(...) 14 | result = 0 15 | local arg = {...} 16 | for i,v in ipairs(arg) do 17 | result = result + v 18 | end 19 | return result/#arg 20 | end 21 | 22 | print("The average is",average(10,5,3,4,5,6)) 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [100], 3 | "html.format.wrapLineLength": 100, 4 | "[html]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode" 6 | }, 7 | "[javascript]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode" 9 | }, 10 | "[typescript]": { 11 | "editor.defaultFormatter": "esbenp.prettier-vscode" 12 | }, 13 | "[jsonc]": { 14 | "editor.defaultFormatter": "esbenp.prettier-vscode" 15 | }, 16 | "editor.codeActionsOnSave": { 17 | "source.fixAll.stylelint": "explicit" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/apl.txt: -------------------------------------------------------------------------------- 1 | ∇ FizzBuzz end;i 2 | :For i :In ⍳end 3 | :If 0=15|i 4 | ⎕←'FizzBuzz' 5 | :ElseIf 0=3|i 6 | ⎕←'Fizz' 7 | :ElseIf 0=5|i 8 | ⎕←'Buzz' 9 | :Else 10 | ⎕←i 11 | :EndIf 12 | :EndFor 13 | ∇ 14 | 15 | ⎕ ← data ← (1 2 3 4) (2 5 8 6) (8 6 2 3) (8 7 6 1) 16 | 17 | ]dinput 18 | Sum ← { 19 | ⍺ ← 0 ⍝ Left arg defaults to 0 if not given 20 | 0=≢⍵: ⍺ ⍝ If right arg is empty, return left arg 21 | (⍺+⊃⍵)∇1↓⍵ ⍝ Add head to acc, recur over tail 22 | } 23 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/c++.txt: -------------------------------------------------------------------------------- 1 | class MyClass { // The class 2 | public: // Access specifier 3 | int myNum; // Attribute (int variable) 4 | string myString; // Attribute (string variable) 5 | }; 6 | 7 | int main() { 8 | MyClass myObj; // Create an object of MyClass 9 | 10 | // Access attributes and set values 11 | myObj.myNum = 15; 12 | myObj.myString = "Some text"; 13 | 14 | // Print attribute values 15 | cout << myObj.myNum << "\n"; 16 | cout << myObj.myString; 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/nginx.txt: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server; 4 | 5 | root /var/www/html; 6 | 7 | # Add index.php to the list if you are using PHP 8 | index index.html index.htm index.nginx-debian.html; 9 | 10 | server_name _; 11 | 12 | location / { 13 | # First attempt to serve request as file, then 14 | # as directory, then fall back to displaying a 404. 15 | try_files $uri $uri/ =404; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { LayoutComponent } from './layout/layout.component'; 3 | import { HomeComponent } from './home/home.component'; 4 | import { DiffComponent } from './diff/diff.component'; 5 | 6 | export const routes: Routes = [ 7 | { 8 | path: '', 9 | component: LayoutComponent, 10 | children: [ 11 | { path: '', pathMatch: 'full', redirectTo: 'home' }, 12 | { path: 'home', component: HomeComponent }, 13 | { path: 'diff', component: DiffComponent }, 14 | ], 15 | }, 16 | ]; 17 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/dart.txt: -------------------------------------------------------------------------------- 1 | // Go ahead and modify this example. 2 | 3 | import "dart:html"; 4 | 5 | // Computes the nth Fibonacci number. 6 | int fibonacci(int n) { 7 | if (n < 2) return n; 8 | return fibonacci(n - 1) + fibonacci(n - 2); 9 | } 10 | 11 | // Displays a Fibonacci number. 12 | void main() { 13 | int i = 20; 14 | String message = "fibonacci($i) = ${fibonacci(i)}"; 15 | 16 | // This example uses HTML to display the result and it will appear 17 | // in a nested HTML frame (an iframe). 18 | document.body.append(new HeadingElement.h1()..appendText(message)); 19 | } 20 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/elm.txt: -------------------------------------------------------------------------------- 1 | -- Show a list of items I need to buy at the grocery store. 2 | -- 3 | 4 | import Html exposing (..) 5 | 6 | 7 | main = 8 | div [] 9 | [ h1 [] [ text "My Grocery List" ] 10 | , ul [] 11 | [ li [] [ text "Black Beans" ] 12 | , li [] [ text "Limes" ] 13 | , li [] [ text "Greek Yogurt" ] 14 | , li [] [ text "Cilantro" ] 15 | , li [] [ text "Honey" ] 16 | , li [] [ text "Sweet Potatoes" ] 17 | , li [] [ text "Cumin" ] 18 | , li [] [ text "Chili Powder" ] 19 | , li [] [ text "Quinoa" ] 20 | ] 21 | ] 22 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/haskell.txt: -------------------------------------------------------------------------------- 1 | -- Type annotation (optional) 2 | fib :: Int -> Integer 3 | 4 | -- With self-referencing data 5 | fib n = fibs !! n 6 | where fibs = 0 : scanl (+) 1 fibs 7 | -- 0,1,1,2,3,5,... 8 | 9 | -- Same, coded directly 10 | fib n = fibs !! n 11 | where fibs = 0 : 1 : next fibs 12 | next (a : t@(b:_)) = (a+b) : next t 13 | 14 | -- Similar idea, using zipWith 15 | fib n = fibs !! n 16 | where fibs = 0 : 1 : zipWith (+) fibs (tail fibs) 17 | 18 | -- Using a generator function 19 | fib n = fibs (0,1) !! n 20 | where fibs (a,b) = a : fibs (b,a+b) 21 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/less.txt: -------------------------------------------------------------------------------- 1 | // numbers are converted into the same units 2 | @conversion-1: 5cm + 10mm; // result is 6cm 3 | @conversion-2: 2 - 3cm - 5mm; // result is -1.5cm 4 | 5 | // conversion is impossible 6 | @incompatible-units: 2 + 5px - 3cm; // result is 4px 7 | 8 | // example with variables 9 | @base: 5%; 10 | @filler: @base * 2; // result is 10% 11 | @other: @base + @filler; // result is 15% 12 | 13 | .bordered { 14 | border-top: dotted 1px black; 15 | border-bottom: solid 2px black; 16 | } 17 | 18 | #menu a { 19 | color: #111; 20 | .bordered(); 21 | } 22 | 23 | .post a { 24 | color: red; 25 | .bordered(); 26 | } 27 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/erlang.txt: -------------------------------------------------------------------------------- 1 | %% A process whose only job is to keep a counter. 2 | %% First version 3 | -module(counter). 4 | -export([start/0, codeswitch/1]). 5 | 6 | start() -> loop(0). 7 | 8 | loop(Sum) -> 9 | receive 10 | {increment, Count} -> 11 | loop(Sum+Count); 12 | {counter, Pid} -> 13 | Pid ! {counter, Sum}, 14 | loop(Sum); 15 | code_switch -> 16 | ?MODULE:codeswitch(Sum) 17 | % Force the use of 'codeswitch/1' from the latest MODULE version 18 | end. 19 | 20 | codeswitch(Sum) -> loop(Sum). 21 | 22 | % Print the data using the pretty print specifier 23 | io:format("Here is the data: ~p~n", [Data]). 24 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/home/home.component.scss: -------------------------------------------------------------------------------- 1 | :host ::ng-deep { 2 | .gui-form { 3 | .gui-field-label { 4 | width: 100px; 5 | } 6 | } 7 | 8 | .cm-editor { 9 | border: 1px solid var(--mat-sys-outline-variant); 10 | } 11 | } 12 | 13 | section { 14 | display: grid; 15 | grid-template-columns: 300px 1fr; 16 | max-width: 1200px; 17 | margin: 16px auto; 18 | } 19 | 20 | .config-panel { 21 | position: sticky; 22 | top: 0; 23 | } 24 | 25 | h4 { 26 | padding: 8px 0; 27 | margin: 0; 28 | line-height: 36px; 29 | } 30 | 31 | .editor-wrap { 32 | display: flex; 33 | 34 | * { 35 | flex: 1; 36 | width: 0; 37 | } 38 | } 39 | 40 | textarea { 41 | display: block; 42 | width: 100%; 43 | line-height: 1.38; 44 | } 45 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/r.txt: -------------------------------------------------------------------------------- 1 | Call: 2 | lm(formula = y ~ x) 3 | 4 | Residuals: 5 | 1 2 3 4 5 6 6 | 3.3333 -0.6667 -2.6667 -2.6667 -0.6667 3.3333 7 | 8 | Coefficients: 9 | Estimate Std. Error t value Pr(>|t|) 10 | (Intercept) -9.3333 2.8441 -3.282 0.030453 * 11 | x 7.0000 0.7303 9.585 0.000662 *** 12 | --- 13 | Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 14 | 15 | Residual standard error: 3.055 on 4 degrees of freedom 16 | Multiple R-squared: 0.9583, Adjusted R-squared: 0.9478 17 | F-statistic: 91.88 on 1 and 4 DF, p-value: 0.000662 18 | 19 | > par(mfrow=c(2, 2)) # Request 2x2 plot layout 20 | > plot(lm_1) # Diagnostic plot of regression model 21 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/d.txt: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | 3 | int x = 10; 4 | immutable int y = 30; 5 | const int* p; 6 | 7 | pure int purefunc(int i,const char* q,immutable int* s) { 8 | //writeln("Simple print"); //cannot call impure function 'writeln' 9 | 10 | debug writeln("in foo()"); // ok, impure code allowed in debug statement 11 | // x = i; // error, modifying global state 12 | // i = x; // error, reading mutable global state 13 | // i = *p; // error, reading const global state 14 | i = y; // ok, reading immutable global state 15 | auto myvar = new int; // Can use the new expression: 16 | return i; 17 | } 18 | 19 | void main() { 20 | writeln("Value returned from pure function : ",purefunc(x,null,null)); 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | 44 | .nx/cache 45 | .nx/workspace-data 46 | -------------------------------------------------------------------------------- /projects/code-editor/eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const tseslint = require('typescript-eslint'); 3 | const rootConfig = require('../../eslint.config.js'); 4 | 5 | module.exports = tseslint.config( 6 | ...rootConfig, 7 | { 8 | files: ['**/*.ts'], 9 | rules: { 10 | '@angular-eslint/directive-selector': [ 11 | 'error', 12 | { 13 | type: 'attribute', 14 | prefix: '', 15 | style: 'camelCase', 16 | }, 17 | ], 18 | '@angular-eslint/component-selector': [ 19 | 'error', 20 | { 21 | type: 'element', 22 | prefix: '', 23 | style: 'kebab-case', 24 | }, 25 | ], 26 | }, 27 | }, 28 | { 29 | files: ['**/*.html'], 30 | rules: {}, 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /projects/dev-app/eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const tseslint = require("typescript-eslint"); 3 | const rootConfig = require("../../eslint.config.js"); 4 | 5 | module.exports = tseslint.config( 6 | ...rootConfig, 7 | { 8 | files: ["**/*.ts"], 9 | rules: { 10 | "@angular-eslint/directive-selector": [ 11 | "error", 12 | { 13 | type: "attribute", 14 | prefix: "app", 15 | style: "camelCase", 16 | }, 17 | ], 18 | "@angular-eslint/component-selector": [ 19 | "error", 20 | { 21 | type: "element", 22 | prefix: "app", 23 | style: "kebab-case", 24 | }, 25 | ], 26 | }, 27 | }, 28 | { 29 | files: ["**/*.html"], 30 | rules: {}, 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/asn.1.txt: -------------------------------------------------------------------------------- 1 | TBSCertificate ::= SEQUENCE { 2 | version [0] Version DEFAULT v1, 3 | serialNumber CertificateSerialNumber, 4 | signature AlgorithmIdentifier, 5 | issuer Name, 6 | validity Validity, 7 | subject Name, 8 | subjectPublicKeyInfo SubjectPublicKeyInfo, 9 | issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, 10 | -- If present, version MUST be v2 or v3 11 | subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, 12 | -- If present, version MUST be v2 or v3 13 | extensions [3] Extensions OPTIONAL 14 | -- If present, version MUST be v3 -- } 15 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/sass.txt: -------------------------------------------------------------------------------- 1 | /* style.scss */ 2 | 3 | @use 'base' 4 | 5 | .inverse 6 | background-color: base.$primary-color 7 | color: white 8 | 9 | $font-stack: Helvetica, sans-serif 10 | $primary-color: #333 11 | 12 | body 13 | font: 100% $font-stack 14 | color: $primary-color 15 | 16 | nav 17 | ul 18 | margin: 0 19 | padding: 0 20 | list-style: none 21 | 22 | li 23 | display: inline-block 24 | 25 | a 26 | display: block 27 | padding: 6px 12px 28 | text-decoration: none 29 | 30 | @mixin theme($theme: DarkGray) 31 | background: $theme 32 | box-shadow: 0 0 1px rgba($theme, .25) 33 | color: #fff 34 | 35 | .info 36 | @include theme 37 | 38 | .alert 39 | @include theme($theme: DarkRed) 40 | 41 | .success 42 | @include theme($theme: DarkGreen) 43 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/go.txt: -------------------------------------------------------------------------------- 1 | // Concurrent computation of pi. 2 | // See http://goo.gl/ZuTZM. 3 | // 4 | // This demonstrates Go's ability to handle 5 | // large numbers of concurrent processes. 6 | // It is an unreasonable way to calculate pi. 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "math" 12 | ) 13 | 14 | func main() { 15 | fmt.Println(pi(5000)) 16 | } 17 | 18 | // pi launches n goroutines to compute an 19 | // approximation of pi. 20 | func pi(n int) float64 { 21 | ch := make(chan float64) 22 | for k := 0; k <= n; k++ { 23 | go term(ch, float64(k)) 24 | } 25 | f := 0.0 26 | for k := 0; k <= n; k++ { 27 | f += <-ch 28 | } 29 | return f 30 | } 31 | 32 | func term(ch chan float64, k float64) { 33 | ch <- 4 * math.Pow(-1, k) / (2*k + 1) 34 | } 35 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/perl.txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | =begin 3 | perl example code for Ace 4 | =cut 5 | 6 | use v5.10; 7 | use strict; 8 | use warnings; 9 | 10 | use List::Util qw(first); 11 | my @primes; 12 | 13 | # Put 2 as the first prime so we won't have an empty array 14 | push @primes, 2; 15 | 16 | for my $number_to_check (3 .. 200) { 17 | # Check if the current number is divisible by any previous prime 18 | # if it is, skip to the next number. Use first to bail out as soon 19 | # as we find a prime that divides it. 20 | next if (first {$number_to_check % $_ == 0} @primes); 21 | 22 | # If we reached this point it means $number_to_check is not 23 | # divisable by any prime number that came before it. 24 | push @primes, $number_to_check; 25 | } 26 | 27 | # List out all of the primes 28 | say join(', ', @primes); 29 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/scss.txt: -------------------------------------------------------------------------------- 1 | /* style.scss */ 2 | 3 | @use 'base'; 4 | 5 | .inverse { 6 | background-color: base.$primary-color; 7 | color: white; 8 | } 9 | 10 | $font-stack: Helvetica, sans-serif; 11 | $primary-color: #333; 12 | 13 | body { 14 | font: 100% $font-stack; 15 | color: $primary-color; 16 | } 17 | 18 | nav { 19 | ul { 20 | margin: 0; 21 | padding: 0; 22 | list-style: none; 23 | } 24 | 25 | li { display: inline-block; } 26 | 27 | a { 28 | display: block; 29 | padding: 6px 12px; 30 | text-decoration: none; 31 | } 32 | } 33 | 34 | @mixin theme($theme: DarkGray) { 35 | background: $theme; 36 | box-shadow: 0 0 1px rgba($theme, .25); 37 | color: #fff; 38 | } 39 | 40 | .info { 41 | @include theme; 42 | } 43 | .alert { 44 | @include theme($theme: DarkRed); 45 | } 46 | .success { 47 | @include theme($theme: DarkGreen); 48 | } 49 | 50 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/vue.txt: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 44 | -------------------------------------------------------------------------------- /projects/code-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@acrodata/code-editor", 3 | "version": "0.6.0", 4 | "description": "CodeMirror 6 wrapper for Angular", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "author": "nzbin", 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/acrodata/code-editor.git" 13 | }, 14 | "homepage": "https://acrodata.github.io/code-editor/", 15 | "keywords": [ 16 | "angular", 17 | "codemirror", 18 | "codemirror6", 19 | "code-editor", 20 | "ngx-codemirror" 21 | ], 22 | "peerDependencies": { 23 | "@angular/core": ">=16.0.0", 24 | "@angular/forms": ">=16.0.0" 25 | }, 26 | "dependencies": { 27 | "@codemirror/merge": "^6.0.0", 28 | "@codemirror/theme-one-dark": "^6.0.0", 29 | "codemirror": "^6.0.0", 30 | "tslib": "^2.3.0" 31 | }, 32 | "sideEffects": false 33 | } 34 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/yaml.txt: -------------------------------------------------------------------------------- 1 | # This sample document was taken from wikipedia: 2 | # http://en.wikipedia.org/wiki/YAML#Sample_document 3 | --- 4 | receipt: Oz-Ware Purchase Invoice 5 | date: 2007-08-06 6 | customer: 7 | given: Dorothy 8 | family: Gale 9 | 10 | items: 11 | - part_no: 'A4786' 12 | descrip: Water Bucket (Filled) 13 | price: 1.47 14 | quantity: 4 15 | 16 | - part_no: 'E1628' 17 | descrip: High Heeled "Ruby" Slippers 18 | size: 8 19 | price: 100.27 20 | quantity: 1 21 | version: 1.2.3.4 22 | 23 | bill-to: &id001 24 | street: | 25 | 123 Tornado Alley 26 | Suite 16 27 | city: East Centerville 28 | state: KS 29 | 30 | ship-to: *id001 31 | 32 | specialDelivery: > 33 | Follow the Yellow Brick 34 | Road to the Emerald City. 35 | Pay no attention to the 36 | man behind the curtain. 37 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/vbscript.txt: -------------------------------------------------------------------------------- 1 | myfilename = "C:\Wikipedia - VBScript - Example - Hello World.txt" 2 | MakeHelloWorldFile myfilename 3 | 4 | Sub MakeHelloWorldFile (FileName) 5 | 'Create a new file in C: drive or overwrite existing file 6 | Set FSO = CreateObject("Scripting.FileSystemObject") 7 | If FSO.FileExists(FileName) Then 8 | Answer = MsgBox ("File " & FileName & " exists ... OK to overwrite?", vbOKCancel) 9 | 'If button selected is not OK, then quit now 10 | 'vbOK is a language constant 11 | If Answer <> vbOK Then Exit Sub 12 | Else 13 | 'Confirm OK to create 14 | Answer = MsgBox ("File " & FileName & " ... OK to create?", vbOKCancel) 15 | If Answer <> vbOK Then Exit Sub 16 | End If 17 | 'Create new file (or replace an existing file) 18 | Set FileObject = FSO.CreateTextFile (FileName) 19 | FileObject.WriteLine "Time ... " & Now() 20 | FileObject.WriteLine "Hello World" 21 | FileObject.Close() 22 | MsgBox "File " & FileName & " ... updated." 23 | End Sub 24 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | env: 12 | PUBLISH_DIR: ./dist/dev-app/browser 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up Node.js 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: '22' 21 | 22 | - name: Install dependencies 23 | run: yarn 24 | 25 | - name: Build site 26 | run: npm run build 27 | 28 | - name: Create 404.html 29 | run: cp ${{ env.PUBLISH_DIR }}/index.html ${{ env.PUBLISH_DIR }}/404.html 30 | 31 | - name: Deploy to gh-pages 32 | uses: peaceiris/actions-gh-pages@v3 33 | with: 34 | github_token: ${{ secrets.GITHUB_TOKEN }} 35 | publish_dir: ${{ env.PUBLISH_DIR }} 36 | publish_branch: gh-pages 37 | user_name: 'github-actions[bot]' 38 | user_email: 'github-actions[bot]@users.noreply.github.com' 39 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/diff/diff.component.html: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/layout/layout.component.html: -------------------------------------------------------------------------------- 1 | 2 | Codemirror 6 wrapper for Angular 3 | 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/kotlin.txt: -------------------------------------------------------------------------------- 1 | sealed class Result { 2 | data class Success(val data: T) : Result() 3 | data class Error(val exception: Exception) : Result() 4 | } 5 | 6 | class LoginRepository(private val responseParser: LoginResponseParser) { 7 | private const val loginUrl = "https://example.com/login" 8 | 9 | // Function that makes the network request, blocking the current thread 10 | fun makeLoginRequest( 11 | jsonBody: String 12 | ): Result { 13 | val url = URL(loginUrl) 14 | (url.openConnection() as? HttpURLConnection)?.run { 15 | requestMethod = "POST" 16 | setRequestProperty("Content-Type", "application/json; utf-8") 17 | setRequestProperty("Accept", "application/json") 18 | doOutput = true 19 | outputStream.write(jsonBody.toByteArray()) 20 | return Result.Success(responseParser.parse(inputStream)) 21 | } 22 | return Result.Error(Exception("Cannot open HttpURLConnection")) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Acrodata 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/markdown.txt: -------------------------------------------------------------------------------- 1 | # H1 2 | ## H2 3 | ### H3 4 | #### H4 5 | ##### H5 6 | ###### H6 7 | 8 | Emphasis, aka italics, with *asterisks* or _underscores_. 9 | 10 | Strong emphasis, aka bold, with **asterisks** or __underscores__. 11 | 12 | Combined emphasis with **asterisks and _underscores_**. 13 | 14 | Strikethrough uses two tildes. ~~Scratch this.~~ 15 | 16 | 1. First ordered list item 17 | 2. Another item 18 | ⋅⋅⋅* Unordered sub-list. 19 | 1. Actual numbers don't matter, just that it's a number 20 | ⋅⋅⋅1. Ordered sub-list 21 | 4. And another item. 22 | 23 | ⋅⋅⋅You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown). 24 | 25 | ⋅⋅⋅To have a line break without a paragraph, you will need to use two trailing spaces.⋅⋅ 26 | ⋅⋅⋅Note that this line is separate, but within the same paragraph.⋅⋅ 27 | ⋅⋅⋅(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.) 28 | 29 | 30 | * Unordered list can use asterisks 31 | - Or minuses 32 | + Or pluses 33 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreFiles": [ 3 | "dist/**" 4 | ], 5 | "extends": [ 6 | "stylelint-config-standard", 7 | "stylelint-config-recommended-scss" 8 | ], 9 | "rules": { 10 | "alpha-value-notation": null, 11 | "annotation-no-unknown": null, 12 | "at-rule-empty-line-before": null, 13 | "block-no-empty": null, 14 | "color-function-notation": "legacy", 15 | "function-no-unknown": null, 16 | "import-notation": null, 17 | "no-descending-specificity": null, 18 | "no-empty-source": null, 19 | "media-query-no-invalid": null, 20 | "number-max-precision": null, 21 | "selector-pseudo-element-no-unknown": [ 22 | true, 23 | { 24 | "ignorePseudoElements": [ 25 | "ng-deep" 26 | ] 27 | } 28 | ], 29 | "selector-class-pattern": null, 30 | "selector-type-no-unknown": null, 31 | "value-keyword-case": null, 32 | "scss/at-extend-no-missing-placeholder": null, 33 | "scss/at-if-no-null": null, 34 | "scss/comment-no-empty": null, 35 | "scss/operator-no-unspaced": null, 36 | "scss/operator-no-newline-after": null 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "noImplicitOverride": true, 11 | "noPropertyAccessFromIndexSignature": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "sourceMap": true, 15 | "declaration": false, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "bundler", 18 | "importHelpers": true, 19 | "target": "ES2022", 20 | "module": "ES2022", 21 | "useDefineForClassFields": false, 22 | "lib": [ 23 | "ES2022", 24 | "dom" 25 | ], 26 | "paths": { 27 | "@acrodata/code-editor": [ 28 | "projects/code-editor/public-api" 29 | ] 30 | } 31 | }, 32 | "angularCompilerOptions": { 33 | "enableI18nLegacyMessageIdFormat": false, 34 | "strictInjectionParameters": true, 35 | "strictInputAccessModifiers": true, 36 | "strictTemplates": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/java.txt: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.Vector; 3 | 4 | public class InfiniteLoop { 5 | 6 | /* 7 | * This will cause the program to hang... 8 | * 9 | * Taken from: 10 | * http://www.exploringbinary.com/java-hangs-when-converting-2-2250738585072012e-308/ 11 | */ 12 | @Override 13 | public static void main(String[] args) { 14 | double d = Double.parseDouble("2.2250738585072012e-308"); 15 | 16 | // unreachable code 17 | System.out.println("Value: " + d); 18 | } 19 | } 20 | 21 | String name = "Joan"; String info = STR."My name is \{name}"; 22 | 23 | STR."Today's weather is \{ getFeelsLike() }, with a temperature of \{ getTemperature()++ } degrees \{ getUnit() }" 24 | 25 | String nestedMultilineTemplates() { 26 | return STR.""" 27 | { 28 | "outerKey1": "outerValue1", 29 | "nestedTemplate": "\{ 30 | STR.""" 31 | { 32 | "innerKey": "\{innerValue.get()}" 33 | } 34 | """ 35 | }", 36 | "outerKey2": "outerValue2" 37 | } 38 | """; 39 | } 40 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/diff/diff.component.ts: -------------------------------------------------------------------------------- 1 | import { CodeEditor, DiffEditor, Orientation, RevertControls, Setup } from '@acrodata/code-editor'; 2 | import { Component } from '@angular/core'; 3 | import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { unifiedMergeView } from '@codemirror/merge'; 5 | 6 | @Component({ 7 | selector: 'app-diff', 8 | imports: [DiffEditor, CodeEditor, FormsModule, ReactiveFormsModule], 9 | templateUrl: './diff.component.html', 10 | styleUrl: './diff.component.scss', 11 | }) 12 | export class DiffComponent { 13 | doc = `one 14 | two 15 | three 16 | four 17 | five`; 18 | 19 | doc2 = this.doc.replace(/t/g, 'T') + '\nSix'; 20 | 21 | value = { 22 | original: this.doc, 23 | modified: this.doc2, 24 | }; 25 | 26 | control = new FormControl({ value: this.value, disabled: true }); 27 | 28 | unifiedExts = [ 29 | unifiedMergeView({ 30 | original: this.doc, 31 | gutter: true, 32 | }), 33 | ]; 34 | 35 | setup: Setup = 'minimal'; 36 | orientation: Orientation = 'a-b'; 37 | revertControls: RevertControls = 'a-to-b'; 38 | highlightChanges = true; 39 | gutter = true; 40 | disabled = false; 41 | 42 | log(e: any) { 43 | console.log(e); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/groovy.txt: -------------------------------------------------------------------------------- 1 | //http://groovy.codehaus.org/Martin+Fowler%27s+closure+examples+in+Groovy 2 | 3 | class Employee { 4 | def name, salary 5 | boolean manager 6 | String toString() { return name } 7 | } 8 | 9 | def emps = [new Employee(name:'Guillaume', manager:true, salary:200), 10 | new Employee(name:'Graeme', manager:true, salary:200), 11 | new Employee(name:'Dierk', manager:false, salary:151), 12 | new Employee(name:'Bernd', manager:false, salary:50)] 13 | 14 | def managers(emps) { 15 | emps.findAll { e -> e.isManager() } 16 | } 17 | 18 | assert emps[0..1] == managers(emps) // [Guillaume, Graeme] 19 | 20 | def highPaid(emps) { 21 | threshold = 150 22 | emps.findAll { e -> e.salary > threshold } 23 | } 24 | 25 | assert emps[0..2] == highPaid(emps) // [Guillaume, Graeme, Dierk] 26 | 27 | def paidMore(amount) { 28 | { e -> e.salary > amount} 29 | } 30 | def highPaid = paidMore(150) 31 | 32 | assert highPaid(emps[0]) // true 33 | assert emps[0..2] == emps.findAll(highPaid) 34 | 35 | def filename = 'test.txt' 36 | new File(filename).withReader{ reader -> doSomethingWith(reader) } 37 | 38 | def readersText 39 | def doSomethingWith(reader) { readersText = reader.text } 40 | 41 | assert new File(filename).text == readersText 42 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/pascal.txt: -------------------------------------------------------------------------------- 1 | (***************************************************************************** 2 | * A simple bubble sort program. Reads integers, one per line, and prints * 3 | * them out in sorted order. Blows up if there are more than 49. * 4 | *****************************************************************************) 5 | PROGRAM Sort(input, output); 6 | CONST 7 | (* Max array size. *) 8 | MaxElts = 50; 9 | TYPE 10 | (* Type of the element array. *) 11 | IntArrType = ARRAY [1..MaxElts] OF Integer; 12 | 13 | VAR 14 | (* Indexes, exchange temp, array size. *) 15 | i, j, tmp, size: integer; 16 | 17 | (* Array of ints *) 18 | arr: IntArrType; 19 | 20 | (* Read in the integers. *) 21 | PROCEDURE ReadArr(VAR size: Integer; VAR a: IntArrType); 22 | BEGIN 23 | size := 1; 24 | WHILE NOT eof DO BEGIN 25 | readln(a[size]); 26 | IF NOT eof THEN 27 | size := size + 1 28 | END 29 | END; 30 | 31 | BEGIN 32 | (* Read *) 33 | ReadArr(size, arr); 34 | 35 | (* Sort using bubble sort. *) 36 | FOR i := size - 1 DOWNTO 1 DO 37 | FOR j := 1 TO i DO 38 | IF arr[j] > arr[j + 1] THEN BEGIN 39 | tmp := arr[j]; 40 | arr[j] := arr[j + 1]; 41 | arr[j + 1] := tmp; 42 | END; 43 | 44 | (* Print. *) 45 | FOR i := 1 TO size DO 46 | writeln(arr[i]) 47 | END. 48 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const eslint = require('@eslint/js'); 3 | const tseslint = require('typescript-eslint'); 4 | const angular = require('angular-eslint'); 5 | 6 | module.exports = tseslint.config( 7 | { 8 | files: ['**/*.ts'], 9 | extends: [ 10 | eslint.configs.recommended, 11 | ...tseslint.configs.recommended, 12 | ...tseslint.configs.stylistic, 13 | ...angular.configs.tsRecommended, 14 | ], 15 | processor: angular.processInlineTemplates, 16 | rules: { 17 | 'max-len': [ 18 | 'warn', 19 | { 20 | code: 100, 21 | ignoreComments: true, 22 | ignoreStrings: true, 23 | ignoreTemplateLiterals: true, 24 | }, 25 | ], 26 | 'object-shorthand': ['warn', 'always', { avoidQuotes: true }], 27 | 'quote-props': ['warn', 'consistent-as-needed'], 28 | 'quotes': ['warn', 'single', { allowTemplateLiterals: true }], 29 | 'semi': ['warn', 'always'], 30 | '@typescript-eslint/no-empty-function': 'off', 31 | '@typescript-eslint/no-explicit-any': 'off', 32 | '@typescript-eslint/no-inferrable-types': 'off', 33 | '@typescript-eslint/no-unused-vars': 'off', 34 | '@angular-eslint/component-class-suffix': 'off', 35 | '@angular-eslint/directive-class-suffix': 'off', 36 | '@angular-eslint/no-empty-lifecycle-method': 'off', 37 | '@angular-eslint/no-output-native': 'off', 38 | }, 39 | }, 40 | { 41 | files: ['**/*.html'], 42 | extends: [...angular.configs.templateRecommended, ...angular.configs.templateAccessibility], 43 | rules: { 44 | '@angular-eslint/template/prefer-self-closing-tags': 'warn', 45 | }, 46 | } 47 | ); 48 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/latex.txt: -------------------------------------------------------------------------------- 1 | % This is a template for doing homework assignments in LaTeX 2 | 3 | \documentclass{article} % This command is used to set the type of document you are working on such as an article, book, or presenation 4 | 5 | \usepackage{geometry} % This package allows the editing of the page layout 6 | \usepackage{amsmath} % This package allows the use of a large range of mathematical formula, commands, and symbols 7 | \usepackage{graphicx} % This package allows the importing of images 8 | 9 | \newcommand{\question}[2][]{\begin{flushleft} 10 | \textbf{Question #1}: \textit{#2} 11 | 12 | \end{flushleft}} 13 | \newcommand{\sol}{\textbf{Solution}:} %Use if you want a boldface solution line 14 | \newcommand{\maketitletwo}[2][]{\begin{center} 15 | \Large{\textbf{Assignment #1} 16 | 17 | Course Title} % Name of course here 18 | \vspace{5pt} 19 | 20 | \normalsize{Matthew Frenkel % Your name here 21 | 22 | \today} % Change to due date if preferred 23 | \vspace{15pt} 24 | 25 | \end{center}} 26 | \begin{document} 27 | \maketitletwo[5] % Optional argument is assignment number 28 | %Keep a blank space between maketitletwo and \question[1] 29 | 30 | \question[1]{Here is my first question} 31 | 32 | YOUR SOLUTION HERE 33 | 34 | \question[2]{Here is my second question} 35 | 36 | YOUR SOLUTION HERE 37 | 38 | \question[3]{What is the \Large{$\int_0^2 x^2 \, dx $}\normalsize{. Show all steps}} 39 | 40 | \begin{align*} 41 | \int_0^2 x^2 &= \left. \frac{x^3}{3} \right|_0^2 \\ 42 | &= \frac{2^3}{3}-\frac{0^3}{3}\\ 43 | &= \frac{8}{3} 44 | \end{align*} 45 | \end{document} 46 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/scala.txt: -------------------------------------------------------------------------------- 1 | // http://www.scala-lang.org/node/54 2 | 3 | package examples.actors 4 | 5 | import scala.actors.Actor 6 | import scala.actors.Actor._ 7 | 8 | abstract class PingMessage 9 | case object Start extends PingMessage 10 | case object SendPing extends PingMessage 11 | case object Pong extends PingMessage 12 | 13 | abstract class PongMessage 14 | case object Ping extends PongMessage 15 | case object Stop extends PongMessage 16 | 17 | object pingpong extends Application { 18 | val pong = new Pong 19 | val ping = new Ping(100000, pong) 20 | ping.start 21 | pong.start 22 | ping ! Start 23 | } 24 | 25 | class Ping(count: Int, pong: Actor) extends Actor { 26 | def act() { 27 | println("Ping: Initializing with count "+count+": "+pong) 28 | var pingsLeft = count 29 | loop { 30 | react { 31 | case Start => 32 | println("Ping: starting.") 33 | pong ! Ping 34 | pingsLeft = pingsLeft - 1 35 | case SendPing => 36 | pong ! Ping 37 | pingsLeft = pingsLeft - 1 38 | case Pong => 39 | if (pingsLeft % 1000 == 0) 40 | println("Ping: pong from: "+sender) 41 | if (pingsLeft > 0) 42 | self ! SendPing 43 | else { 44 | println("Ping: Stop.") 45 | pong ! Stop 46 | exit('stop) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | class Pong extends Actor { 54 | def act() { 55 | var pongCount = 0 56 | loop { 57 | react { 58 | case Ping => 59 | if (pongCount % 1000 == 0) 60 | println("Pong: ping "+pongCount+" from "+sender) 61 | sender ! Pong 62 | pongCount = pongCount + 1 63 | case Stop => 64 | println("Pong: Stop.") 65 | exit('stop) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/typescript.txt: -------------------------------------------------------------------------------- 1 | class Greeter { 2 | greeting: string; 3 | constructor (message: string) { 4 | this.greeting = message; 5 | } 6 | greet() { 7 | return "Hello, " + this.greeting; 8 | } 9 | } 10 | 11 | var greeter = new Greeter("world"); 12 | 13 | var button = document.createElement('button') 14 | button.innerText = "Say Hello"; 15 | button.onclick = function() { 16 | alert(greeter.greet()) 17 | } 18 | 19 | document.body.appendChild(button) 20 | 21 | class Snake extends Animal { 22 | move() { 23 | alert("Slithering..."); 24 | super(5); 25 | } 26 | } 27 | 28 | class Horse extends Animal { 29 | move() { 30 | alert("Galloping..."); 31 | super.move(45); 32 | } 33 | } 34 | 35 | module Sayings { 36 | export class Greeter { 37 | greeting: string; 38 | constructor (message: string) { 39 | this.greeting = message; 40 | } 41 | greet() { 42 | return "Hello, " + this.greeting; 43 | } 44 | } 45 | } 46 | module Mankala { 47 | export class Features { 48 | public turnContinues = false; 49 | public seedStoredCount = 0; 50 | public capturedCount = 0; 51 | public spaceCaptured = NoSpace; 52 | 53 | public clear() { 54 | this.turnContinues = false; 55 | this.seedStoredCount = 0; 56 | this.capturedCount = 0; 57 | this.spaceCaptured = NoSpace; 58 | } 59 | 60 | public toString() { 61 | var stringBuilder = ""; 62 | if (this.turnContinues) { 63 | stringBuilder += " turn continues,"; 64 | } 65 | stringBuilder += " stores " + this.seedStoredCount; 66 | if (this.capturedCount > 0) { 67 | stringBuilder += " captures " + this.capturedCount + " from space " + this.spaceCaptured; 68 | } 69 | return stringBuilder; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/rust.txt: -------------------------------------------------------------------------------- 1 | use core::rand::RngUtil; 2 | 3 | fn main() { 4 | for ["Alice", "Bob", "Carol"].each |&name| { 5 | do spawn { 6 | let v = rand::Rng().shuffle([1, 2, 3]); 7 | for v.each |&num| { 8 | print(fmt!("%s says: '%d'\n", name, num + 1)) 9 | } 10 | } 11 | } 12 | } 13 | 14 | fn map(vector: &[T], function: &fn(v: &T) -> U) -> ~[U] { 15 | let mut accumulator = ~[]; 16 | for vec::each(vector) |element| { 17 | accumulator.push(function(element)); 18 | } 19 | return accumulator; 20 | } 21 | 22 | struct ConstGenericStruct([(); N]); 23 | // T constrains by being an argument to GenericTrait. 24 | impl GenericTrait for i32 { /* ... */ } 25 | 26 | // T constrains by being an arguement to GenericStruct 27 | impl Trait for GenericStruct { /* ... */ } 28 | 29 | // Likewise, N constrains by being an argument to ConstGenericStruct 30 | impl Trait for ConstGenericStruct { /* ... */ } 31 | 32 | // T constrains by being in an associated type in a bound for type `U` which is 33 | // itself a generic parameter constraining the trait. 34 | impl GenericTrait for u32 where U: HasAssocType { /* ... */ } 35 | 36 | // Like previous, except the type is `(U, isize)`. `U` appears inside the type 37 | // that includes `T`, and is not the type itself. 38 | impl GenericStruct where (U, isize): HasAssocType { /* ... */ } 39 | 40 | //! - Inner line doc 41 | //!! - Still an inner line doc (but with a bang at the beginning) 42 | 43 | /*! - Inner block doc */ 44 | /*!! - Still an inner block doc (but with a bang at the beginning) */ 45 | 46 | /** - Outer block doc (exactly) 2 asterisks */ 47 | 48 | macro_rules! mac_variant { 49 | ($vis:vis $name:ident) => { 50 | enum $name { 51 | $vis Unit, 52 | 53 | $vis Tuple(u8, u16), 54 | 55 | $vis Struct { f: u8 }, 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 9 |
10 |

11 | 12 |

13 |
14 | 31 | 32 | @if (showOutput) { 33 | 34 | } 35 |
36 | 37 |

Unified diff-editor

38 | 52 |
53 |
54 | 55 |
56 | 62 | 63 |
64 |

Split diff-editor

65 | 75 |
76 |
77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-editor-srcs", 3 | "version": "0.0.0-NOT-USED", 4 | "engines": { 5 | "yarn": ">=1.22.0 <2", 6 | "npm": "Please use Yarn instead of NPM to install dependencies" 7 | }, 8 | "scripts": { 9 | "ng": "ng", 10 | "start": "ng serve dev-app", 11 | "build": "ng build dev-app --base-href=/code-editor/", 12 | "watch": "ng build dev-app --watch --configuration development", 13 | "test": "ng test dev-app", 14 | "lint": "ng lint --fix && npm run lint:scss", 15 | "lint:ts": "eslint \"projects/**/*.ts\" --fix", 16 | "lint:scss": "stylelint \"projects/**/*.scss\" --fix", 17 | "build:lib": "ng build code-editor && cp README.md dist/code-editor", 18 | "publish": "npm run build:lib && cd dist/code-editor && npm publish", 19 | "deploy": "npm run build && ng deploy", 20 | "prepare": "husky install" 21 | }, 22 | "private": true, 23 | "dependencies": { 24 | "@acrodata/gui": "^3.0.0", 25 | "@angular/cdk": "^20.2.13", 26 | "@angular/common": "^20.3.12", 27 | "@angular/compiler": "^20.3.12", 28 | "@angular/core": "^20.3.12", 29 | "@angular/forms": "^20.3.12", 30 | "@angular/material": "^20.2.13", 31 | "@angular/platform-browser": "^20.3.12", 32 | "@angular/router": "^20.3.12", 33 | "@codemirror/language-data": "^6.5.1", 34 | "@codemirror/merge": "^6.10.0", 35 | "@codemirror/theme-one-dark": "^6.1.2", 36 | "@ng-matero/extensions": "^20.3.0", 37 | "codemirror": "^6.0.1", 38 | "lodash-es": "^4.17.21", 39 | "rxjs": "~7.8.0", 40 | "tslib": "^2.3.0", 41 | "zone.js": "~0.15.1" 42 | }, 43 | "devDependencies": { 44 | "@angular/build": "^20.3.10", 45 | "@angular/cli": "^20.3.10", 46 | "@angular/compiler-cli": "^20.3.12", 47 | "@commitlint/cli": "^19.5.0", 48 | "@commitlint/config-conventional": "^19.5.0", 49 | "@types/jasmine": "~5.1.0", 50 | "@types/lodash-es": "^4.17.10", 51 | "@types/node": "^20.12.0", 52 | "angular-cli-ghpages": "^2.0.0", 53 | "angular-eslint": "^20.6.0", 54 | "eslint": "^9.39.0", 55 | "husky": "^9.1.6", 56 | "jasmine-core": "~5.1.0", 57 | "karma": "~6.4.0", 58 | "karma-chrome-launcher": "~3.2.0", 59 | "karma-coverage": "~2.2.0", 60 | "karma-jasmine": "~5.1.0", 61 | "karma-jasmine-html-reporter": "~2.1.0", 62 | "lint-staged": "^15.2.0", 63 | "ng-packagr": "^20.3.2", 64 | "prettier": "^3.6.0", 65 | "stylelint": "^16.5.0", 66 | "stylelint-config-recommended-scss": "^14.0.0", 67 | "stylelint-config-standard": "^36.0.0", 68 | "typescript": "~5.8.3", 69 | "typescript-eslint": "^8.46.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/dockerfile.txt: -------------------------------------------------------------------------------- 1 | # 2 | # example Dockerfile for http://docs.docker.io/en/latest/examples/postgresql_service/ 3 | # 4 | 5 | FROM ubuntu 6 | MAINTAINER SvenDowideit@docker.com 7 | 8 | # Add the PostgreSQL PGP key to verify their Debian packages. 9 | # It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc 10 | RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 11 | 12 | # Add PostgreSQL's repository. It contains the most recent stable release 13 | # of PostgreSQL, ``9.3``. 14 | RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list 15 | 16 | # Update the Ubuntu and PostgreSQL repository indexes 17 | RUN apt-get update 18 | 19 | # Install ``python-software-properties``, ``software-properties-common`` and PostgreSQL 9.3 20 | # There are some warnings (in red) that show up during the build. You can hide 21 | # them by prefixing each apt-get statement with DEBIAN_FRONTEND=noninteractive 22 | RUN apt-get -y -q install python-software-properties software-properties-common 23 | RUN apt-get -y -q install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3 24 | 25 | # Note: The official Debian and Ubuntu images automatically ``apt-get clean`` 26 | # after each ``apt-get`` 27 | 28 | # Run the rest of the commands as the ``postgres`` user created by the ``postgres-9.3`` package when it was ``apt-get installed`` 29 | USER postgres 30 | 31 | # Create a PostgreSQL role named ``docker`` with ``docker`` as the password and 32 | # then create a database `docker` owned by the ``docker`` role. 33 | # Note: here we use ``&&\`` to run commands one after the other - the ``\`` 34 | # allows the RUN command to span multiple lines. 35 | RUN /etc/init.d/postgresql start &&\ 36 | psql --command "CREATE USER docker WITH SUPERUSER PASSWORD 'docker';" &&\ 37 | createdb -O docker docker 38 | 39 | # Adjust PostgreSQL configuration so that remote connections to the 40 | # database are possible. 41 | RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/9.3/main/pg_hba.conf 42 | 43 | # And add ``listen_addresses`` to ``/etc/postgresql/9.3/main/postgresql.conf`` 44 | RUN echo "listen_addresses='*'" >> /etc/postgresql/9.3/main/postgresql.conf 45 | 46 | # Expose the PostgreSQL port 47 | EXPOSE 5432 48 | 49 | # Add VOLUMEs to allow backup of config, logs and databases 50 | VOLUME ["/etc/postgresql", "/var/log/postgresql", "/var/lib/postgresql"] 51 | 52 | # Set the default command to run when starting the container 53 | CMD ["/usr/lib/postgresql/9.3/bin/postgres", "-D", "/var/lib/postgresql/9.3/main", "-c", "config_file=/etc/postgresql/9.3/main/postgresql.conf"] 54 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/xml.txt: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | true 6 | 7 | 26 8 | 25 9 | 21978 10 | 11 | 12 | 14 | 24865670 15 | Continent 16 | Africa 17 | 18 | 20 | 24865675 21 | Continent 22 | Europe 23 | 24 | 26 | 24865673 27 | Continent 28 | South America 29 | 30 | 32 | 28289421 33 | Continent 34 | Antarctic 35 | 36 | 38 | 24865671 39 | Continent 40 | Asia 41 | 42 | 44 | 24865672 45 | Continent 46 | North America 47 | 48 | 50 | 55949070 51 | Continent 52 | Australia 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /projects/dev-app/src/assets/lang_samples/plaintext.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec cursus aliquet sapien, sed rhoncus leo ullamcorper ornare. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus feugiat eleifend nisl, aliquet rhoncus quam scelerisque vel. Morbi eu pellentesque ex. Nam suscipit maximus leo blandit cursus. Aenean sollicitudin nisi luctus, ornare nibh viverra, laoreet ex. Donec eget nibh sit amet dolor ornare elementum. Morbi sollicitudin enim vitae risus pretium vestibulum. Ut pretium hendrerit libero, non vulputate ante volutpat et. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nullam malesuada turpis vitae est porttitor, id tincidunt neque dignissim. Integer rhoncus vestibulum justo in iaculis. Praesent nec augue ut dui scelerisque gravida vel id velit. Donec vehicula feugiat mollis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. 2 | 3 | Praesent diam lorem, luctus quis ullamcorper non, consequat quis orci. Ut vel massa vel nunc sagittis porttitor a vitae ante. Quisque euismod lobortis imperdiet. Vestibulum tincidunt vehicula posuere. Nulla facilisi. Donec sodales imperdiet risus id ullamcorper. Nulla luctus orci tortor, vitae tincidunt urna aliquet nec. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam consequat dapibus massa. Sed ac pharetra magna, in imperdiet neque. Nullam nunc nisi, consequat vel nunc et, sagittis aliquam arcu. Aliquam non orci magna. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed id sem ut sem pulvinar rhoncus. Aenean venenatis nunc eget mi ornare, vitae maximus lacus varius. Quisque quis vestibulum justo. 4 | 5 | Donec euismod luctus volutpat. Donec sed lacinia enim. Vivamus aliquam elit cursus, convallis diam at, volutpat turpis. Sed lacinia nisl in auctor dapibus. Nunc turpis mi, mattis ut rhoncus id, lacinia sed lectus. Donec sodales tellus quis libero gravida pretium et quis magna. Etiam ultricies mollis purus, eget consequat velit. Duis vitae nibh vitae arcu tincidunt congue. Maecenas ut velit in ipsum condimentum dictum quis eget urna. Sed mattis nulla arcu, vitae mattis ligula dictum at. 6 | 7 | Praesent at dignissim dolor. Donec quis placerat sem. Cras vitae placerat sapien, eu sagittis ex. Mauris nec luctus risus. Cras imperdiet semper neque suscipit auctor. Mauris nisl massa, commodo sit amet dignissim id, malesuada sed ante. Praesent varius sapien eget eros vehicula porttitor. 8 | 9 | Mauris auctor nunc in quam tempor, eget consectetur nisi rhoncus. Donec et nulla imperdiet, gravida dui at, accumsan velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin sollicitudin condimentum auctor. Sed lacinia eleifend nisi, id scelerisque leo laoreet sit amet. Morbi congue augue a malesuada pulvinar. Curabitur nec ante finibus, commodo orci vel, aliquam libero. Morbi molestie purus non nunc placerat fermentum. Pellentesque commodo ligula sed pretium aliquam. Praesent ut nibh ex. Vivamus vestibulum velit in leo suscipit, vitae pellentesque urna vulputate. Suspendisse pretium placerat ligula eu ullamcorper. Nam eleifend mi tellus, ut tristique ante ultricies vitae. Quisque venenatis dapibus tellus sit amet mattis. Donec erat arcu, elementum vel nisl at, sagittis vulputate nisi. 10 | -------------------------------------------------------------------------------- /projects/dev-app/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, Component, DestroyRef, OnInit, inject } from '@angular/core'; 2 | import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; 3 | import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { MatButtonModule } from '@angular/material/button'; 5 | 6 | import { CodeEditor, DiffEditor } from '@acrodata/code-editor'; 7 | import { GuiFields, GuiForm } from '@acrodata/gui'; 8 | import { languages } from '@codemirror/language-data'; 9 | import { unifiedMergeView } from '@codemirror/merge'; 10 | 11 | @Component({ 12 | selector: 'app-home', 13 | imports: [FormsModule, ReactiveFormsModule, CodeEditor, DiffEditor, GuiForm, MatButtonModule], 14 | templateUrl: './home.component.html', 15 | styleUrl: './home.component.scss', 16 | }) 17 | export class HomeComponent implements OnInit, AfterViewInit { 18 | private readonly destroyRef = inject(DestroyRef); 19 | 20 | languages = languages; 21 | 22 | form = new FormGroup({}); 23 | 24 | config: GuiFields = { 25 | language: { 26 | type: 'combobox', 27 | name: 'Language', 28 | options: languages 29 | .map(lang => ({ label: lang.name, value: lang.name.toLowerCase() })) 30 | .sort((a, b) => a.label.localeCompare(b.label)), 31 | }, 32 | theme: { 33 | type: 'buttonToggle', 34 | name: 'Theme', 35 | options: [ 36 | { label: 'Light', value: 'light' }, 37 | { label: 'Dark', value: 'dark' }, 38 | ], 39 | }, 40 | setup: { 41 | type: 'buttonToggle', 42 | name: 'Setup', 43 | options: [ 44 | { label: 'Basic', value: 'basic' }, 45 | { label: 'Minimal', value: 'minimal' }, 46 | { label: 'None', value: '' }, 47 | ], 48 | }, 49 | disabled: { 50 | type: 'switch', 51 | name: 'Disabled', 52 | }, 53 | readonly: { 54 | type: 'switch', 55 | name: 'Readonly', 56 | }, 57 | placeholder: { 58 | type: 'text', 59 | name: 'Placeholder', 60 | }, 61 | indentWithTab: { 62 | type: 'switch', 63 | name: 'Indent with tab', 64 | }, 65 | indentUnit: { 66 | type: 'text', 67 | name: 'Indent unit', 68 | }, 69 | lineWrapping: { 70 | type: 'switch', 71 | name: 'Line wrapping', 72 | }, 73 | highlightWhitespace: { 74 | type: 'switch', 75 | name: 'Highlight whitespace', 76 | }, 77 | }; 78 | 79 | options: any = { 80 | language: 'javascript', 81 | theme: 'light', 82 | setup: 'basic', 83 | disabled: false, 84 | readonly: false, 85 | placeholder: 'Type your code here...', 86 | indentWithTab: false, 87 | indentUnit: '', 88 | lineWrapping: false, 89 | highlightWhitespace: false, 90 | }; 91 | 92 | code = ''; 93 | 94 | showOutput = false; 95 | 96 | ngOnInit(): void { 97 | this.getLangSample('javascript'); 98 | } 99 | 100 | ngAfterViewInit(): void { 101 | this.form 102 | .get('language') 103 | ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) 104 | .subscribe((lang: string) => { 105 | console.log(lang); 106 | this.getLangSample(lang.replace(' ', '_').replace('#', 'sharp')); 107 | }); 108 | } 109 | 110 | getLangSample(lang: string) { 111 | fetch(`assets/lang_samples/${lang}.txt`).then(async response => { 112 | if (response.ok) { 113 | this.code = await response.text(); 114 | } else { 115 | this.code = ''; 116 | } 117 | }); 118 | } 119 | 120 | log(e: any) { 121 | console.log(e); 122 | } 123 | 124 | originalCode = `one 125 | two 126 | three 127 | four 128 | five`; 129 | modifiedCode = this.originalCode.replace(/t/g, 'T') + '\nSix'; 130 | 131 | unifiedExts = [ 132 | unifiedMergeView({ 133 | original: this.originalCode, 134 | }), 135 | ]; 136 | 137 | config2: GuiFields = { 138 | orientation: { 139 | type: 'buttonToggle', 140 | name: 'Orientation', 141 | options: [ 142 | { label: 'a-b', value: 'a-b' }, 143 | { label: 'b-a', value: 'b-a' }, 144 | ], 145 | }, 146 | revertControls: { 147 | type: 'buttonToggle', 148 | name: 'Revert controls', 149 | options: [ 150 | { label: 'a-to-b', value: 'a-to-b' }, 151 | { label: 'b-to-a', value: 'b-to-a' }, 152 | { label: 'none', value: '' }, 153 | ], 154 | }, 155 | highlightChanges: { 156 | type: 'switch', 157 | name: 'Highlight changes', 158 | }, 159 | gutter: { 160 | type: 'switch', 161 | name: 'Gutter', 162 | }, 163 | }; 164 | 165 | options2: any = { 166 | orientation: 'a-b', 167 | revertControls: '', 168 | highlightChanges: true, 169 | gutter: true, 170 | }; 171 | } 172 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "code-editor": { 7 | "projectType": "library", 8 | "root": "projects/code-editor", 9 | "sourceRoot": "projects/code-editor/src", 10 | "prefix": "", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular/build:ng-packagr", 14 | "options": { 15 | "project": "projects/code-editor/ng-package.json" 16 | }, 17 | "configurations": { 18 | "production": { 19 | "tsConfig": "projects/code-editor/tsconfig.lib.prod.json" 20 | }, 21 | "development": { 22 | "tsConfig": "projects/code-editor/tsconfig.lib.json" 23 | } 24 | }, 25 | "defaultConfiguration": "production" 26 | }, 27 | "test": { 28 | "builder": "@angular/build:karma", 29 | "options": { 30 | "tsConfig": "projects/code-editor/tsconfig.spec.json", 31 | "polyfills": [ 32 | "zone.js", 33 | "zone.js/testing" 34 | ] 35 | } 36 | }, 37 | "lint": { 38 | "builder": "@angular-eslint/builder:lint", 39 | "options": { 40 | "lintFilePatterns": [ 41 | "projects/code-editor/**/*.ts", 42 | "projects/code-editor/**/*.html" 43 | ], 44 | "eslintConfig": "projects/code-editor/eslint.config.js" 45 | } 46 | } 47 | } 48 | }, 49 | "dev-app": { 50 | "projectType": "application", 51 | "schematics": { 52 | "@schematics/angular:component": { 53 | "style": "scss" 54 | } 55 | }, 56 | "root": "projects/dev-app", 57 | "sourceRoot": "projects/dev-app/src", 58 | "prefix": "app", 59 | "architect": { 60 | "build": { 61 | "builder": "@angular/build:application", 62 | "options": { 63 | "outputPath": "dist/dev-app", 64 | "index": "projects/dev-app/src/index.html", 65 | "browser": "projects/dev-app/src/main.ts", 66 | "polyfills": [ 67 | "zone.js" 68 | ], 69 | "tsConfig": "projects/dev-app/tsconfig.app.json", 70 | "inlineStyleLanguage": "scss", 71 | "assets": [ 72 | "projects/dev-app/src/favicon.ico", 73 | "projects/dev-app/src/assets" 74 | ], 75 | "styles": [ 76 | "projects/dev-app/src/styles.scss" 77 | ], 78 | "scripts": [] 79 | }, 80 | "configurations": { 81 | "production": { 82 | "budgets": [ 83 | { 84 | "type": "initial", 85 | "maximumWarning": "2mb" 86 | }, 87 | { 88 | "type": "anyComponentStyle", 89 | "maximumWarning": "4kb" 90 | } 91 | ], 92 | "outputHashing": "all" 93 | }, 94 | "development": { 95 | "optimization": false, 96 | "extractLicenses": false, 97 | "sourceMap": true 98 | } 99 | }, 100 | "defaultConfiguration": "production" 101 | }, 102 | "serve": { 103 | "builder": "@angular/build:dev-server", 104 | "configurations": { 105 | "production": { 106 | "buildTarget": "dev-app:build:production" 107 | }, 108 | "development": { 109 | "buildTarget": "dev-app:build:development" 110 | } 111 | }, 112 | "defaultConfiguration": "development" 113 | }, 114 | "extract-i18n": { 115 | "builder": "@angular/build:extract-i18n", 116 | "options": { 117 | "buildTarget": "dev-app:build" 118 | } 119 | }, 120 | "test": { 121 | "builder": "@angular/build:karma", 122 | "options": { 123 | "polyfills": [ 124 | "zone.js", 125 | "zone.js/testing" 126 | ], 127 | "tsConfig": "projects/dev-app/tsconfig.spec.json", 128 | "inlineStyleLanguage": "scss", 129 | "assets": [ 130 | "projects/dev-app/src/favicon.ico", 131 | "projects/dev-app/src/assets" 132 | ], 133 | "styles": [ 134 | "projects/dev-app/src/styles.scss" 135 | ], 136 | "scripts": [] 137 | } 138 | }, 139 | "lint": { 140 | "builder": "@angular-eslint/builder:lint", 141 | "options": { 142 | "lintFilePatterns": [ 143 | "projects/dev-app/**/*.ts", 144 | "projects/dev-app/**/*.html" 145 | ], 146 | "eslintConfig": "projects/dev-app/eslint.config.js" 147 | } 148 | }, 149 | "deploy": { 150 | "builder": "angular-cli-ghpages:deploy", 151 | "options": { 152 | "baseHref": "/code-editor/", 153 | "remote": "origin" 154 | } 155 | } 156 | } 157 | } 158 | }, 159 | "cli": { 160 | "analytics": false 161 | }, 162 | "schematics": { 163 | "@schematics/angular:component": { 164 | "type": "component" 165 | }, 166 | "@schematics/angular:directive": { 167 | "type": "directive" 168 | }, 169 | "@schematics/angular:service": { 170 | "type": "service" 171 | }, 172 | "@schematics/angular:guard": { 173 | "typeSeparator": "." 174 | }, 175 | "@schematics/angular:interceptor": { 176 | "typeSeparator": "." 177 | }, 178 | "@schematics/angular:module": { 179 | "typeSeparator": "." 180 | }, 181 | "@schematics/angular:pipe": { 182 | "typeSeparator": "." 183 | }, 184 | "@schematics/angular:resolver": { 185 | "typeSeparator": "." 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Editor 2 | 3 | [![npm](https://img.shields.io/npm/v/@acrodata/code-editor.svg)](https://www.npmjs.com/package/@acrodata/code-editor) 4 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/acrodata/code-editor/blob/main/LICENSE) 5 | 6 | codemirror+angular 7 | 8 | CodeMirror 6 wrapper for Angular 9 | 10 | #### Quick links 11 | 12 | [Documentation](https://github.com/acrodata/code-editor?tab=readme-ov-file#code-editor) | 13 | [Playground](https://acrodata.github.io/code-editor/) 14 | 15 | ## Installation 16 | 17 | ```bash 18 | npm install @acrodata/code-editor --save 19 | ``` 20 | 21 | ## Usage 22 | 23 | ### Code Editor 24 | 25 | ```ts 26 | import { Component } from '@angular/core'; 27 | import { FormsModule } from '@angular/forms'; 28 | import { CodeEditor } from '@acrodata/code-editor'; 29 | 30 | @Component({ 31 | selector: 'your-app', 32 | template: ``, 33 | imports: [FormsModule, CodeEditor], 34 | }) 35 | export class YourAppComponent { 36 | value = `console.log("Hello world")`; 37 | } 38 | ``` 39 | 40 | ### Diff Editor 41 | 42 | ```ts 43 | import { Component } from '@angular/core'; 44 | import { FormsModule } from '@angular/forms'; 45 | import { DiffEditor } from '@acrodata/code-editor'; 46 | 47 | @Component({ 48 | selector: 'your-app', 49 | template: ``, 50 | imports: [FormsModule, DiffEditor], 51 | }) 52 | export class YourAppComponent { 53 | value = { 54 | original: `bar`; 55 | modified: `foo`; 56 | } 57 | } 58 | ``` 59 | 60 | ## API 61 | 62 | ### Code Editor 63 | 64 | | Name | Type | Default | Description | 65 | | --------------------- | ---------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | 66 | | [value] | string | `''` | The editor's value. | 67 | | [root] | Document \| ShadowRoot | `undefined` | EditorView's [root][_root]. | 68 | | [autoFocus] | boolean | `false` | Whether focus on the editor after init. | 69 | | [disabled] | boolean | `false` | Whether the editor is disabled. | 70 | | [readonly] | boolean | `false` | Whether the editor is readonly. | 71 | | [theme] | Theme | `light` | The editor's theme. | 72 | | [placeholder] | string | `''` | The editor's placecholder. | 73 | | [indentWithTab] | boolean | `false` | Whether indent with Tab key. | 74 | | [indentUnit] | string | `''` | Should be a string consisting either entirely of the same whitespace character. | 75 | | [lineWrapping] | boolean | `false` | Whether the editor wraps lines. | 76 | | [highlightWhitespace] | boolean | `false` | Whether highlight the whitespace. | 77 | | [languages] | LanguageDescription[] | `[]` | An array of language descriptions for known [language-data][_language-data]. | 78 | | [language] | string | `''` | The editor's language. You should set the `languages` prop at first. | 79 | | [setup] | Setup | `'basic'` | The editor's built-in setup. The value can be set to [`basic`][_basicSetup], [`minimal`][_minimalSetup] or `null`. | 80 | | [extensions] | Extension[] | `[]` | It will be appended to the root [extensions][_extensions]. | 81 | | (change) | EventEmitter | `-` | Event emitted when the editor's value changes. | 82 | | (focus) | EventEmitter | `-` | Event emitted when focus on the editor. | 83 | | (blur) | EventEmitter | `-` | Event emitted when the editor has lost focus. | 84 | 85 | ### Diff Editor 86 | 87 | | Name | Type | Default | Description | 88 | | --------------------- | ------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | 89 | | [setup] | Setup | `'basic'` | The editor's built-in setup. The value can be set to [`basic`][_basicSetup], [`minimal`][_minimalSetup] or `null`. | 90 | | [originalValue] | string | `''` | The diff-editor's original value. | 91 | | [originalExtensions] | Extension[] | `[]` | The MergeView original config's [extensions][_extensions]. | 92 | | [modifiedValue] | string | `''` | The diff-editor's modified value. | 93 | | [modifiedExtensions] | Extension[] | `[]` | The MergeView modified config's [extensions][_extensions]. | 94 | | [orientation] | Orientation | `undefined` | Controls whether editor A or editor B is shown first. Defaults to `"a-b"`. | 95 | | [revertControls] | RevertControls | `undefined` | Controls whether revert controls are shown between changed chunks. | 96 | | [renderRevertControl] | RenderRevertControl | `undefined` | When given, this function is called to render the button to revert a chunk. | 97 | | [highlightChanges] | boolean | `true` | By default, the merge view will mark inserted and deleted text in changed chunks. | 98 | | [gutter] | boolean | `true` | Controls whether a gutter marker is shown next to changed lines. | 99 | | [disabled] | boolean | `false` | Whether the diff-editor is disabled. | 100 | | [collapseUnchanged] | { margin?: number; minSize?: number } | `undefined` | When given, long stretches of unchanged text are collapsed. | 101 | | [diffConfig] | DiffConfig | `undefined` | Pass options to the diff algorithm. | 102 | | (originalValueChange) | EventEmitter | `-` | Event emitted when the editor's original value changes. | 103 | | (originalFocus) | EventEmitter | `-` | Event emitted when focus on the original editor. | 104 | | (originalBlur) | EventEmitter | `-` | Event emitted when blur on the original editor. | 105 | | (modifiedValueChange) | EventEmitter | `-` | Event emitted when the editor's modified value changes. | 106 | | (modifiedFocus) | EventEmitter | `-` | Event emitted when focus on the modified editor. | 107 | | (modifiedBlur) | EventEmitter | `-` | Event emitted when blur on the modified editor. | 108 | 109 | ## License 110 | 111 | MIT 112 | 113 | [_root]: https://codemirror.net/docs/ref/#view.EditorView.root 114 | [_language-data]: https://github.com/codemirror/language-data/blob/main/src/language-data.ts 115 | [_basicSetup]: https://codemirror.net/docs/ref/#codemirror.basicSetup 116 | [_minimalSetup]: https://codemirror.net/docs/ref/#codemirror.minimalSetup 117 | [_extensions]: https://codemirror.net/docs/ref/#state.EditorStateConfig.extensions 118 | -------------------------------------------------------------------------------- /projects/code-editor/diff-editor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | Component, 4 | ElementRef, 5 | EventEmitter, 6 | Input, 7 | OnChanges, 8 | OnDestroy, 9 | OnInit, 10 | Output, 11 | SimpleChanges, 12 | ViewEncapsulation, 13 | booleanAttribute, 14 | forwardRef, 15 | inject, 16 | } from '@angular/core'; 17 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; 18 | 19 | import { DiffConfig, MergeView } from '@codemirror/merge'; 20 | import { Compartment, Extension } from '@codemirror/state'; 21 | import { EditorView } from '@codemirror/view'; 22 | import { basicSetup, minimalSetup } from 'codemirror'; 23 | 24 | import { External, Setup } from './code-editor'; 25 | 26 | export type Orientation = 'a-b' | 'b-a'; 27 | export type RevertControls = 'a-to-b' | 'b-to-a'; 28 | export type RenderRevertControl = () => HTMLElement; 29 | 30 | export interface DiffEditorModel { 31 | original: string; 32 | modified: string; 33 | } 34 | 35 | @Component({ 36 | selector: 'diff-editor', 37 | template: ``, 38 | styles: ` 39 | .diff-editor { 40 | display: block; 41 | 42 | .cm-mergeView, 43 | .cm-mergeViewEditors { 44 | height: 100%; 45 | } 46 | 47 | .cm-mergeView .cm-editor, 48 | .cm-mergeView .cm-scroller { 49 | height: 100% !important; 50 | } 51 | } 52 | `, 53 | host: { 54 | class: 'diff-editor', 55 | }, 56 | encapsulation: ViewEncapsulation.None, 57 | changeDetection: ChangeDetectionStrategy.OnPush, 58 | providers: [ 59 | { 60 | provide: NG_VALUE_ACCESSOR, 61 | useExisting: forwardRef(() => DiffEditor), 62 | multi: true, 63 | }, 64 | ], 65 | }) 66 | export class DiffEditor implements OnChanges, OnInit, OnDestroy, ControlValueAccessor { 67 | private _elementRef = inject>(ElementRef); 68 | 69 | /** 70 | * The editor's built-in setup. The value can be set to 71 | * [`basic`](https://codemirror.net/docs/ref/#codemirror.basicSetup), 72 | * [`minimal`](https://codemirror.net/docs/ref/#codemirror.minimalSetup) or `null`. 73 | * 74 | * Don't support change dynamically! 75 | */ 76 | @Input() setup: Setup = 'basic'; 77 | 78 | /** The diff-editor's original value. */ 79 | @Input() originalValue: string = ''; 80 | 81 | /** 82 | * The MergeView original config's 83 | * [extensions](https://codemirror.net/docs/ref/#state.EditorStateConfig.extensions). 84 | * 85 | * Don't support change dynamically! 86 | */ 87 | @Input() originalExtensions: Extension[] = []; 88 | 89 | /** The diff-editor's modified value. */ 90 | @Input() modifiedValue: string = ''; 91 | 92 | /** 93 | * The MergeView modified config's 94 | * [extensions](https://codemirror.net/docs/ref/#state.EditorStateConfig.extensions). 95 | * 96 | * Don't support change dynamically! 97 | */ 98 | @Input() modifiedExtensions: Extension[] = []; 99 | 100 | /** Controls whether editor A or editor B is shown first. Defaults to `"a-b"`. */ 101 | @Input() orientation?: Orientation; 102 | 103 | /** Controls whether revert controls are shown between changed chunks. */ 104 | @Input() revertControls?: RevertControls; 105 | 106 | /** When given, this function is called to render the button to revert a chunk. */ 107 | @Input() renderRevertControl?: RenderRevertControl; 108 | 109 | /** 110 | * By default, the merge view will mark inserted and deleted text 111 | * in changed chunks. Set this to false to turn that off. 112 | */ 113 | @Input({ transform: booleanAttribute }) highlightChanges = true; 114 | 115 | /** Controls whether a gutter marker is shown next to changed lines. */ 116 | @Input({ transform: booleanAttribute }) gutter = true; 117 | 118 | /** Whether the diff-editor is disabled. */ 119 | @Input({ transform: booleanAttribute }) disabled = false; 120 | 121 | /** 122 | * When given, long stretches of unchanged text are collapsed. 123 | * `margin` gives the number of lines to leave visible after/before 124 | * a change (default is 3), and `minSize` gives the minimum amount 125 | * of collapsible lines that need to be present (defaults to 4). 126 | */ 127 | @Input() collapseUnchanged?: { margin?: number; minSize?: number }; 128 | 129 | /** Pass options to the diff algorithm. */ 130 | @Input() diffConfig?: DiffConfig; 131 | 132 | /** Event emitted when the editor's original value changes. */ 133 | @Output() originalValueChange = new EventEmitter(); 134 | 135 | /** Event emitted when focus on the original editor. */ 136 | @Output() originalFocus = new EventEmitter(); 137 | 138 | /** Event emitted when blur on the original editor. */ 139 | @Output() originalBlur = new EventEmitter(); 140 | 141 | /** Event emitted when the editor's modified value changes. */ 142 | @Output() modifiedValueChange = new EventEmitter(); 143 | 144 | /** Event emitted when focus on the modified editor. */ 145 | @Output() modifiedFocus = new EventEmitter(); 146 | 147 | /** Event emitted when blur on the modified editor. */ 148 | @Output() modifiedBlur = new EventEmitter(); 149 | 150 | private _onChange: (value: DiffEditorModel) => void = () => {}; 151 | private _onTouched: () => void = () => {}; 152 | 153 | /** The merge view instance. */ 154 | mergeView?: MergeView; 155 | 156 | private _updateListener = (editor: 'a' | 'b') => { 157 | return EditorView.updateListener.of(vu => { 158 | if (vu.docChanged && !vu.transactions.some(tr => tr.annotation(External))) { 159 | const value = vu.state.doc.toString(); 160 | if (editor == 'a') { 161 | this._onChange({ original: value, modified: this.modifiedValue }); 162 | this.originalValue = value; 163 | this.originalValueChange.emit(value); 164 | } else if (editor == 'b') { 165 | this._onChange({ original: this.originalValue, modified: value }); 166 | this.modifiedValue = value; 167 | this.modifiedValueChange.emit(value); 168 | } 169 | } 170 | }); 171 | }; 172 | 173 | private _editableConf = new Compartment(); 174 | 175 | ngOnChanges(changes: SimpleChanges): void { 176 | if (changes['originalValue']) { 177 | this.setValue('a', this.originalValue); 178 | } 179 | if (changes['modifiedValue']) { 180 | this.setValue('b', this.modifiedValue); 181 | } 182 | if (changes['orientation']) { 183 | this.mergeView?.reconfigure({ orientation: this.orientation }); 184 | } 185 | if (changes['revertControls']) { 186 | this.mergeView?.reconfigure({ revertControls: this.revertControls }); 187 | } 188 | if (changes['renderRevertControl']) { 189 | this.mergeView?.reconfigure({ renderRevertControl: this.renderRevertControl }); 190 | } 191 | if (changes['highlightChanges']) { 192 | this.mergeView?.reconfigure({ highlightChanges: this.highlightChanges }); 193 | } 194 | if (changes['gutter']) { 195 | this.mergeView?.reconfigure({ gutter: this.gutter }); 196 | } 197 | if (changes['collapseUnchanged']) { 198 | this.mergeView?.reconfigure({ collapseUnchanged: this.collapseUnchanged }); 199 | } 200 | if (changes['diffConfig']) { 201 | this.mergeView?.reconfigure({ diffConfig: this.diffConfig }); 202 | } 203 | if (changes['disabled']) { 204 | this.setEditable('a', !this.disabled); 205 | this.setEditable('b', !this.disabled); 206 | } 207 | } 208 | 209 | ngOnInit(): void { 210 | this.mergeView = new MergeView({ 211 | parent: this._elementRef.nativeElement, 212 | a: { 213 | doc: this.originalValue, 214 | extensions: [ 215 | this._updateListener('a'), 216 | this._editableConf.of([]), 217 | this.setup === 'basic' ? basicSetup : this.setup === 'minimal' ? minimalSetup : [], 218 | ...this.originalExtensions, 219 | ], 220 | }, 221 | b: { 222 | doc: this.modifiedValue, 223 | extensions: [ 224 | this._updateListener('b'), 225 | this._editableConf.of([]), 226 | this.setup === 'basic' ? basicSetup : this.setup === 'minimal' ? minimalSetup : [], 227 | ...this.modifiedExtensions, 228 | ], 229 | }, 230 | orientation: this.orientation, 231 | revertControls: this.revertControls, 232 | renderRevertControl: this.renderRevertControl, 233 | highlightChanges: this.highlightChanges, 234 | gutter: this.gutter, 235 | collapseUnchanged: this.collapseUnchanged, 236 | diffConfig: this.diffConfig, 237 | }); 238 | 239 | this.mergeView?.a.contentDOM.addEventListener('focus', () => { 240 | this._onTouched(); 241 | this.originalFocus.emit(); 242 | }); 243 | 244 | this.mergeView?.a.contentDOM.addEventListener('blur', () => { 245 | this._onTouched(); 246 | this.originalBlur.emit(); 247 | }); 248 | 249 | this.mergeView?.b.contentDOM.addEventListener('focus', () => { 250 | this._onTouched(); 251 | this.modifiedFocus.emit(); 252 | }); 253 | 254 | this.mergeView?.b.contentDOM.addEventListener('blur', () => { 255 | this._onTouched(); 256 | this.modifiedBlur.emit(); 257 | }); 258 | 259 | this.setEditable('a', !this.disabled); 260 | this.setEditable('b', !this.disabled); 261 | } 262 | 263 | ngOnDestroy(): void { 264 | this.mergeView?.destroy(); 265 | } 266 | 267 | writeValue(value: DiffEditorModel): void { 268 | if (this.mergeView && value != null && typeof value === 'object') { 269 | this.originalValue = value.original; 270 | this.modifiedValue = value.modified; 271 | this.setValue('a', value.original); 272 | this.setValue('b', value.modified); 273 | } 274 | } 275 | 276 | registerOnChange(fn: (value: DiffEditorModel) => void) { 277 | this._onChange = fn; 278 | } 279 | 280 | registerOnTouched(fn: () => void) { 281 | this._onTouched = fn; 282 | } 283 | 284 | setDisabledState(isDisabled: boolean) { 285 | this.disabled = isDisabled; 286 | this.setEditable('a', !isDisabled); 287 | this.setEditable('b', !isDisabled); 288 | } 289 | 290 | /** Sets diff-editor's value. */ 291 | setValue(editor: 'a' | 'b', value: string) { 292 | this.mergeView?.[editor].dispatch({ 293 | changes: { from: 0, to: this.mergeView[editor].state.doc.length, insert: value }, 294 | }); 295 | } 296 | 297 | /** Sets diff-editor's editable state. */ 298 | setEditable(editor: 'a' | 'b', value: boolean) { 299 | this.mergeView?.[editor].dispatch({ 300 | effects: this._editableConf.reconfigure(EditorView.editable.of(value)), 301 | }); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /projects/code-editor/code-editor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | Component, 4 | ElementRef, 5 | EventEmitter, 6 | Input, 7 | OnChanges, 8 | OnDestroy, 9 | OnInit, 10 | Output, 11 | SimpleChanges, 12 | ViewEncapsulation, 13 | booleanAttribute, 14 | forwardRef, 15 | inject, 16 | } from '@angular/core'; 17 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; 18 | 19 | import { indentWithTab } from '@codemirror/commands'; 20 | import { LanguageDescription, indentUnit } from '@codemirror/language'; 21 | import { 22 | Annotation, 23 | Compartment, 24 | EditorState, 25 | Extension, 26 | StateEffect, 27 | Transaction, 28 | } from '@codemirror/state'; 29 | import { oneDark } from '@codemirror/theme-one-dark'; 30 | import { EditorView, highlightWhitespace, keymap, placeholder } from '@codemirror/view'; 31 | import { basicSetup, minimalSetup } from 'codemirror'; 32 | 33 | export type Theme = 'light' | 'dark' | Extension; 34 | export type Setup = 'basic' | 'minimal' | null; 35 | 36 | export const External = Annotation.define(); 37 | 38 | @Component({ 39 | selector: 'code-editor', 40 | template: ``, 41 | styles: ` 42 | .code-editor { 43 | display: block; 44 | 45 | .cm-editor { 46 | height: 100%; 47 | } 48 | } 49 | `, 50 | host: { 51 | class: 'code-editor', 52 | }, 53 | encapsulation: ViewEncapsulation.None, 54 | changeDetection: ChangeDetectionStrategy.OnPush, 55 | providers: [ 56 | { 57 | provide: NG_VALUE_ACCESSOR, 58 | useExisting: forwardRef(() => CodeEditor), 59 | multi: true, 60 | }, 61 | ], 62 | }) 63 | export class CodeEditor implements OnChanges, OnInit, OnDestroy, ControlValueAccessor { 64 | private _elementRef = inject>(ElementRef); 65 | 66 | /** 67 | * EditorView's [root](https://codemirror.net/docs/ref/#view.EditorView.root). 68 | * 69 | * Don't support change dynamically! 70 | */ 71 | @Input() root?: Document | ShadowRoot; 72 | 73 | /** 74 | * Whether focus on the editor after init. 75 | * 76 | * Don't support change dynamically! 77 | */ 78 | @Input({ transform: booleanAttribute }) autoFocus = false; 79 | 80 | /** The editor's value. */ 81 | @Input() value = ''; 82 | 83 | /** Whether the editor is disabled. */ 84 | @Input({ transform: booleanAttribute }) disabled = false; 85 | 86 | /** Whether the editor is readonly. */ 87 | @Input({ transform: booleanAttribute }) readonly = false; 88 | 89 | /** The editor's theme. */ 90 | @Input() theme: Theme = 'light'; 91 | 92 | /** The editor's placecholder. */ 93 | @Input() placeholder = ''; 94 | 95 | /** Whether indent with Tab key. */ 96 | @Input({ transform: booleanAttribute }) indentWithTab = false; 97 | 98 | /** Should be a string consisting either entirely of the same whitespace character. */ 99 | @Input() indentUnit = ''; 100 | 101 | /** Whether the editor wraps lines. */ 102 | @Input({ transform: booleanAttribute }) lineWrapping = false; 103 | 104 | /** Whether highlight the whitespace. */ 105 | @Input({ transform: booleanAttribute }) highlightWhitespace = false; 106 | 107 | /** 108 | * An array of language descriptions for known 109 | * [language-data](https://github.com/codemirror/language-data/blob/main/src/language-data.ts). 110 | * 111 | * Don't support change dynamically! 112 | */ 113 | @Input() languages: LanguageDescription[] = []; 114 | 115 | /** The editor's language. You should set the `languages` prop at first. */ 116 | @Input() language = ''; 117 | 118 | /** 119 | * The editor's built-in setup. The value can be set to 120 | * [`basic`](https://codemirror.net/docs/ref/#codemirror.basicSetup), 121 | * [`minimal`](https://codemirror.net/docs/ref/#codemirror.minimalSetup) or `null`. 122 | */ 123 | @Input() setup: Setup = 'basic'; 124 | 125 | /** 126 | * It will be appended to the root 127 | * [extensions](https://codemirror.net/docs/ref/#state.EditorStateConfig.extensions). 128 | */ 129 | @Input() extensions: Extension[] = []; 130 | 131 | /** Event emitted when the editor's value changes. */ 132 | @Output() change = new EventEmitter(); 133 | 134 | /** Event emitted when focus on the editor. */ 135 | @Output() focus = new EventEmitter(); 136 | 137 | /** Event emitted when the editor has lost focus. */ 138 | @Output() blur = new EventEmitter(); 139 | 140 | private _onChange: (value: string) => void = () => {}; 141 | private _onTouched: () => void = () => {}; 142 | 143 | /** 144 | * The instance of [EditorView](https://codemirror.net/docs/ref/#view.EditorView). 145 | */ 146 | view!: EditorView; 147 | 148 | /** 149 | * The new state created by [EditorState](https://codemirror.net/docs/ref/#state.EditorState) 150 | */ 151 | state!: EditorState; 152 | 153 | private _updateListener = EditorView.updateListener.of(vu => { 154 | if (vu.docChanged && !vu.transactions.some(tr => tr.annotation(External))) { 155 | const value = vu.state.doc.toString(); 156 | this._onChange(value); 157 | this.change.emit(value); 158 | } 159 | }); 160 | 161 | // Extension compartments can be used to make a configuration dynamic. 162 | // https://codemirror.net/docs/ref/#state.Compartment 163 | private _editableConf = new Compartment(); 164 | private _readonlyConf = new Compartment(); 165 | private _themeConf = new Compartment(); 166 | private _placeholderConf = new Compartment(); 167 | private _indentWithTabConf = new Compartment(); 168 | private _indentUnitConf = new Compartment(); 169 | private _lineWrappingConf = new Compartment(); 170 | private _highlightWhitespaceConf = new Compartment(); 171 | private _languageConf = new Compartment(); 172 | 173 | private _getAllExtensions() { 174 | return [ 175 | this._updateListener, 176 | 177 | this._editableConf.of([]), 178 | this._readonlyConf.of([]), 179 | this._themeConf.of([]), 180 | this._placeholderConf.of([]), 181 | this._indentWithTabConf.of([]), 182 | this._indentUnitConf.of([]), 183 | this._lineWrappingConf.of([]), 184 | this._highlightWhitespaceConf.of([]), 185 | this._languageConf.of([]), 186 | 187 | this.setup === 'basic' ? basicSetup : this.setup === 'minimal' ? minimalSetup : [], 188 | 189 | ...this.extensions, 190 | ]; 191 | } 192 | 193 | ngOnChanges(changes: SimpleChanges): void { 194 | if (!this.view) return; 195 | 196 | if (changes['value']) { 197 | this.setValue(this.value); 198 | } 199 | if (changes['disabled']) { 200 | this.setEditable(!this.disabled); 201 | } 202 | if (changes['readonly']) { 203 | this.setReadonly(this.readonly); 204 | } 205 | if (changes['theme']) { 206 | this.setTheme(this.theme); 207 | } 208 | if (changes['placeholder']) { 209 | this.setPlaceholder(this.placeholder); 210 | } 211 | if (changes['indentWithTab']) { 212 | this.setIndentWithTab(this.indentWithTab); 213 | } 214 | if (changes['indentUnit']) { 215 | this.setIndentUnit(this.indentUnit); 216 | } 217 | if (changes['lineWrapping']) { 218 | this.setLineWrapping(this.lineWrapping); 219 | } 220 | if (changes['highlightWhitespace']) { 221 | this.setHighlightWhitespace(this.highlightWhitespace); 222 | } 223 | if (changes['language']) { 224 | this.setLanguage(this.language); 225 | } 226 | if (changes['setup'] || changes['extensions']) { 227 | this.setExtensions(this._getAllExtensions()); 228 | } 229 | } 230 | 231 | ngOnInit(): void { 232 | this.state = EditorState.create({ 233 | doc: this.value, 234 | extensions: this._getAllExtensions(), 235 | }); 236 | this.view = new EditorView({ 237 | root: this.root, 238 | parent: this._elementRef.nativeElement, 239 | state: this.state, 240 | }); 241 | 242 | if (this.autoFocus) { 243 | this.view.focus(); 244 | } 245 | 246 | this.view.contentDOM.addEventListener('focus', () => { 247 | this._onTouched(); 248 | this.focus.emit(); 249 | }); 250 | 251 | this.view.contentDOM.addEventListener('blur', () => { 252 | this._onTouched(); 253 | this.blur.emit(); 254 | }); 255 | 256 | this.setEditable(!this.disabled); 257 | this.setReadonly(this.readonly); 258 | this.setTheme(this.theme); 259 | this.setPlaceholder(this.placeholder); 260 | this.setIndentWithTab(this.indentWithTab); 261 | this.setIndentUnit(this.indentUnit); 262 | this.setLineWrapping(this.lineWrapping); 263 | this.setHighlightWhitespace(this.highlightWhitespace); 264 | this.setLanguage(this.language); 265 | } 266 | 267 | ngOnDestroy(): void { 268 | this.view.destroy(); 269 | } 270 | 271 | writeValue(value: any): void { 272 | if (this.view) { 273 | this.setValue(value); 274 | } 275 | } 276 | 277 | registerOnChange(fn: (value: string) => void) { 278 | this._onChange = fn; 279 | } 280 | 281 | registerOnTouched(fn: () => void) { 282 | this._onTouched = fn; 283 | } 284 | 285 | setDisabledState(isDisabled: boolean) { 286 | this.disabled = isDisabled; 287 | this.setEditable(!isDisabled); 288 | } 289 | 290 | /** Sets editor's value. */ 291 | setValue(value: string) { 292 | this.view.dispatch({ 293 | changes: { from: 0, to: this.view.state.doc.length, insert: value }, 294 | annotations: Transaction.addToHistory.of(false), 295 | }); 296 | } 297 | 298 | private _dispatchEffects(effects: StateEffect | readonly StateEffect[]) { 299 | return this.view.dispatch({ effects }); 300 | } 301 | 302 | /** Sets the root extensions of the editor. */ 303 | setExtensions(value: Extension[]) { 304 | this._dispatchEffects(StateEffect.reconfigure.of(value)); 305 | } 306 | 307 | /** Sets editor's editable state. */ 308 | setEditable(value: boolean) { 309 | this._dispatchEffects(this._editableConf.reconfigure(EditorView.editable.of(value))); 310 | } 311 | 312 | /** Sets editor's readonly state. */ 313 | setReadonly(value: boolean) { 314 | this._dispatchEffects(this._readonlyConf.reconfigure(EditorState.readOnly.of(value))); 315 | } 316 | 317 | /** Sets editor's theme. */ 318 | setTheme(value: Theme) { 319 | this._dispatchEffects( 320 | this._themeConf.reconfigure(value === 'light' ? [] : value === 'dark' ? oneDark : value) 321 | ); 322 | } 323 | 324 | /** Sets editor's placeholder. */ 325 | setPlaceholder(value: string) { 326 | this._dispatchEffects(this._placeholderConf.reconfigure(value ? placeholder(value) : [])); 327 | } 328 | 329 | /** Sets editor' indentWithTab. */ 330 | setIndentWithTab(value: boolean) { 331 | this._dispatchEffects( 332 | this._indentWithTabConf.reconfigure(value ? keymap.of([indentWithTab]) : []) 333 | ); 334 | } 335 | 336 | /** Sets editor's indentUnit. */ 337 | setIndentUnit(value: string) { 338 | this._dispatchEffects(this._indentUnitConf.reconfigure(value ? indentUnit.of(value) : [])); 339 | } 340 | 341 | /** Sets editor's lineWrapping. */ 342 | setLineWrapping(value: boolean) { 343 | this._dispatchEffects(this._lineWrappingConf.reconfigure(value ? EditorView.lineWrapping : [])); 344 | } 345 | 346 | /** Sets editor's highlightWhitespace. */ 347 | setHighlightWhitespace(value: boolean) { 348 | this._dispatchEffects( 349 | this._highlightWhitespaceConf.reconfigure(value ? highlightWhitespace() : []) 350 | ); 351 | } 352 | 353 | /** Sets editor's language dynamically. */ 354 | setLanguage(lang: string) { 355 | if (!lang || lang == 'plaintext') { 356 | this._dispatchEffects(this._languageConf.reconfigure([])); 357 | return; 358 | } 359 | if (this.languages.length === 0) { 360 | if (this.view) { 361 | console.error('No supported languages. Please set the `languages` prop at first.'); 362 | } 363 | return; 364 | } 365 | const langDesc = this._findLanguage(lang); 366 | langDesc?.load().then(lang => { 367 | this._dispatchEffects(this._languageConf.reconfigure([lang])); 368 | }); 369 | } 370 | 371 | /** Find the language's extension by its name. Case insensitive. */ 372 | private _findLanguage(name: string) { 373 | for (const lang of this.languages) { 374 | for (const alias of [lang.name, ...lang.alias]) { 375 | if (name.toLowerCase() === alias.toLowerCase()) { 376 | return lang; 377 | } 378 | } 379 | } 380 | console.error('Language not found:', name); 381 | console.info('Supported language names:', this.languages.map(lang => lang.name).join(', ')); 382 | return null; 383 | } 384 | } 385 | --------------------------------------------------------------------------------