blob: 2fe1f7b45b87d85c03aaa8000eff59d865a9271b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import angular from 'angular';
import CodeMirror from 'codemirror';
import 'codemirror/mode/yaml/yaml';
import 'codemirror/addon/fold/foldcode';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/fold/indent-fold';
import 'codemirror/addon/fold/comment-fold';
import 'codemirror/addon/lint/lint';
import './addon/lint/lint-yaml-brooklyn';
import './addon/lint/lint-yaml-tab';
import 'codemirror/addon/hint/show-hint';
import './addon/hint/super-hint';
import './addon/hint/super-hint-yaml-schema';
import './addon/hint/super-hint-yaml-type';
import './addon/hint/super-hint-yaml-anchor';
const MODULE_NAME = 'brooklyn.component.yaml-editor';
angular.module(MODULE_NAME, [])
.directive('brYamlEditor', ['$rootScope', 'brSnackbar', yamlEditorDirective]);
export default MODULE_NAME;
export function yamlEditorDirective($rootScope, brSnackbar) {
return {
restrict: 'E',
scope: {
type: '@',
value: '=',
onChange: '&'
},
link: link
};
function link($scope, $element) {
$scope.type = $scope.type || 'brooklyn';
// Init CodeMirror
CodeMirror.extendMode('yaml', {
fold: 'indent',
type: $scope.type
});
let lint = (text, options, cm) => {
let issues = cm.getHelpers(CodeMirror.Pos(0, 0), 'lint').reduce((issues, helper) => issues.concat(helper(text, options, cm)), []);
$rootScope.$broadcast('yaml.lint', issues.some(issue => !issue.severity || issue.severity === 'error'), cm.getValue());
return issues;
};
let hint = (cm, callback, options) => {
let loadingState = createLoadingState();
let removeLoadingState = () => {
if (loadingState) {
loadingState.remove();
}
cm.off('cursorActivity', removeLoadingState);
};
cm.on('cursorActivity', removeLoadingState);
cm.addWidget(cm.getCursor(), loadingState);
let promises = cm.getHelpers(CodeMirror.Pos(0, 0), 'hint').reduce((promises, helper) => promises.concat(helper(cm, options)), []);
Promise.all(promises).then(results => {
removeLoadingState();
callback(results.reduce((hints, result) => {
let list = hints.list.concat(result.list);
list.sort();
return Object.assign(hints, result, {list: list});
}, {list: []}));
}).catch(error => {
removeLoadingState();
$scope.$apply(() => {
brSnackbar.create(error.message);
});
callback({
list: [],
from: cm.getCursor(),
to: cm.getCursor()
});
});
};
hint.async = true;
let createLoadingState = () => {
let loadingState = document.createElement('ul');
loadingState.className = 'CodeMirror-hints';
let loading = loadingState.appendChild(document.createElement('li'));
loading.className = 'CodeMirror-hint CodeMirror-hint-loading';
loading.appendChild(document.createTextNode('Loading'));
return loadingState;
};
$scope.cm = new CodeMirror($element[0], {
value: $scope.value || '',
styleActiveLine: true,
matchBrackets: true,
theme: 'eclipse',
lineNumbers: true,
lineWrapping: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-lint-markers', 'CodeMirror-foldgutter'],
mode: {
name: 'yaml',
globalVars: true
},
lint: lint,
extraKeys: {
'Ctrl-Space': 'autocomplete',
'Tab': cm => {
let wideSelection = cm.listSelections().reduce((acc, range) => {
if ((range.anchor.line !== range.head.line) || (range.anchor.ch !== range.head.ch)) {
return acc || true;
}
return acc;
}, false);
if (wideSelection) {
CodeMirror.commands.indentMore(cm);
} else {
cm.replaceSelection(' ', 'end');
}
}
},
foldOptions: {
widget: '{ ... }'
}
});
$scope.cm.on('changes', (cm, change)=> {
if ($scope.value !== cm.getValue()) {
$scope.$apply(()=> {
$scope.value = cm.getValue();
let fn = $scope.onChange();
if (angular.isFunction(fn)) {
fn(cm);
}
});
}
});
$scope.cm.on('superhint-error', (error) => {
$scope.$apply(()=> {
brSnackbar.create(error.message);
});
});
$scope.$watch('value', (newVal, oldVal)=> {
$scope.cm.getDoc();
$scope.cm.focus();
if ($scope.cm.getValue() !== newVal && angular.isDefined(newVal)) {
$scope.cm.setValue(newVal);
}
setTimeout(()=> {
$scope.cm.focus();
$scope.cm.refresh();
});
});
}
}