popcode
popcode copied to clipboard
Working on an auto-indentation/beautify/formatting feature!
I recently started working on a code beautification feature for pop-code. In general, I'm using js-beautify
to do the formatting.
My current approach is to reproduce the auto-formatting present in Cloud9's IDE (format only the current selection & retain cursor if formatting the entire page). I'm basically copying the code from C9 verbatim
In doing this, I've run into a dependency problem. Cloud9 depends on this package: c9.ide.threewaymerge. Unfortunately, that package depends on ace
and we use blace
.
The clearest way I can think of solving this is to simply copy the relevant code from threewaymerge
and edit it to work with blace
. If I do that, where should I put the code?
Thanks! Kenrick
diff --git a/src/components/Editor.jsx b/src/components/Editor.jsx
index 944f6bf..fc6c67e 100644
--- a/src/components/Editor.jsx
+++ b/src/components/Editor.jsx
@@ -1,21 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
-import ACE from 'brace';
import bindAll from 'lodash/bindAll';
import get from 'lodash/get';
import throttle from 'lodash/throttle';
import noop from 'lodash/noop';
+import Beautify from 'js-beautify';
+import merge from 'c9.ide.threewaymerge/threewaymerge';
+
+import ACE from 'brace';
import 'brace/ext/searchbox';
import 'brace/mode/html';
import 'brace/mode/css';
import 'brace/mode/javascript';
import 'brace/theme/monokai';
+const {Range} = ACE.acequire('ace/range');
+
const RESIZE_THROTTLE = 250;
const NORMAL_FONTSIZE = 14;
const LARGE_FONTSIZE = 20;
+const BEAUTIFY_SETTINGS = {
+ indent_size: 4,
+ indent_char: ' ',
+ indent_with_tabs: true,
+ eol: '\n',
+ end_with_newline: false,
+ indent_inner_html: true,
+ indent_level: 0,
+ preserve_newlines: true,
+ max_preserve_newlines: 10,
+ space_in_paren: false,
+ space_in_empty_paren: false,
+ jslint_happy: false,
+ space_after_anon_function: false,
+ brace_style: 'collapse',
+ unindent_chained_methods: false,
+ break_chained_methods: false,
+ keep_array_indentation: false,
+ unescape_strings: false,
+ wrap_line_length: 0,
+ e4x: false,
+ comma_first: false,
+ operator_position: 'before-newline',
+};
+
function createSessionWithoutWorker(source, language) {
const session = ACE.createEditSession(source, null);
session.setUseWorker(false);
@@ -23,6 +53,7 @@ function createSessionWithoutWorker(source, language) {
return session;
}
+
class Editor extends React.Component {
constructor() {
super();
@@ -33,7 +64,8 @@ class Editor extends React.Component {
}
}, RESIZE_THROTTLE);
- bindAll(this, '_handleWindowResize', '_resizeEditor', '_setupEditor');
+ bindAll(this, '_handleWindowResize', '_resizeEditor', '_setupEditor',
+ '_handleKeyPress');
}
componentDidMount() {
@@ -142,11 +174,84 @@ class Editor extends React.Component {
this._resizeEditor();
}
+ _diffAndReplace(document, range, text) {
+ const start = document.positionToIndex(range.start);
+ const oldText = document.getTextRange(range);
+ merge.patchAce(oldText, text, document, {
+ offset: start,
+ method: 'quick',
+ });
+ const diff = text.replace(/\r\n|\r|\n/g, document.getNewLineCharacter());
+ return document.indexToPosition(start + diff.length);
+ }
+
+ _handleKeyPress(event) {
+ if (event.key === 'i' && (event.metaKey || event.ctrlKey) &&
+ !event.altKey && !event.ctrlKey) {
+ const {session, selection} = this._editor;
+
+ let keepSelection = false;
+ let range = selection.getRange();
+ if (range.isEmpty()) {
+ const numRows = session.getLength();
+ range = new Range(0, 0, numRows, 0);
+ keepSelection = true;
+ }
+
+ const options = Object.assign({}, BEAUTIFY_SETTINGS);
+ if (session.getUseSoftTabs()) {
+ options.indent_char = ' ';
+ options.indent_size = session.getTabSize();
+ } else {
+ options.indent_char = '\t';
+ options.indent_size = 1;
+ }
+
+ const line = session.getLine(range.start.row);
+ const [indent] = line.match(/^\s*/);
+ let trim = false;
+
+ if (range.start.column < indent.length) {
+ range.start.column = 0;
+ } else {
+ trim = true;
+ }
+
+ let value = session.getTextRange(range);
+
+ try {
+ value = Beautify.html(value, options);
+ if (trim) {
+ value = value.replace(/^/gm, indent).trim();
+ }
+ if (range.end.column === 0) {
+ value += `\n${indent}`;
+ }
+ } catch (e) {
+ console.warn('Failed to format text with error', e);
+ return;
+ }
+
+ this._diffAndReplace(session.doc, range, value);
+
+ if (!keepSelection) {
+ selection.setSelectionRange(Range.fromPoints(range.start, end));
+ }
+
+ event.preventDefault();
+ }
+ }
+
render() {
return (
<div
className="editors__editor"
ref={this._setupEditor}
+ onKeyPress={this._handleKeyPress}
/>
);
}
In general, I'm not sure where to put the code related to this change! I'm open to suggestions to match the general organization style of popcode
.
@cricklet cool!
In general, I'm not sure where to put the code related to this change! I'm open to suggestions to match the general organization style of
popcode
.
- The guts should go in a module in
src/util
that is reasonably agnostic to the specifics of ACE, whose job is just to take a source string + selection range (or whatever makes sense) and return a modified string + cursor position (or whatever makes sense) - That module should be invoked by a saga (probably defined in
src/sagas/projects.js
), which consumes an action along the lines ofBEAUTIFY_SOURCE
, calls the aforementioned method, and then dispatches another action e.g.SOURCE_BEAUTIFIED
which instructs the projects reducer to update the project source in question to the beautified version, and also instructs the UI reducer to move the cursor position to the right place - That initial
BEAUTIFY_SOURCE
action should be invoked by the_handleKeyPress
method in theEditor
component but the footprint in that component should only be a few LOC
The clearest way I can think of solving this is to simply copy the relevant code from
threewaymerge
and edit it to work withblace
. If I do that, where should I put the code?
I just forked that repo into the popcode
org; you should have write access! Let’s just push all of our modifications to a branch (maybe brace
) which we can then target in Popcode’s package.json
.
Finally—feel free to open a pull request even if it’s a work in progress—happy to follow along and provide help/feedback as needed.