基于 ace-editor 实现自定义的 Angular 编辑器组件(基础功能、自定义代码段、自定义关键字、自动完成)

组件完整代码

使用方法(父级容器需要指定宽和高):

<div class="editor-wrap">
   <app-editor [(ngModel)]="drools" [preFillData]="preFillData" #editor></app-editor>
</div>

editor.component.ts 组件的完整代码如下:

import { Observable } from "rxjs";
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  forwardRef,
  Input,
  OnDestroy,
  SimpleChanges,
  ViewChild,
} from "@angular/core";
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
// import * as ace from "ace-builds";
// import * as languageTools from "ace-builds/src-noconflict/ext-language_tools";

import * as ace from "./lib/ace";
import * as languageTools from "./lib/ext-language_tools";
import "./lib/ext-language_tools";
import "./lib/theme-chrome";
import "./lib/mode-drools";

import initCustomSnippets from "./lib/snippets/drools";
import { EditorService } from "./editor.service";

import {
  ICompleterConfig,
  IEditorOptions,
  IQueryParam,
} from "./editor.interface";

// 配置项基础路径设置
ace.config.set(
  "basePath",
  "https://unpkg.com/ace-builds@1.4.12/src-noconflict"
);

const EDITOR_CONFIG_GLOBAL: { [key: string]: boolean } = {
  enableBackendContact: false, // 标识是否启用后端联想功能
  isObjectContact: false, // 标识是否是对象属性联想功能
};

