+int historyBufferScroll(int n) {
+ if (IS_SET(MODE_ALTSCREEN) || !n) return histOp;
+ int p=abs(n=(n<0) ? max(n,-term.row) : min(n,term.row)), r=term.row-p,
+ s=sizeof(*term.dirty), *ptr=histOp?&histOff:&insertOff;
+ if (!histMode || histOp) tfulldirt(); else {
+ memmove(&term.dirty[-min(n,0)], &term.dirty[max(n,0)], s*r);
+ memset(&term.dirty[n>0 ? r : 0], 0, s * p);
+ }
+ int const prevOffBuf = sel.alt ? 0 : insertOff + term.row;
+ term.line = &buf[*ptr = (buffSize+*ptr+n) % buffSize];
+ // Cut part of selection removed from buffer, and update sel.ne/b.
+ if (sel.ob.x != -1 && !histOp && n) {
+ int const offBuf = sel.alt ? 0 : insertOff + term.row,
+ pb = rangeY(sel.ob.y - prevOffBuf),
+ pe = rangeY(sel.oe.y - prevOffBuf);
+ int const b = rangeY(sel.ob.y - offBuf), nln = n < 0,
+ e = rangeY(sel.oe.y - offBuf), last = offBuf - nln;
+ if (pb != b && ((pb < b) != nln)) sel.ob.y = last;
+ if (pe != e && ((pe < e) != nln)) sel.oe.y = last;
+ if (sel.oe.y == last && sel.ob.y == last) selclear();
+ }
+ selnormalize();
+ // Clear the new region exposed by the shift.
+ if (!histOp) tclearregion(0, n>0?r+1:0, buffCols-1, n>0?term.row:p-1);
+ return 1;
+}