4040import org .apache .commons .compress .utils .IOUtils ;
4141import org .apache .logging .log4j .core .util .NullOutputStream ;
4242
43+ import com .fasterxml .jackson .databind .DeserializationFeature ;
44+ import com .fasterxml .jackson .databind .ObjectMapper ;
45+
4346import processing .app .Base ;
4447import processing .app .Editor ;
4548import processing .app .helpers .ProcessUtils ;
@@ -57,14 +60,15 @@ public ClangFormat(Editor editor) {
5760 @ Override
5861 public void run () {
5962 String originalText = editor .getCurrentTab ().getText ();
60-
63+ int cursorOffset = editor . getCurrentTab (). getTextArea (). getCaretPosition ();
6164 try {
62- String formattedText = runClangFormatOn (originalText );
63- if (formattedText .equals (originalText )) {
65+ FormatResult result = runClangFormatOn (originalText , cursorOffset );
66+ if (result . FormattedText .equals (originalText )) {
6467 editor .statusNotice (tr ("No changes necessary for Auto Format." ));
6568 return ;
6669 }
67- editor .getCurrentTab ().setText (formattedText );
70+ editor .getCurrentTab ().setText (result .FormattedText );
71+ editor .getCurrentTab ().getTextArea ().setCaretPosition (result .Cursor );
6872 editor .statusNotice (tr ("Auto Format finished." ));
6973 } catch (IOException | InterruptedException e ) {
7074 editor .statusError ("Auto format error: " + e .getMessage ());
@@ -94,21 +98,49 @@ private Thread copyAndClose(InputStream input, OutputStream output) {
9498 return t ;
9599 }
96100
97- String runClangFormatOn (String source )
101+ FormatResult runClangFormatOn (String source , int cursorOffset )
98102 throws IOException , InterruptedException {
99- String cmd [] = new String [] { clangExecutable };
103+ String cmd [] = new String [] { clangExecutable , "--cursor=" + cursorOffset };
100104
101105 Process process = ProcessUtils .exec (cmd );
102- ByteArrayOutputStream result = new ByteArrayOutputStream ();
106+ ByteArrayOutputStream clangOutput = new ByteArrayOutputStream ();
103107 ByteArrayInputStream dataOut = new ByteArrayInputStream (source .getBytes ());
104- Thread out = copyAndClose (process .getInputStream (), result );
108+
109+ Thread in = copyAndClose (dataOut , process .getOutputStream ());
105110 Thread err = copyAndClose (process .getErrorStream (),
106111 NullOutputStream .getInstance ());
107- Thread in = copyAndClose (dataOut , process .getOutputStream ());
112+ Thread out = copyAndClose (process .getInputStream (), clangOutput );
113+
108114 /* int r = */ process .waitFor ();
109115 in .join ();
110116 out .join ();
111117 err .join ();
112- return result .toString ();
118+
119+ // clang-format will output first a JSON object with:
120+ // - the resulting cursor position and
121+ // - a flag teling if the conversion was successful
122+ // for example:
123+ //
124+ // { "Cursor": 34, "IncompleteFormat": false }
125+ ObjectMapper mapper = new ObjectMapper ();
126+ mapper .configure (DeserializationFeature .ACCEPT_SINGLE_VALUE_AS_ARRAY , true );
127+ mapper .configure (DeserializationFeature .EAGER_DESERIALIZER_FETCH , true );
128+ mapper .configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false );
129+ FormatResult res = mapper .readValue (clangOutput .toByteArray (),
130+ FormatResult .class );
131+
132+ // After the JSON object above clang-format will output the formatted source
133+ // code in plain text
134+ String formattedText = clangOutput .toString ();
135+ formattedText = formattedText .substring (formattedText .indexOf ('}' ) + 1 );
136+ // handle different line endings
137+ res .FormattedText = formattedText .replaceFirst ("\\ R" , "" );
138+ return res ;
113139 }
114140}
141+
142+ class FormatResult {
143+ public String FormattedText ;
144+ public int Cursor ;
145+ public boolean IncompleteFormat ;
146+ }
0 commit comments