@Component({
  selector: "app-editor",
  templateUrl: "./editor.component.html",
  styleUrls: ["./editor.component.scss"],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => EditorComponent),
      multi: true,
    },
    EditorService,
  ],
})
export class EditorComponent
  implements AfterViewInit, OnDestroy, ControlValueAccessor
{
  @Input() public fontSize: string = "16px";

  @Input() public editorOptions: IEditorOptions = {};

  @Input() public preFillData: { [key: string]: string } = {};

  @Input() public url: string = "Variable/var-complete";

  @ViewChild("editor", { static: true })
  private _editorRef: ElementRef<HTMLElement>;

  // 编辑器初始配置
  private _initEditorOptions: IEditorOptions = {
    // 基本的自动补全功能与代码片段提示
    enableBasicAutocompletion: true,
    enableSnippets: true,
    enableLiveAutocompletion: true,
  };

  private _editor: ace.Ace.Editor = null;

  private _onValueChange(value: string): void {}

  constructor(private _service: EditorService) {}

  public ngOnChanges(changes: SimpleChanges): void {
    if (
      changes["editorOptions"] &&
      Object.keys(changes["editorOptions"]).length
    ) {
      this._editor &&
        this._editor.setOptions({
          ...this._initEditorOptions,
          ...changes["editorOptions"],
        });
    }
  }

  public ngAfterViewInit(): void {
    this.initEditor();
    this.initEvent();
    this.initCommands();
    // 添加自定义的代码补全功能
    const preFillSnippets = this.preFillConfigs(initCustomSnippets);
    this.initComplater(preFillSnippets);
    // 添加数据库代码提示功能
    const getDatasource = (param: IQueryParam) =>
      this._service.getConjunctions(this.url, param);
    this.addContactCompleter(this.getContactCompleter(getDatasource));
  }

  public ngOnDestroy(): void {
    this.destroy();
  }

  public destroy(): void {
    this._editor && this._editor.destroy();
    this._editor = null;
  }

  public writeValue(value: string): void {
    const previousVal = this._editor?.getValue() || null;
    if (this._editor && value !== previousVal && value) {
      this._editor.setValue(value);
    }
  }

  public registerOnChange(fn: (value: string) => void): void {
    this._onValueChange = fn;
  }

  public registerOnTouched(fn: (value: string) => void): void {}

  public setDisabledState?(isDisabled: boolean): void {
    this._editor.setReadOnly(isDisabled);
  }

  private initEditor(): void {
    ace.config.set("fontSize", this.fontSize);

    // 初始化编辑器
    this._editor = ace.edit(this._editorRef.nativeElement);

    // 设置编辑器的主题和模式
    this._editor.setTheme("ace/theme/chrome");
    this._editor.session.setMode("ace/mode/drools");

    this._editor.setOptions(this._initEditorOptions);
  }

  private initEvent(): void {
    // 取值
    this._editor.on("change", () => {
      this._onValueChange(this._editor.getValue());
      this._editor.getValue() && this._editor.execCommand("startAutocomplete");
    });
  }

  private initCommands(): void {
    this._editor.commands.addCommand({
      name: "ShowAutoCompleter",
      bindKey: {
        win: "Ctrl-m",
        mac: "Command-m",
      },
      exec: function (editor) {
        ace.config.loadModule("ace/ext/keybinding_menu", function (module) {
          module.init(editor);
          EDITOR_CONFIG_GLOBAL.enableBackendContact = true;
          editor.execCommand("startAutocomplete");
        });
      },
    });
    this._editor.commands.on("afterExec", function (e, t) {
      if (e.command.name == "insertstring" && e.args == ".") {
        EDITOR_CONFIG_GLOBAL.enableBackendContact = true;
        EDITOR_CONFIG_GLOBAL.isObjectContact = true;
        e.editor.execCommand("startAutocomplete");
      }
    });
  }

  private preFillConfigs(initConfigs: ICompleterConfig[]): ICompleterConfig[] {
    let temp = initConfigs.slice();
    for (let i = 0; i < temp.length; i++) {
      if (
        this.preFillData &&
        this.preFillData.variableName &&
        temp[i].snippet
      ) {
        temp[i].snippet = temp[i].snippet.replace(
          /\$\{\s*\d+\s*:\s*variable_name\s*\}/gi,
          this.preFillData.variableName
        );
      }
    }
    return temp;
  }

  private initComplater(initConfigs: ICompleterConfig[] = []): void {
    languageTools.addCompleter({
      getCompletions: function (editor, session, pos, prefix, callback) {
        if (prefix.length === 0) {
          callback(null, []);
          return;
        }
        callback(null, initConfigs);
      },
      getDocTooltip: function (item) {
        if (item.type == "snippet" && !item.docHTML) {
          item.docHTML = [
            "<b>",
            item.caption,
            "</b>",
            "<hr></hr>",
            item.snippet,
          ].join("");
          return item.docHTML;
        }
      },
    });
  }

  // 获取联想词完成器
  private getContactCompleter(
    getDatasource: (param: IQueryParam) => Observable<any>
  ): ace.Ace.Completer {
    return {
      getCompletions: function (editor, session, pos, prefix, callback) {
        if (prefix.length === 0) {
          callback(null, []);
          return;
        }
        if (!EDITOR_CONFIG_GLOBAL.enableBackendContact) {
          return null;
        }
        let param: IQueryParam = {
          data: editor.getValue(),
          pos,
        };
        getDatasource(param).subscribe({
          next: (wordList: ICompleterConfig[]) => {
            callback(
              null,
              wordList.map(function (word: ICompleterConfig) {
                return {
                  name: word.name || "",
                  value: EDITOR_CONFIG_GLOBAL.isObjectContact
                    ? prefix + word.value
                    : word.value || "",
                  caption: EDITOR_CONFIG_GLOBAL.isObjectContact
                    ? prefix + word.caption
                    : word.caption || "",
                  score: word.score || 1000,
                  type: word.type || "keyword",
                  meta: word.meta || "database",
                };
              })
            );
            EDITOR_CONFIG_GLOBAL.enableBackendContact = false;
            EDITOR_CONFIG_GLOBAL.isObjectContact = false;
          },
          error: (err) => {
            EDITOR_CONFIG_GLOBAL.enableBackendContact = false;
            EDITOR_CONFIG_GLOBAL.isObjectContact = false;
          },
        });
      },
    };
  }

  private addContactCompleter(completer: ace.Ace.Completer): void {
    if (completer) {
      // 恢复初始默认的 4 个 completer
      languageTools.setCompleters(this._editor.completers.slice(0, 4));
      languageTools.addCompleter(completer);
    }
  }
}

自定义关键字/代码段格式

complete config
snippets 定义

import { ICompleterConfig } from "../../editor.interface";

const initCustomSnippets: ICompleterConfig[] = [
  // 自定义关键字
   {
     name: "test",
     value: "test",
     caption: "test",
     meta: "keyword",
     type: "keyword",
     score: 1000,
   },
  // 自定义代码段(带占位符)
  {
    name: "common",
    value: "",
    snippet:
      '#if (${1:PACKAGE_NAME} && ${1:PACKAGE_NAME} != "")package ${1:PACKAGE_NAME};#end\n\
\n\
    ',
    caption: "common",
    meta: "snippet",
    type: "snippet",
    score: 1000,
  },
];

export default initCustomSnippets;

对象后端键入点(.),联想对象属性

  1. 需要把 ext-language-tools.js 文件中的 ID_REGEX 替换为如下:
 // var ID_REGEX = /[a-zA-Z_0-9\$\-\u00A2-\u2000\u2070-\uFFFF]/;
  var ID_REGEX = /[a-zA-Z_0-9\$-\$.]/;
  1. 监听键入点(.)的事件,然后执行启动补全提示的命令:
this._editor.commands.on("afterExec", function (e, t) {
      if (e.command.name == "insertstring" && e.args == ".") {
        e.editor.execCommand("startAutocomplete");
      }
    });
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值