Mercurial > hg
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) |