40
40
import org .apache .commons .compress .utils .IOUtils ;
41
41
import org .apache .logging .log4j .core .util .NullOutputStream ;
42
42
43
+ import com .fasterxml .jackson .databind .DeserializationFeature ;
44
+ import com .fasterxml .jackson .databind .ObjectMapper ;
45
+
43
46
import processing .app .Base ;
44
47
import processing .app .Editor ;
45
48
import processing .app .helpers .ProcessUtils ;
@@ -57,14 +60,15 @@ public ClangFormat(Editor editor) {
57
60
@ Override
58
61
public void run () {
59
62
String originalText = editor .getCurrentTab ().getText ();
60
-
63
+ int cursorOffset = editor . getCurrentTab (). getTextArea (). getCaretPosition ();
61
64
try {
62
- String formattedText = runClangFormatOn (originalText );
63
- if (formattedText .equals (originalText )) {
65
+ FormatResult result = runClangFormatOn (originalText , cursorOffset );
66
+ if (result . FormattedText .equals (originalText )) {
64
67
editor .statusNotice (tr ("No changes necessary for Auto Format." ));
65
68
return ;
66
69
}
67
- editor .getCurrentTab ().setText (formattedText );
70
+ editor .getCurrentTab ().setText (result .FormattedText );
71
+ editor .getCurrentTab ().getTextArea ().setCaretPosition (result .Cursor );
68
72
editor .statusNotice (tr ("Auto Format finished." ));
69
73
} catch (IOException | InterruptedException e ) {
70
74
editor .statusError ("Auto format error: " + e .getMessage ());
@@ -94,21 +98,49 @@ private Thread copyAndClose(InputStream input, OutputStream output) {
94
98
return t ;
95
99
}
96
100
97
- String runClangFormatOn (String source )
101
+ FormatResult runClangFormatOn (String source , int cursorOffset )
98
102
throws IOException , InterruptedException {
99
- String cmd [] = new String [] { clangExecutable };
103
+ String cmd [] = new String [] { clangExecutable , "--cursor=" + cursorOffset };
100
104
101
105
Process process = ProcessUtils .exec (cmd );
102
- ByteArrayOutputStream result = new ByteArrayOutputStream ();
106
+ ByteArrayOutputStream clangOutput = new ByteArrayOutputStream ();
103
107
ByteArrayInputStream dataOut = new ByteArrayInputStream (source .getBytes ());
104
- Thread out = copyAndClose (process .getInputStream (), result );
108
+
109
+ Thread in = copyAndClose (dataOut , process .getOutputStream ());
105
110
Thread err = copyAndClose (process .getErrorStream (),
106
111
NullOutputStream .getInstance ());
107
- Thread in = copyAndClose (dataOut , process .getOutputStream ());
112
+ Thread out = copyAndClose (process .getInputStream (), clangOutput );
113
+
108
114
/* int r = */ process .waitFor ();
109
115
in .join ();
110
116
out .join ();
111
117
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 ;
113
139
}
114
140
}
141
+
142
+ class FormatResult {
143
+ public String FormattedText ;
144
+ public int Cursor ;
145
+ public boolean IncompleteFormat ;
146
+ }
0 commit comments