提交 6163e83a 编写于 作者: M Martin Aeschlimann

[json edit] handle not yet existing parent objects, handle empty documents

上级 d9aaca92
...@@ -4,43 +4,55 @@ ...@@ -4,43 +4,55 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import { ParseError, parseTree, Segment, findNodeAtLocation } from 'vs/base/common/json'; import { ParseError, Node, parseTree, findNodeAtLocation, JSONPath, Segment } from 'vs/base/common/json';
import { Edit, FormattingOptions, format } from 'vs/base/common/jsonFormatter'; import { Edit, FormattingOptions, format, applyEdit } from 'vs/base/common/jsonFormatter';
export function removeProperty(text: string, segments: Segment[], formattingOptions: FormattingOptions) : Edit[] { export function removeProperty(text: string, path: JSONPath, formattingOptions: FormattingOptions) : Edit[] {
return setProperty(text, segments, void 0, formattingOptions); return setProperty(text, path, void 0, formattingOptions);
} }
export function setProperty(text: string, segments: Segment[], value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number) : Edit[] { export function setProperty(text: string, path: JSONPath, value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number) : Edit[] {
let lastSegment = segments.pop();
if (typeof lastSegment !== 'string') {
throw new Error('Last segment must be a property name');
}
let errors: ParseError[] = []; let errors: ParseError[] = [];
let node = parseTree(text, errors); let root = parseTree(text, errors);
if (segments.length > 0) { let parent: Node = void 0;
node = findNodeAtLocation(node, segments);
if (node === void 0) { let lastSegment: Segment = void 0;
throw new Error('Cannot find object'); while (path.length > 0) {
lastSegment = path.pop();
parent = findNodeAtLocation(root, path);
if (parent === void 0 && value !== void 0) {
if (typeof lastSegment === 'string') {
value = { [lastSegment]: value };
} else {
value = [ value ];
}
} else {
break;
} }
} }
if (node && node.type === 'object') {
let existing = findNodeAtLocation(node, [ lastSegment ]); if (!parent) {
// empty document
if (value === void 0) { // delete
throw new Error('Can not delete in empty document');
}
return withFormatting(text, { offset: root ? root.offset : 0, length: root ? root.length : text.length, content: JSON.stringify(value) }, formattingOptions);
} else if (parent.type === 'object' && typeof lastSegment === 'string') {
let existing = findNodeAtLocation(parent, [ lastSegment ]);
if (existing !== void 0) { if (existing !== void 0) {
if (value === void 0) { // delete if (value === void 0) { // delete
let propertyIndex = node.children.indexOf(existing.parent); let propertyIndex = parent.children.indexOf(existing.parent);
let removeBegin : number; let removeBegin : number;
let removeEnd = existing.parent.offset + existing.parent.length; let removeEnd = existing.parent.offset + existing.parent.length;
if (propertyIndex > 0) { if (propertyIndex > 0) {
// remove the comma of the previous node // remove the comma of the previous node
let previous = node.children[propertyIndex - 1]; let previous = parent.children[propertyIndex - 1];
removeBegin = previous.offset + previous.length; removeBegin = previous.offset + previous.length;
} else { } else {
removeBegin = node.offset + 1; removeBegin = parent.offset + 1;
if (node.children.length > 1) { if (parent.children.length > 1) {
// remove the comma of the next node // remove the comma of the next node
let next = node.children[1]; let next = parent.children[1];
removeEnd = next.offset; removeEnd = next.offset;
} }
} }
...@@ -54,26 +66,28 @@ export function setProperty(text: string, segments: Segment[], value: any, forma ...@@ -54,26 +66,28 @@ export function setProperty(text: string, segments: Segment[], value: any, forma
throw new Error(`Property ${lastSegment} does not exist.`); throw new Error(`Property ${lastSegment} does not exist.`);
} }
let newProperty = `${JSON.stringify(lastSegment)}: ${JSON.stringify(value)}`; let newProperty = `${JSON.stringify(lastSegment)}: ${JSON.stringify(value)}`;
let index = getInsertionIndex ? getInsertionIndex(node.children.map(p => p.children[0].value)) : node.children.length; let index = getInsertionIndex ? getInsertionIndex(parent.children.map(p => p.children[0].value)) : parent.children.length;
let edit: Edit; let edit: Edit;
if (index > 0) { if (index > 0) {
let previous = node.children[index - 1]; let previous = parent.children[index - 1];
edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty}; edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty};
} else if (node.children.length === 0) { } else if (parent.children.length === 0) {
edit = { offset: node.offset + 1, length: 0, content: newProperty}; edit = { offset: parent.offset + 1, length: 0, content: newProperty};
} else { } else {
edit = { offset: node.offset + 1, length: 0, content: newProperty + ','}; edit = { offset: parent.offset + 1, length: 0, content: newProperty + ','};
} }
return withFormatting(text, edit, formattingOptions); return withFormatting(text, edit, formattingOptions);
} }
} else if (parent.type === 'array' && typeof lastSegment === 'number') {
throw new Error('Array modification not supported yet');
} else { } else {
throw new Error('Path does not reference an object'); throw new Error(`Can not add ${typeof lastSegment !== 'number' ? 'index' : 'property' } to parent of type ${parent.type}`);
} }
} }
function withFormatting(text:string, edit: Edit, formattingOptions: FormattingOptions) : Edit[] { function withFormatting(text:string, edit: Edit, formattingOptions: FormattingOptions) : Edit[] {
// apply the edit // apply the edit
let newText = text.substring(0, edit.offset) + edit.content + text.substring(edit.offset + edit.length); let newText = applyEdit(text, edit);
// format the new text // format the new text
let begin = edit.offset; let begin = edit.offset;
...@@ -83,7 +97,7 @@ function withFormatting(text:string, edit: Edit, formattingOptions: FormattingOp ...@@ -83,7 +97,7 @@ function withFormatting(text:string, edit: Edit, formattingOptions: FormattingOp
// apply the formatting edits and track the begin and end offsets of the changes // apply the formatting edits and track the begin and end offsets of the changes
for (let i = edits.length - 1; i >= 0; i--) { for (let i = edits.length - 1; i >= 0; i--) {
let edit = edits[i]; let edit = edits[i];
newText = newText.substring(0, edit.offset) + edit.content + newText.substring(edit.offset + edit.length); newText = applyEdit(newText, edit);
begin = Math.min(begin, edit.offset); begin = Math.min(begin, edit.offset);
end = Math.max(end, edit.offset + edit.length); end = Math.max(end, edit.offset + edit.length);
end += edit.content.length - edit.length; end += edit.content.length - edit.length;
...@@ -91,4 +105,4 @@ function withFormatting(text:string, edit: Edit, formattingOptions: FormattingOp ...@@ -91,4 +105,4 @@ function withFormatting(text:string, edit: Edit, formattingOptions: FormattingOp
// create a single edit with all changes // create a single edit with all changes
let editLength = text.length - (newText.length - end) - begin; let editLength = text.length - (newText.length - end) - begin;
return [{ offset: begin, length: editLength, content: newText.substring(begin, end) }]; return [{ offset: begin, length: editLength, content: newText.substring(begin, end) }];
} }
\ No newline at end of file
...@@ -35,6 +35,10 @@ suite('JSON - edits', () => { ...@@ -35,6 +35,10 @@ suite('JSON - edits', () => {
let content = '{\n "x": "y"\n}'; let content = '{\n "x": "y"\n}';
let edits = setProperty(content, ['x'], 'bar', formatterOptions); let edits = setProperty(content, ['x'], 'bar', formatterOptions);
assertEdit(content, edits, '{\n "x": "bar"\n}'); assertEdit(content, edits, '{\n "x": "bar"\n}');
content = 'true';
edits = setProperty(content, [], 'bar', formatterOptions);
assertEdit(content, edits, '"bar"');
}); });
test('insert property', () => { test('insert property', () => {
...@@ -42,6 +46,9 @@ suite('JSON - edits', () => { ...@@ -42,6 +46,9 @@ suite('JSON - edits', () => {
let edits = setProperty(content, ['foo'], 'bar', formatterOptions); let edits = setProperty(content, ['foo'], 'bar', formatterOptions);
assertEdit(content, edits, '{\n "foo": "bar"\n}'); assertEdit(content, edits, '{\n "foo": "bar"\n}');
edits = setProperty(content, ['foo', 'foo2'], 'bar', formatterOptions);
assertEdit(content, edits, '{\n "foo": {\n "foo2": "bar"\n }\n}');
content = "{\n}"; content = "{\n}";
edits = setProperty(content, ['foo'], 'bar', formatterOptions); edits = setProperty(content, ['foo'], 'bar', formatterOptions);
assertEdit(content, edits, '{\n "foo": "bar"\n}'); assertEdit(content, edits, '{\n "foo": "bar"\n}');
...@@ -72,6 +79,10 @@ suite('JSON - edits', () => { ...@@ -72,6 +79,10 @@ suite('JSON - edits', () => {
edits = setProperty(content, ['x', 'c'], 'bar', formatterOptions, () => 2); edits = setProperty(content, ['x', 'c'], 'bar', formatterOptions, () => 2);
assertEdit(content, edits, '{\n "x": {\n "a": 1,\n "b": true,\n "c": "bar"\n }\n}\n'); assertEdit(content, edits, '{\n "x": {\n "a": 1,\n "b": true,\n "c": "bar"\n }\n}\n');
content = '';
edits = setProperty(content, ['foo', 0], 'bar', formatterOptions);
assertEdit(content, edits, '{\n "foo": [\n "bar"\n ]\n}');
}); });
test('remove property', () => { test('remove property', () => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册