Repository: zyedidia/micro
File: internal/buffer/line_array.go (Go)

Code before:
	defer la.lock.Unlock()
	return la.substr(start, end)
}

// LinesNum returns the number of lines in the buffer
func (la *LineArray) LinesNum() int {
	la.lock.Lock()
	defer la.lock.Unlock()
	return len(la.lines)
}

// Start returns the start of the buffer
func (la *LineArray) Start() Loc {
	return Loc{0, 0}
}

// End returns the location of the last character in the buffer
func (la *LineArray) End() Loc {
	la.lock.Lock()
	defer la.lock.Unlock()
	numlines := len(la.lines)
	return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
}

// LineBytes returns line n as an array of bytes (no lock)
func (la *LineArray) LineBytesNoLock(n int) []byte {
	if n >= len(la.lines) || n < 0 {
		return []byte{}
	}
	return la.lines[n].data
}

// LineBytes returns line n as an array of bytes
func (la *LineArray) LineBytes(n int) []byte {
	la.lock.Lock()
	defer la.lock.Unlock()
	return la.LineBytesNoLock(n)
}

// State gets the highlight state for the given line number (no lock)
func (la *LineArray) StateNoLock(lineN int) highlight.State {
	if lineN >= len(la.lines) || lineN < 0 {
		return nil
	}
	return la.lines[lineN].state
}

// State gets the highlight state for the given line number
func (la *LineArray) State(lineN int) highlight.State {
	la.lock.Lock()
	defer la.lock.Unlock()

Review comment (performance):
Any actual examples of those "bulk modifications"?

I'm assuming there are none. So the actual goal of this commit is not to support bulk modifications but to allow the highlighter to explicitly lock the linearray via `input.Lock()` and `input.Unlock()` (in the next commit)?

AFAICS since the next commit the highlighter is no longer using lockful variants of these functions, it only uses NoLock variants, right?

So I suggest to simply remove locking from the original functions (i.e. make it explicit that the caller should explicitly synchronize with buffer modifications using `input.Lock()` and `input.Unlock()`), instead of extending the interface with all these *NoLock variants, which is ugly and just creates a false sense of safety when using the locking variants.

...There is an exception though: `Match()` is called from the main goroutine in `getStyle()`, to find out the highlight group which was set by the highlighter. If `Match()` will be non-locking, how do we synchronize access to `match` between main and async goroutines in this case? We may add `MatchLocked()`, or explicitly do `Lock()` and `Unlock()` in `getStyle()`... But now it seems to me that actually the best way is to restore those original per-line locks in `SetMatch()`, `SetState()` etc, and keep using them for the same purpose they were used before: to synchronize access to `state` and `match` only (unlike our newly introduced linearray lock, which is to synchronize access to the linearray itself and to the lines contents). This also seems more efficient: `displayBuffer()` will not wait on the entire linearray lock, it will only wait on a single line lock.

This (adding linearray lock but keeping per-line locks as well) would make our entire change very simple: it would basically just add `input.Lock()` and `input.Unlock()`. And that would be great actually, I'd really like our fix for the race issue to be as simple and non-intrusive (and yet effective) as possible.

Code after:
func (la *LineArray) Start() Loc {
	return Loc{0, 0}
}

// End returns the location of the last character in the buffer
func (la *LineArray) End() Loc {
	numlines := len(la.lines)
	return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
}

// LineBytes returns line n as an array of bytes
func (la *LineArray) LineBytes(lineN int) []byte {
	if lineN >= len(la.lines) || lineN < 0 {
		return []byte{}
	}
	return la.lines[lineN].data
}

// State gets the highlight state for the given line number
func (la *LineArray) State(lineN int) highlight.State {
	la.lines[lineN].lock.Lock()
	defer la.lines[lineN].lock.Unlock()
	return la.lines[lineN].state
}

// SetState sets the highlight state at the given line number
func (la *LineArray) SetState(lineN int, s highlight.State) {
	la.lines[lineN].lock.Lock()
	defer la.lines[lineN].lock.Unlock()
	la.lines[lineN].state = s
}

// SetMatch sets the match at the given line number
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
	la.lines[lineN].lock.Lock()
	defer la.lines[lineN].lock.Unlock()
	la.lines[lineN].match = m
}

// Match retrieves the match for the given line number
func (la *LineArray) Match(lineN int) highlight.LineMatch {
	la.lines[lineN].lock.Lock()
	defer la.lines[lineN].lock.Unlock()
	return la.lines[lineN].match
}

// Locks the whole LineArray
func (la *LineArray) Lock() {
	la.lock.Lock()
}