组件完整代码
使用方法(父级容器需要指定宽和高):
<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);
}
}
}
自定义关键字/代码段格式
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;
对象后端键入点(.),联想对象属性
- 需要把 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\$-\$.]/;
- 监听键入点(.)的事件,然后执行启动补全提示的命令:
this._editor.commands.on("afterExec", function (e, t) {
if (e.command.name == "insertstring" && e.args == ".") {
e.editor.execCommand("startAutocomplete");
}
});