diff options
Diffstat (limited to 'gui/internal/model/forms.go')
| -rw-r--r-- | gui/internal/model/forms.go | 57 |
1 files changed, 56 insertions, 1 deletions
diff --git a/gui/internal/model/forms.go b/gui/internal/model/forms.go index f0a3983..290bbc8 100644 --- a/gui/internal/model/forms.go +++ b/gui/internal/model/forms.go @@ -88,16 +88,46 @@ func (r *Rules) ReplaceForm(i int, text string) error { // DeleteForm removes form i and the comment lines directly above it - its // block (GUI design ยง5.1). A comment separated from the form by a blank // line belongs to the file, not to the form, and stays. +// +// A block is whole lines, which is wrong when another form shares one: +// deleting the first of "(exclude A) (exclude B)" would take B as well, +// silently, leaving a file that still parses and still checks clean. When +// the block would reach another form, only the form's own text goes, with +// the spaces that followed it. func (r *Rules) DeleteForm(i int) error { - f, err := r.form(i) + forms, err := r.Forms() if err != nil { return err } + if i < 0 || i >= len(forms) { + return fmt.Errorf("model: no form %d", i) + } + f := forms[i] start, end := r.block(f) + if r.blockTouchesAnotherForm(forms, i, start, end) { + start, end = f.Pos.Offset, f.End.Offset + for end < len(r.Text) && (r.Text[end] == ' ' || r.Text[end] == '\t') { + end++ + } + } r.Text = join(r.Text[:start], r.Text[end:]) return nil } +// blockTouchesAnotherForm reports whether the span start..end covers any +// part of a form other than forms[i]. +func (r *Rules) blockTouchesAnotherForm(forms []Form, i, start, end int) bool { + for j, other := range forms { + if j == i { + continue + } + if other.Pos.Offset < end && other.End.Offset > start { + return true + } + } + return false +} + // MoveForm moves form i one place up (delta -1) or down (delta 1), with its // comments. Moving past either end does nothing. func (r *Rules) MoveForm(i, delta int) error { @@ -222,6 +252,31 @@ func (r *Rules) block(f Form) (start, end int) { return start, end } +// clearForm removes the form spanning start..end from text. It takes the +// whole line - with the comment trailing it, and the blank it would leave +// behind - only when the line holds nothing else. A setting written beside +// something else, which is what the Settings window itself writes when it +// creates "(defaults\n (case ignore))", loses only itself: taking the line +// took the closing paren of defaults with it and broke the file. +func clearForm(text string, start, end int) string { + ls, le := lineStart(text, start), lineEnd(text, end) + before := strings.TrimSpace(text[ls:start]) + after := text[end:le] + if i := strings.IndexByte(after, ';'); i >= 0 { + // A comment after it on the same line describes it, and goes too. + after = after[:i] + } + if before == "" && strings.TrimSpace(after) == "" { + return join(text[:ls], text[le:]) + } + // Something else shares the line: drop the form and the spaces that + // followed it, and nothing more. + for end < len(text) && (text[end] == ' ' || text[end] == '\t') { + end++ + } + return text[:start] + text[end:] +} + // lineStart is the offset just after the newline before at. func lineStart(s string, at int) int { if at > len(s) { |
