git
/
ws.c
388 строк · 9.4 Кб
1/*
2* Whitespace rules
3*
4* Copyright (c) 2007 Junio C Hamano
5*/
6#include "git-compat-util.h"7#include "attr.h"8#include "strbuf.h"9#include "ws.h"10
11unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;12
13static struct whitespace_rule {14const char *rule_name;15unsigned rule_bits;16unsigned loosens_error:1,17exclude_default:1;18} whitespace_rule_names[] = {19{ "trailing-space", WS_TRAILING_SPACE, 0 },20{ "space-before-tab", WS_SPACE_BEFORE_TAB, 0 },21{ "indent-with-non-tab", WS_INDENT_WITH_NON_TAB, 0 },22{ "cr-at-eol", WS_CR_AT_EOL, 1 },23{ "blank-at-eol", WS_BLANK_AT_EOL, 0 },24{ "blank-at-eof", WS_BLANK_AT_EOF, 0 },25{ "tab-in-indent", WS_TAB_IN_INDENT, 0, 1 },26};27
28unsigned parse_whitespace_rule(const char *string)29{
30unsigned rule = WS_DEFAULT_RULE;31
32while (string) {33int i;34size_t len;35const char *ep;36const char *arg;37int negated = 0;38
39string = string + strspn(string, ", \t\n\r");40ep = strchrnul(string, ',');41len = ep - string;42
43if (*string == '-') {44negated = 1;45string++;46len--;47}48if (!len)49break;50for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) {51if (strncmp(whitespace_rule_names[i].rule_name,52string, len))53continue;54if (negated)55rule &= ~whitespace_rule_names[i].rule_bits;56else57rule |= whitespace_rule_names[i].rule_bits;58break;59}60if (skip_prefix(string, "tabwidth=", &arg)) {61unsigned tabwidth = atoi(arg);62if (0 < tabwidth && tabwidth < 0100) {63rule &= ~WS_TAB_WIDTH_MASK;64rule |= tabwidth;65}66else67warning("tabwidth %.*s out of range",68(int)(ep - arg), arg);69}70string = ep;71}72
73if (rule & WS_TAB_IN_INDENT && rule & WS_INDENT_WITH_NON_TAB)74die("cannot enforce both tab-in-indent and indent-with-non-tab");75return rule;76}
77
78unsigned whitespace_rule(struct index_state *istate, const char *pathname)79{
80static struct attr_check *attr_whitespace_rule;81const char *value;82
83if (!attr_whitespace_rule)84attr_whitespace_rule = attr_check_initl("whitespace", NULL);85
86git_check_attr(istate, pathname, attr_whitespace_rule);87value = attr_whitespace_rule->items[0].value;88if (ATTR_TRUE(value)) {89/* true (whitespace) */90unsigned all_rule = ws_tab_width(whitespace_rule_cfg);91int i;92for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++)93if (!whitespace_rule_names[i].loosens_error &&94!whitespace_rule_names[i].exclude_default)95all_rule |= whitespace_rule_names[i].rule_bits;96return all_rule;97} else if (ATTR_FALSE(value)) {98/* false (-whitespace) */99return ws_tab_width(whitespace_rule_cfg);100} else if (ATTR_UNSET(value)) {101/* reset to default (!whitespace) */102return whitespace_rule_cfg;103} else {104/* string */105return parse_whitespace_rule(value);106}107}
108
109/* The returned string should be freed by the caller. */
110char *whitespace_error_string(unsigned ws)111{
112struct strbuf err = STRBUF_INIT;113if ((ws & WS_TRAILING_SPACE) == WS_TRAILING_SPACE)114strbuf_addstr(&err, "trailing whitespace");115else {116if (ws & WS_BLANK_AT_EOL)117strbuf_addstr(&err, "trailing whitespace");118if (ws & WS_BLANK_AT_EOF) {119if (err.len)120strbuf_addstr(&err, ", ");121strbuf_addstr(&err, "new blank line at EOF");122}123}124if (ws & WS_SPACE_BEFORE_TAB) {125if (err.len)126strbuf_addstr(&err, ", ");127strbuf_addstr(&err, "space before tab in indent");128}129if (ws & WS_INDENT_WITH_NON_TAB) {130if (err.len)131strbuf_addstr(&err, ", ");132strbuf_addstr(&err, "indent with spaces");133}134if (ws & WS_TAB_IN_INDENT) {135if (err.len)136strbuf_addstr(&err, ", ");137strbuf_addstr(&err, "tab in indent");138}139return strbuf_detach(&err, NULL);140}
141
142/* If stream is non-NULL, emits the line after checking. */
143static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule,144FILE *stream, const char *set,145const char *reset, const char *ws)146{
147unsigned result = 0;148int written = 0;149int trailing_whitespace = -1;150int trailing_newline = 0;151int trailing_carriage_return = 0;152int i;153
154/* Logic is simpler if we temporarily ignore the trailing newline. */155if (len > 0 && line[len - 1] == '\n') {156trailing_newline = 1;157len--;158}159if ((ws_rule & WS_CR_AT_EOL) &&160len > 0 && line[len - 1] == '\r') {161trailing_carriage_return = 1;162len--;163}164
165/* Check for trailing whitespace. */166if (ws_rule & WS_BLANK_AT_EOL) {167for (i = len - 1; i >= 0; i--) {168if (isspace(line[i])) {169trailing_whitespace = i;170result |= WS_BLANK_AT_EOL;171}172else173break;174}175}176
177if (trailing_whitespace == -1)178trailing_whitespace = len;179
180/* Check indentation */181for (i = 0; i < trailing_whitespace; i++) {182if (line[i] == ' ')183continue;184if (line[i] != '\t')185break;186if ((ws_rule & WS_SPACE_BEFORE_TAB) && written < i) {187result |= WS_SPACE_BEFORE_TAB;188if (stream) {189fputs(ws, stream);190fwrite(line + written, i - written, 1, stream);191fputs(reset, stream);192fwrite(line + i, 1, 1, stream);193}194} else if (ws_rule & WS_TAB_IN_INDENT) {195result |= WS_TAB_IN_INDENT;196if (stream) {197fwrite(line + written, i - written, 1, stream);198fputs(ws, stream);199fwrite(line + i, 1, 1, stream);200fputs(reset, stream);201}202} else if (stream) {203fwrite(line + written, i - written + 1, 1, stream);204}205written = i + 1;206}207
208/* Check for indent using non-tab. */209if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= ws_tab_width(ws_rule)) {210result |= WS_INDENT_WITH_NON_TAB;211if (stream) {212fputs(ws, stream);213fwrite(line + written, i - written, 1, stream);214fputs(reset, stream);215}216written = i;217}218
219if (stream) {220/*221* Now the rest of the line starts at "written".
222* The non-highlighted part ends at "trailing_whitespace".
223*/
224
225/* Emit non-highlighted (middle) segment. */226if (trailing_whitespace - written > 0) {227fputs(set, stream);228fwrite(line + written,229trailing_whitespace - written, 1, stream);230fputs(reset, stream);231}232
233/* Highlight errors in trailing whitespace. */234if (trailing_whitespace != len) {235fputs(ws, stream);236fwrite(line + trailing_whitespace,237len - trailing_whitespace, 1, stream);238fputs(reset, stream);239}240if (trailing_carriage_return)241fputc('\r', stream);242if (trailing_newline)243fputc('\n', stream);244}245return result;246}
247
248void ws_check_emit(const char *line, int len, unsigned ws_rule,249FILE *stream, const char *set,250const char *reset, const char *ws)251{
252(void)ws_check_emit_1(line, len, ws_rule, stream, set, reset, ws);253}
254
255unsigned ws_check(const char *line, int len, unsigned ws_rule)256{
257return ws_check_emit_1(line, len, ws_rule, NULL, NULL, NULL, NULL);258}
259
260int ws_blank_line(const char *line, int len)261{
262/*263* We _might_ want to treat CR differently from other
264* whitespace characters when ws_rule has WS_CR_AT_EOL, but
265* for now we just use this stupid definition.
266*/
267while (len-- > 0) {268if (!isspace(*line))269return 0;270line++;271}272return 1;273}
274
275/* Copy the line onto the end of the strbuf while fixing whitespaces */
276void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule, int *error_count)277{
278/*279* len is number of bytes to be copied from src, starting
280* at src. Typically src[len-1] is '\n', unless this is
281* the incomplete last line.
282*/
283int i;284int add_nl_to_tail = 0;285int add_cr_to_tail = 0;286int fixed = 0;287int last_tab_in_indent = -1;288int last_space_in_indent = -1;289int need_fix_leading_space = 0;290
291/*292* Strip trailing whitespace
293*/
294if (ws_rule & WS_BLANK_AT_EOL) {295if (0 < len && src[len - 1] == '\n') {296add_nl_to_tail = 1;297len--;298if (0 < len && src[len - 1] == '\r') {299add_cr_to_tail = !!(ws_rule & WS_CR_AT_EOL);300len--;301}302}303if (0 < len && isspace(src[len - 1])) {304while (0 < len && isspace(src[len-1]))305len--;306fixed = 1;307}308}309
310/*311* Check leading whitespaces (indent)
312*/
313for (i = 0; i < len; i++) {314char ch = src[i];315if (ch == '\t') {316last_tab_in_indent = i;317if ((ws_rule & WS_SPACE_BEFORE_TAB) &&3180 <= last_space_in_indent)319need_fix_leading_space = 1;320} else if (ch == ' ') {321last_space_in_indent = i;322if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&323ws_tab_width(ws_rule) <= i - last_tab_in_indent)324need_fix_leading_space = 1;325} else326break;327}328
329if (need_fix_leading_space) {330/* Process indent ourselves */331int consecutive_spaces = 0;332int last = last_tab_in_indent + 1;333
334if (ws_rule & WS_INDENT_WITH_NON_TAB) {335/* have "last" point at one past the indent */336if (last_tab_in_indent < last_space_in_indent)337last = last_space_in_indent + 1;338else339last = last_tab_in_indent + 1;340}341
342/*343* between src[0..last-1], strip the funny spaces,
344* updating them to tab as needed.
345*/
346for (i = 0; i < last; i++) {347char ch = src[i];348if (ch != ' ') {349consecutive_spaces = 0;350strbuf_addch(dst, ch);351} else {352consecutive_spaces++;353if (consecutive_spaces == ws_tab_width(ws_rule)) {354strbuf_addch(dst, '\t');355consecutive_spaces = 0;356}357}358}359while (0 < consecutive_spaces--)360strbuf_addch(dst, ' ');361len -= last;362src += last;363fixed = 1;364} else if ((ws_rule & WS_TAB_IN_INDENT) && last_tab_in_indent >= 0) {365/* Expand tabs into spaces */366int start = dst->len;367int last = last_tab_in_indent + 1;368for (i = 0; i < last; i++) {369if (src[i] == '\t')370do {371strbuf_addch(dst, ' ');372} while ((dst->len - start) % ws_tab_width(ws_rule));373else374strbuf_addch(dst, src[i]);375}376len -= last;377src += last;378fixed = 1;379}380
381strbuf_add(dst, src, len);382if (add_cr_to_tail)383strbuf_addch(dst, '\r');384if (add_nl_to_tail)385strbuf_addch(dst, '\n');386if (fixed && error_count)387(*error_count)++;388}
389