comparison hgext/histedit.py @ 48200:b6fc7d188f68

chistedit: move view state from a dict to a custom class Differential Revision: https://phab.mercurial-scm.org/D11636
author Martin von Zweigbergk <martinvonz@google.com>
date Mon, 11 Oct 2021 22:47:37 -0700
parents 5ced12cfa41b
children 8ac61257c807
comparison
equal deleted inserted replaced
48199:9d0e5629cfbf 48200:b6fc7d188f68
1195 1195
1196 # ============ EVENTS =============== 1196 # ============ EVENTS ===============
1197 def movecursor(state, oldpos, newpos): 1197 def movecursor(state, oldpos, newpos):
1198 """Change the rule/changeset that the cursor is pointing to, regardless of 1198 """Change the rule/changeset that the cursor is pointing to, regardless of
1199 current mode (you can switch between patches from the view patch window).""" 1199 current mode (you can switch between patches from the view patch window)."""
1200 state[b'pos'] = newpos 1200 state.pos = newpos
1201 1201
1202 mode, _ = state[b'mode'] 1202 mode, _ = state.mode
1203 if mode == MODE_RULES: 1203 if mode == MODE_RULES:
1204 # Scroll through the list by updating the view for MODE_RULES, so that 1204 # Scroll through the list by updating the view for MODE_RULES, so that
1205 # even if we are not currently viewing the rules, switching back will 1205 # even if we are not currently viewing the rules, switching back will
1206 # result in the cursor's rule being visible. 1206 # result in the cursor's rule being visible.
1207 modestate = state[b'modes'][MODE_RULES] 1207 modestate = state.modes[MODE_RULES]
1208 if newpos < modestate[b'line_offset']: 1208 if newpos < modestate[b'line_offset']:
1209 modestate[b'line_offset'] = newpos 1209 modestate[b'line_offset'] = newpos
1210 elif newpos > modestate[b'line_offset'] + state[b'page_height'] - 1: 1210 elif newpos > modestate[b'line_offset'] + state.page_height - 1:
1211 modestate[b'line_offset'] = newpos - state[b'page_height'] + 1 1211 modestate[b'line_offset'] = newpos - state.page_height + 1
1212 1212
1213 # Reset the patch view region to the top of the new patch. 1213 # Reset the patch view region to the top of the new patch.
1214 state[b'modes'][MODE_PATCH][b'line_offset'] = 0 1214 state.modes[MODE_PATCH][b'line_offset'] = 0
1215 1215
1216 1216
1217 def changemode(state, mode): 1217 def changemode(state, mode):
1218 curmode, _ = state[b'mode'] 1218 curmode, _ = state.mode
1219 state[b'mode'] = (mode, curmode) 1219 state.mode = (mode, curmode)
1220 if mode == MODE_PATCH: 1220 if mode == MODE_PATCH:
1221 state[b'modes'][MODE_PATCH][b'patchcontents'] = patchcontents(state) 1221 state.modes[MODE_PATCH][b'patchcontents'] = patchcontents(state)
1222 1222
1223 1223
1224 def makeselection(state, pos): 1224 def makeselection(state, pos):
1225 state[b'selected'] = pos 1225 state.selected = pos
1226 1226
1227 1227
1228 def swap(state, oldpos, newpos): 1228 def swap(state, oldpos, newpos):
1229 """Swap two positions and calculate necessary conflicts in 1229 """Swap two positions and calculate necessary conflicts in
1230 O(|newpos-oldpos|) time""" 1230 O(|newpos-oldpos|) time"""
1231 1231
1232 rules = state[b'rules'] 1232 rules = state.rules
1233 assert 0 <= oldpos < len(rules) and 0 <= newpos < len(rules) 1233 assert 0 <= oldpos < len(rules) and 0 <= newpos < len(rules)
1234 1234
1235 rules[oldpos], rules[newpos] = rules[newpos], rules[oldpos] 1235 rules[oldpos], rules[newpos] = rules[newpos], rules[oldpos]
1236 1236
1237 # TODO: swap should not know about histeditrule's internals 1237 # TODO: swap should not know about histeditrule's internals
1242 end = max(oldpos, newpos) 1242 end = max(oldpos, newpos)
1243 for r in pycompat.xrange(start, end + 1): 1243 for r in pycompat.xrange(start, end + 1):
1244 rules[newpos].checkconflicts(rules[r]) 1244 rules[newpos].checkconflicts(rules[r])
1245 rules[oldpos].checkconflicts(rules[r]) 1245 rules[oldpos].checkconflicts(rules[r])
1246 1246
1247 if state[b'selected']: 1247 if state.selected:
1248 makeselection(state, newpos) 1248 makeselection(state, newpos)
1249 1249
1250 1250
1251 def changeaction(state, pos, action): 1251 def changeaction(state, pos, action):
1252 """Change the action state on the given position to the new action""" 1252 """Change the action state on the given position to the new action"""
1253 rules = state[b'rules'] 1253 rules = state.rules
1254 assert 0 <= pos < len(rules) 1254 assert 0 <= pos < len(rules)
1255 rules[pos].action = action 1255 rules[pos].action = action
1256 1256
1257 1257
1258 def cycleaction(state, pos, next=False): 1258 def cycleaction(state, pos, next=False):
1259 """Changes the action state the next or the previous action from 1259 """Changes the action state the next or the previous action from
1260 the action list""" 1260 the action list"""
1261 rules = state[b'rules'] 1261 rules = state.rules
1262 assert 0 <= pos < len(rules) 1262 assert 0 <= pos < len(rules)
1263 current = rules[pos].action 1263 current = rules[pos].action
1264 1264
1265 assert current in KEY_LIST 1265 assert current in KEY_LIST
1266 1266
1273 1273
1274 1274
1275 def changeview(state, delta, unit): 1275 def changeview(state, delta, unit):
1276 """Change the region of whatever is being viewed (a patch or the list of 1276 """Change the region of whatever is being viewed (a patch or the list of
1277 changesets). 'delta' is an amount (+/- 1) and 'unit' is 'page' or 'line'.""" 1277 changesets). 'delta' is an amount (+/- 1) and 'unit' is 'page' or 'line'."""
1278 mode, _ = state[b'mode'] 1278 mode, _ = state.mode
1279 if mode != MODE_PATCH: 1279 if mode != MODE_PATCH:
1280 return 1280 return
1281 mode_state = state[b'modes'][mode] 1281 mode_state = state.modes[mode]
1282 num_lines = len(mode_state[b'patchcontents']) 1282 num_lines = len(mode_state[b'patchcontents'])
1283 page_height = state[b'page_height'] 1283 page_height = state.page_height
1284 unit = page_height if unit == b'page' else 1 1284 unit = page_height if unit == b'page' else 1
1285 num_pages = 1 + (num_lines - 1) // page_height 1285 num_pages = 1 + (num_lines - 1) // page_height
1286 max_offset = (num_pages - 1) * page_height 1286 max_offset = (num_pages - 1) * page_height
1287 newline = mode_state[b'line_offset'] + delta * unit 1287 newline = mode_state[b'line_offset'] + delta * unit
1288 mode_state[b'line_offset'] = max(0, min(max_offset, newline)) 1288 mode_state[b'line_offset'] = max(0, min(max_offset, newline))
1292 """Change state based on the current character input 1292 """Change state based on the current character input
1293 1293
1294 This takes the current state and based on the current character input from 1294 This takes the current state and based on the current character input from
1295 the user we change the state. 1295 the user we change the state.
1296 """ 1296 """
1297 selected = state[b'selected'] 1297 selected = state.selected
1298 oldpos = state[b'pos'] 1298 oldpos = state.pos
1299 rules = state[b'rules'] 1299 rules = state.rules
1300 1300
1301 if ch in (curses.KEY_RESIZE, b"KEY_RESIZE"): 1301 if ch in (curses.KEY_RESIZE, b"KEY_RESIZE"):
1302 return E_RESIZE 1302 return E_RESIZE
1303 1303
1304 lookup_ch = ch 1304 lookup_ch = ch
1305 if ch is not None and b'0' <= ch <= b'9': 1305 if ch is not None and b'0' <= ch <= b'9':
1306 lookup_ch = b'0' 1306 lookup_ch = b'0'
1307 1307
1308 curmode, prevmode = state[b'mode'] 1308 curmode, prevmode = state.mode
1309 action = KEYTABLE[curmode].get( 1309 action = KEYTABLE[curmode].get(
1310 lookup_ch, KEYTABLE[b'global'].get(lookup_ch) 1310 lookup_ch, KEYTABLE[b'global'].get(lookup_ch)
1311 ) 1311 )
1312 if action is None: 1312 if action is None:
1313 return 1313 return
1389 return line 1389 return line
1390 return line[: n - 2] + b' >' 1390 return line[: n - 2] + b' >'
1391 1391
1392 1392
1393 def patchcontents(state): 1393 def patchcontents(state):
1394 repo = state[b'repo'] 1394 repo = state.repo
1395 rule = state[b'rules'][state[b'pos']] 1395 rule = state.rules[state.pos]
1396 displayer = logcmdutil.changesetdisplayer( 1396 displayer = logcmdutil.changesetdisplayer(
1397 repo.ui, repo, {b"patch": True, b"template": b"status"}, buffered=True 1397 repo.ui, repo, {b"patch": True, b"template": b"status"}, buffered=True
1398 ) 1398 )
1399 overrides = {(b'ui', b'verbose'): True} 1399 overrides = {(b'ui', b'verbose'): True}
1400 with repo.ui.configoverride(overrides, source=b'histedit'): 1400 with repo.ui.configoverride(overrides, source=b'histedit'):
1401 displayer.show(rule.ctx) 1401 displayer.show(rule.ctx)
1402 displayer.close() 1402 displayer.close()
1403 return displayer.hunk[rule.ctx.rev()].splitlines() 1403 return displayer.hunk[rule.ctx.rev()].splitlines()
1404
1405
1406 class _chistedit_state(object):
1407 def __init__(
1408 self,
1409 repo,
1410 rules,
1411 ):
1412 self.repo = repo
1413 self.rules = rules
1414 self.pos = 0
1415 self.selected = None
1416 self.mode = (MODE_INIT, MODE_INIT)
1417 self.page_height = None
1418 self.modes = {
1419 MODE_RULES: {
1420 b'line_offset': 0,
1421 },
1422 MODE_PATCH: {
1423 b'line_offset': 0,
1424 },
1425 }
1404 1426
1405 1427
1406 def _chisteditmain(repo, rules, stdscr): 1428 def _chisteditmain(repo, rules, stdscr):
1407 try: 1429 try:
1408 curses.use_default_colors() 1430 curses.use_default_colors()
1431 pass 1453 pass
1432 1454
1433 def rendercommit(win, state): 1455 def rendercommit(win, state):
1434 """Renders the commit window that shows the log of the current selected 1456 """Renders the commit window that shows the log of the current selected
1435 commit""" 1457 commit"""
1436 pos = state[b'pos'] 1458 pos = state.pos
1437 rules = state[b'rules'] 1459 rules = state.rules
1438 rule = rules[pos] 1460 rule = rules[pos]
1439 1461
1440 ctx = rule.ctx 1462 ctx = rule.ctx
1441 win.box() 1463 win.box()
1442 1464
1495 """ 1517 """
1496 return help.splitlines() 1518 return help.splitlines()
1497 1519
1498 def renderhelp(win, state): 1520 def renderhelp(win, state):
1499 maxy, maxx = win.getmaxyx() 1521 maxy, maxx = win.getmaxyx()
1500 mode, _ = state[b'mode'] 1522 mode, _ = state.mode
1501 for y, line in enumerate(helplines(mode)): 1523 for y, line in enumerate(helplines(mode)):
1502 if y >= maxy: 1524 if y >= maxy:
1503 break 1525 break
1504 addln(win, y, 0, line, curses.color_pair(COLOR_HELP)) 1526 addln(win, y, 0, line, curses.color_pair(COLOR_HELP))
1505 win.noutrefresh() 1527 win.noutrefresh()
1506 1528
1507 def renderrules(rulesscr, state): 1529 def renderrules(rulesscr, state):
1508 rules = state[b'rules'] 1530 rules = state.rules
1509 pos = state[b'pos'] 1531 pos = state.pos
1510 selected = state[b'selected'] 1532 selected = state.selected
1511 start = state[b'modes'][MODE_RULES][b'line_offset'] 1533 start = state.modes[MODE_RULES][b'line_offset']
1512 1534
1513 conflicts = [r.ctx for r in rules if r.conflicts] 1535 conflicts = [r.ctx for r in rules if r.conflicts]
1514 if len(conflicts) > 0: 1536 if len(conflicts) > 0:
1515 line = b"potential conflict in %s" % b','.join( 1537 line = b"potential conflict in %s" % b','.join(
1516 map(pycompat.bytestr, conflicts) 1538 map(pycompat.bytestr, conflicts)
1517 ) 1539 )
1518 addln(rulesscr, -1, 0, line, curses.color_pair(COLOR_WARN)) 1540 addln(rulesscr, -1, 0, line, curses.color_pair(COLOR_WARN))
1519 1541
1520 for y, rule in enumerate(rules[start:]): 1542 for y, rule in enumerate(rules[start:]):
1521 if y >= state[b'page_height']: 1543 if y >= state.page_height:
1522 break 1544 break
1523 if len(rule.conflicts) > 0: 1545 if len(rule.conflicts) > 0:
1524 rulesscr.addstr(y, 0, b" ", curses.color_pair(COLOR_WARN)) 1546 rulesscr.addstr(y, 0, b" ", curses.color_pair(COLOR_WARN))
1525 else: 1547 else:
1526 rulesscr.addstr(y, 0, b" ", curses.COLOR_BLACK) 1548 rulesscr.addstr(y, 0, b" ", curses.COLOR_BLACK)
1572 else: 1594 else:
1573 win.addstr(y, 0, line) 1595 win.addstr(y, 0, line)
1574 win.noutrefresh() 1596 win.noutrefresh()
1575 1597
1576 def renderpatch(win, state): 1598 def renderpatch(win, state):
1577 start = state[b'modes'][MODE_PATCH][b'line_offset'] 1599 start = state.modes[MODE_PATCH][b'line_offset']
1578 content = state[b'modes'][MODE_PATCH][b'patchcontents'] 1600 content = state.modes[MODE_PATCH][b'patchcontents']
1579 renderstring(win, state, content[start:], diffcolors=True) 1601 renderstring(win, state, content[start:], diffcolors=True)
1580 1602
1581 def layout(mode): 1603 def layout(mode):
1582 maxy, maxx = stdscr.getmaxyx() 1604 maxy, maxx = stdscr.getmaxyx()
1583 helplen = len(helplines(mode)) 1605 helplen = len(helplines(mode))
1599 def drawvertwin(size, y, x): 1621 def drawvertwin(size, y, x):
1600 win = curses.newwin(size[0], size[1], y, x) 1622 win = curses.newwin(size[0], size[1], y, x)
1601 y += size[0] 1623 y += size[0]
1602 return win, y, x 1624 return win, y, x
1603 1625
1604 state = { 1626 state = _chistedit_state(repo, rules)
1605 b'pos': 0,
1606 b'rules': rules,
1607 b'selected': None,
1608 b'mode': (MODE_INIT, MODE_INIT),
1609 b'page_height': None,
1610 b'modes': {
1611 MODE_RULES: {
1612 b'line_offset': 0,
1613 },
1614 MODE_PATCH: {
1615 b'line_offset': 0,
1616 },
1617 },
1618 b'repo': repo,
1619 }
1620 1627
1621 # eventloop 1628 # eventloop
1622 ch = None 1629 ch = None
1623 stdscr.clear() 1630 stdscr.clear()
1624 stdscr.refresh() 1631 stdscr.refresh()
1625 while True: 1632 while True:
1626 oldmode, unused = state[b'mode'] 1633 oldmode, unused = state.mode
1627 if oldmode == MODE_INIT: 1634 if oldmode == MODE_INIT:
1628 changemode(state, MODE_RULES) 1635 changemode(state, MODE_RULES)
1629 e = event(state, ch) 1636 e = event(state, ch)
1630 1637
1631 if e == E_QUIT: 1638 if e == E_QUIT:
1632 return False 1639 return False
1633 if e == E_HISTEDIT: 1640 if e == E_HISTEDIT:
1634 return state[b'rules'] 1641 return state.rules
1635 else: 1642 else:
1636 if e == E_RESIZE: 1643 if e == E_RESIZE:
1637 size = screen_size() 1644 size = screen_size()
1638 if size != stdscr.getmaxyx(): 1645 if size != stdscr.getmaxyx():
1639 curses.resizeterm(*size) 1646 curses.resizeterm(*size)
1640 1647
1641 curmode, unused = state[b'mode'] 1648 curmode, unused = state.mode
1642 sizes = layout(curmode) 1649 sizes = layout(curmode)
1643 if curmode != oldmode: 1650 if curmode != oldmode:
1644 state[b'page_height'] = sizes[b'main'][0] 1651 state.page_height = sizes[b'main'][0]
1645 # Adjust the view to fit the current screen size. 1652 # Adjust the view to fit the current screen size.
1646 movecursor(state, state[b'pos'], state[b'pos']) 1653 movecursor(state, state.pos, state.pos)
1647 1654
1648 # Pack the windows against the top, each pane spread across the 1655 # Pack the windows against the top, each pane spread across the
1649 # full width of the screen. 1656 # full width of the screen.
1650 y, x = (0, 0) 1657 y, x = (0, 0)
1651 helpwin, y, x = drawvertwin(sizes[b'help'], y, x) 1658 helpwin, y, x = drawvertwin(sizes[b'help'], y, x)