reslop 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +1 -2
  3. package/lib/capabilities.js +0 -4
  4. package/lib/clipboard.js +39 -1
  5. package/lib/deps-diff.js +0 -3
  6. package/lib/deps.js +144 -176
  7. package/lib/diff/inline.js +66 -1
  8. package/lib/diff/unit.js +11 -28
  9. package/lib/editor.js +69 -9
  10. package/lib/files.js +17 -16
  11. package/lib/git-deps.js +58 -95
  12. package/lib/github.js +7 -7
  13. package/lib/gitlab.js +3 -3
  14. package/lib/highlight/core.js +11 -0
  15. package/lib/highlight/dot.js +2 -12
  16. package/lib/highlight/log.js +4 -11
  17. package/lib/highlight/txt.js +2 -12
  18. package/lib/keys.js +22 -12
  19. package/lib/manifest.js +0 -4
  20. package/lib/npm-commands.js +1 -22
  21. package/lib/remote.js +1 -7
  22. package/lib/render/branches.js +4 -13
  23. package/lib/render/chrome.js +85 -31
  24. package/lib/render/commits.js +95 -207
  25. package/lib/render/diff.js +31 -1
  26. package/lib/render/files.js +7 -16
  27. package/lib/render/frame.js +95 -110
  28. package/lib/render/notes.js +71 -16
  29. package/lib/render/npm.js +104 -18
  30. package/lib/render/primitives.js +108 -45
  31. package/lib/render/unit.js +4 -1
  32. package/lib/report-parse.js +0 -1
  33. package/lib/report-run.js +0 -1
  34. package/lib/report-test.js +40 -47
  35. package/lib/report-tools.js +0 -7
  36. package/lib/review-merge.js +101 -131
  37. package/lib/review.js +64 -77
  38. package/lib/select.js +52 -0
  39. package/lib/session/actions.js +23 -22
  40. package/lib/session/branches.js +5 -3
  41. package/lib/session/change-actions.js +173 -146
  42. package/lib/session/commit.js +32 -13
  43. package/lib/session/compose.js +120 -77
  44. package/lib/session/dispatch.js +36 -21
  45. package/lib/session/edit-click.js +57 -0
  46. package/lib/session/files-pane.js +24 -24
  47. package/lib/session/items.js +69 -89
  48. package/lib/session/lifecycle.js +31 -96
  49. package/lib/session/load.js +51 -58
  50. package/lib/session/navigation.js +105 -96
  51. package/lib/session/npm.js +158 -77
  52. package/lib/session/ops.js +192 -51
  53. package/lib/session/pointer.js +11 -5
  54. package/lib/session/progress.js +9 -6
  55. package/lib/session/review.js +35 -54
  56. package/lib/session/tasks.js +292 -0
  57. package/lib/session/terminal.js +13 -17
  58. package/lib/session/unit.js +3 -4
  59. package/lib/session/update.js +76 -97
  60. package/lib/session/view.js +17 -14
  61. package/lib/session/watch.js +47 -27
  62. package/lib/session.js +34 -79
  63. package/lib/update.js +1 -5
  64. package/lib/utilities.js +21 -1
  65. package/lib/wrap.js +15 -0
  66. package/package.json +1 -1
  67. package/lib/session/todos.js +0 -293
@@ -5,13 +5,13 @@ const files = require('../files.js');
5
5
  const primitives = require('./primitives.js');
6
6
  const wrap = require('../wrap.js');
7
7
  const { shortAge } = files;
8
- const { wrapMultiline, cursorInWrap } = wrap;
8
+ const { wrapMultiline, wrapDoc, cursorInWrap } = wrap;
9
9
  const { measureColumns, padColumns, joinColumns, paintColumns } = primitives;
10
10
  const { padVisible } = primitives;
11
11
 
12
12
  const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
13
- const { FILE_MARK, paneResult, paintCursorList } = primitives;
14
- const { paintBodyFill, listWindowStart } = primitives;
13
+ const { FILE_MARK, paintCursorList, fieldWindow } = primitives;
14
+ const { paintBodyFill, listLine, paintListWindow } = primitives;
15
15
 
16
16
  const COMMIT_COL_ALIGN = {
17
17
  sha: 'end',
@@ -31,11 +31,11 @@ const COMMIT_COL_FG = {
31
31
 
32
32
  const COMMIT_CURRENT_COL_FG = {
33
33
  sha: THEME.shaDarkFg,
34
+ refs: THEME.shaDarkFg,
34
35
  date: THEME.headerFg,
35
36
  };
36
37
 
37
38
  const REFS_MAX = 28;
38
- const LIST_PAD = 1;
39
39
  const COMMIT_TAIL = 1;
40
40
  const lineCounts = (entry) => {
41
41
  const added = entry.added ?? 0;
@@ -178,46 +178,38 @@ const paintCommitRow = (entry, width, color, selected, cols, current) => {
178
178
  return out;
179
179
  };
180
180
 
181
- const paintCommitEdit = (compose, width, color, entry, cols) => {
181
+ const lineEdit = (text, textX, span, scroll) => ({
182
+ x0: 1,
183
+ textX,
184
+ x1: textX + span,
185
+ start: 0,
186
+ text,
187
+ limit: text.length,
188
+ scroll,
189
+ live: true,
190
+ });
191
+
192
+ const subjectWindow = (compose, rest) => {
193
+ const line = splitMessage(compose.text).line;
194
+ const at = Math.min(compose.cursor ?? 0, line.length);
195
+ return fieldWindow(line, at, compose.scrollCol, rest);
196
+ };
197
+
198
+ const paintCommitEdit = (compose, width, color, entry = null, cols = {}) => {
182
199
  const text = splitMessage(compose.text).line;
183
- if (entry && cols) {
184
- const shown = { ...entry, body: text, subject: text };
185
- const row = paintCommitRow(shown, width, color, true, cols);
186
- const inner = contentWidth(width);
187
- const prefix = ` ${FILE_MARK} `;
188
- const parts = padColumns(commitRowFields(entry), cols, COMMIT_COL_ALIGN);
189
- const suffix = joinColumns(parts, cols);
190
- const rest = Math.max(1, inner - visibleWidth(prefix + suffix));
191
- const clipped = truncateVisible(text, rest);
192
- const prefixW = visibleWidth(prefix);
193
- const col = Math.min(compose.cursor ?? 0, visibleWidth(clipped));
194
- return {
195
- row,
196
- cursor: { x: prefixW + col + 1 },
197
- at: 'replace',
198
- };
199
- }
200
- const inner = contentWidth(width);
201
200
  const prefix = ` ${FILE_MARK} `;
202
201
  const prefixW = visibleWidth(prefix);
203
- const rest = Math.max(1, inner - prefixW);
204
- const clipped = truncateVisible(text, rest);
205
- const pad = Math.max(0, rest - visibleWidth(clipped));
206
- const gap = ' '.repeat(pad);
207
- const tail = ' '.repeat(COMMIT_TAIL);
208
- const fgRgb = THEME.chromeFg;
209
- const bgRgb = THEME.buttonBg;
210
- const nameFg = THEME.buttonHotFg;
211
- let row = `${prefix}${clipped}${gap}${tail}`;
212
- if (color) {
213
- row = `${seq(fgRgb, bgRgb)}${EL}`;
214
- row += paint(prefix, fgRgb, bgRgb, color);
215
- row += paint(clipped, nameFg, bgRgb, color);
216
- row += paint(gap, fgRgb, bgRgb, color);
217
- row += paint(tail, fgRgb, bgRgb, color);
218
- }
219
- const col = Math.min(compose.cursor ?? 0, visibleWidth(clipped));
220
- return { row, cursor: { x: prefixW + col + 1 }, at: 'start' };
202
+ const fields = commitRowFields(entry ?? {});
203
+ const suffix = joinColumns(padColumns(fields, cols, COMMIT_COL_ALIGN), cols);
204
+ const rest = Math.max(1, contentWidth(width) - visibleWidth(prefix + suffix));
205
+ const win = subjectWindow(compose, rest);
206
+ const shown = { ...entry, body: win.text, subject: win.text };
207
+ return {
208
+ row: paintCommitRow(shown, width, color, true, cols),
209
+ cursor: { x: prefixW + win.col + 1, scroll: win.scroll },
210
+ at: entry ? 'replace' : 'start',
211
+ edit: lineEdit(text, prefixW + 1, rest, win.scroll),
212
+ };
221
213
  };
222
214
 
223
215
  const pendingAt = (commits) => {
@@ -467,7 +459,7 @@ const pieceFg = (kind, tone) => {
467
459
  if (kind === 'del') return THEME.delLineFg;
468
460
  if (tone.current) {
469
461
  if (kind === 'hash') return THEME.shaDarkFg;
470
- if (kind === 'refs') return THEME.warnFg;
462
+ if (kind === 'refs') return THEME.shaDarkFg;
471
463
  if (kind === 'text' || kind === 'body') return THEME.mutedFg;
472
464
  return tone.fgRgb;
473
465
  }
@@ -521,23 +513,24 @@ const paintPanelLine = (spec) => {
521
513
  );
522
514
  };
523
515
 
524
- const lineRecord = (commit, row, hit, cursor = null) => ({
525
- commit,
526
- row,
527
- hit,
528
- cursor,
529
- });
516
+ const editFromDoc = (doc, row, textX, span) => {
517
+ if (row < 0 || row >= doc.length) return null;
518
+ const piece = doc[row];
519
+ return {
520
+ x0: 1,
521
+ textX,
522
+ x1: textX + span,
523
+ start: piece.start,
524
+ text: piece.text,
525
+ limit: piece.start + piece.text.length,
526
+ live: true,
527
+ };
528
+ };
530
529
 
531
- const commitLines = (
532
- entry,
533
- index,
534
- width,
535
- color,
536
- selected,
537
- edit,
538
- current,
539
- diffWidths,
540
- ) => {
530
+ const editLines = (editHit) => (editHit ? [editHit] : undefined);
531
+
532
+ const commitLines = (entry, index, spec) => {
533
+ const { width, color, selected, edit, current, diffWidths } = spec;
541
534
  const inner = contentWidth(width);
542
535
  const hashW = visibleWidth(entry.sha ?? '');
543
536
  const widths = diffWidths ?? entryCountWidth(entry);
@@ -545,6 +538,7 @@ const commitLines = (
545
538
  const rights = rightRows(entry, cols.rightW, widths);
546
539
  const left = panelLeft(entry, cols.leftW, edit);
547
540
  const count = Math.max(left.length, rights.length);
541
+ const doc = edit ? wrapDoc(`${edit.text ?? ''}`, cols.leftW) : null;
548
542
  const lines = [];
549
543
  for (let row = 0; row < count; row++) {
550
544
  const line = left[row] ?? { text: '', kind: 'text' };
@@ -563,17 +557,22 @@ const commitLines = (
563
557
  rightKind: 'meta',
564
558
  });
565
559
  const messageRow = entry.sha ? row - 1 : row;
560
+ const first = !entry.sha && row === 0;
566
561
  const caret = edit
567
562
  ? messageCaret(
568
563
  edit.text,
569
564
  edit.cursor,
570
565
  cols.leftW,
571
566
  messageRow,
572
- !entry.sha && row === 0,
567
+ first,
573
568
  selected,
574
569
  )
575
570
  : null;
576
- lines.push(lineRecord(index, painted, true, caret));
571
+ const textX = prefixWidth(first, selected) + 1;
572
+ const editHit = doc
573
+ ? editFromDoc(doc, messageRow, textX, cols.leftW)
574
+ : null;
575
+ lines.push(listLine(painted, index, caret, editLines(editHit)));
577
576
  }
578
577
  return lines;
579
578
  };
@@ -582,6 +581,7 @@ const draftLines = (text, cursor, width, color) => {
582
581
  const inner = contentWidth(width);
583
582
  const leftW = Math.max(1, inner - visibleWidth(FULL_INDENT));
584
583
  const parts = messagePartsFrom(text, leftW);
584
+ const doc = wrapDoc(`${text ?? ''}`, leftW);
585
585
  const lines = [];
586
586
  for (let row = 0; row < parts.length; row++) {
587
587
  const line = parts[row];
@@ -599,7 +599,9 @@ const draftLines = (text, cursor, width, color) => {
599
599
  rightKind: 'meta',
600
600
  });
601
601
  const caret = messageCaret(text, cursor, leftW, row, row === 0, true);
602
- lines.push(lineRecord(-1, painted, false, caret));
602
+ const textX = prefixWidth(row === 0, true) + 1;
603
+ const editHit = editFromDoc(doc, row, textX, leftW);
604
+ lines.push(listLine(painted, -1, caret, editLines(editHit)));
603
605
  }
604
606
  return lines;
605
607
  };
@@ -619,164 +621,58 @@ const composeShowsDraft = (compose, commits) => {
619
621
  return true;
620
622
  };
621
623
 
622
- const gapLine = (width, color) =>
623
- lineRecord(-1, paintBodyFill(width, color, 'files'), false);
624
-
625
- const flattenCommits = (commits, cursor, width, color, compose, rev, short) => {
626
- const lines = [];
627
- const diffWidths = listCountWidth(commits);
624
+ const flattenCommits = (view, commits, width, color) => {
625
+ const cursor = view.commitCursor ?? 0;
626
+ const compose = view.compose ?? null;
628
627
  const editing = compose && compose.kind === 'commit';
629
- const pushGap = () => {
630
- if (lines.length) lines.push(gapLine(width, color));
631
- };
628
+ const diffWidths = listCountWidth(commits);
629
+ const gap = listLine(paintBodyFill(width, color, 'files'), -1);
630
+ const lines = [];
632
631
  if (composeShowsDraft(compose, commits)) {
633
- const draft = draftLines(
634
- compose.text ?? '',
635
- compose.cursor ?? 0,
636
- width,
637
- color,
638
- );
639
- for (const line of draft) lines.push(line);
632
+ const text = compose.text ?? '';
633
+ lines.push(...draftLines(text, compose.cursor ?? 0, width, color));
640
634
  }
641
635
  for (let index = 0; index < commits.length; index++) {
642
- pushGap();
636
+ if (lines.length) lines.push(gap);
637
+ const entry = commits[index];
643
638
  const inplace = composeEditsIndex(compose, commits, index, cursor);
644
639
  const selected = inplace || (!editing && index === cursor);
645
- const edit = inplace
646
- ? { text: compose.text ?? '', cursor: compose.cursor ?? 0 }
647
- : null;
648
- const block = commitLines(
649
- commits[index],
650
- index,
640
+ const text = compose?.text ?? '';
641
+ const caret = compose?.cursor ?? 0;
642
+ const edit = inplace ? { text, cursor: caret } : null;
643
+ const current = viewedCommit(entry, view.rev, view.revShort);
644
+ const block = commitLines(entry, index, {
651
645
  width,
652
646
  color,
653
647
  selected,
654
648
  edit,
655
- viewedCommit(commits[index], rev, short),
649
+ current,
656
650
  diffWidths,
657
- );
658
- for (const line of block) lines.push(line);
651
+ });
652
+ lines.push(...block);
659
653
  }
660
654
  return lines;
661
655
  };
662
656
 
663
657
  const focusLine = (lines, cursor, compose) => {
664
658
  if (compose && compose.kind === 'commit') {
665
- for (let i = 0; i < lines.length; i++) {
666
- if (lines[i].cursor) return i;
667
- }
668
- }
669
- for (let i = 0; i < lines.length; i++) {
670
- const line = lines[i];
671
- if (line.hit && line.commit === cursor) return i;
659
+ const at = lines.findIndex((line) => line.cursor);
660
+ if (at >= 0) return at;
672
661
  }
673
- return 0;
674
- };
675
-
676
- const paintLineWindow = (
677
- lines,
678
- focus,
679
- width,
680
- color,
681
- bodyH,
682
- headerLines,
683
- offset,
684
- ) => {
685
- const pad = bodyH >= LIST_PAD * 2 + 1 ? LIST_PAD : 0;
686
- const listH = bodyH - pad * 2;
687
- const start = listWindowStart(focus, offset, listH, lines.length);
688
- const blank = paintBodyFill(width, color, 'files');
689
- const body = [];
690
- const fileHits = [];
691
- let textCursor = null;
692
- if (pad) body.push(blank);
693
- for (let i = 0; i < listH; i++) {
694
- const line = lines[start + i];
695
- if (!line) break;
696
- body.push(line.row);
697
- if (line.cursor) {
698
- textCursor = { x: line.cursor.x, row: body.length - 1 };
699
- }
700
- if (!line.hit) continue;
701
- const y = headerLines + pad + i + 1;
702
- fileHits.push({ y, cursor: line.commit });
703
- }
704
- if (pad) body.push(blank);
705
- return paneResult({
706
- body,
707
- fileHits,
708
- cursor: textCursor,
709
- listScroll: start,
710
- });
662
+ const at = lines.findIndex((line) => line.hit && line.index === cursor);
663
+ return Math.max(0, at);
711
664
  };
712
665
 
713
- const paintFullCommits = (view, width, color, bodyH, headerLines) => {
666
+ const paintBodyCommits = (view, width, color, bodyH, headerLines) => {
667
+ const pane = { width, color, bodyH, headerLines, offset: view.listScroll };
714
668
  const commits = view.commits ?? [];
715
669
  const cursor = view.commitCursor ?? 0;
716
- const compose = view.compose ?? null;
717
- const lines = flattenCommits(
718
- commits,
719
- cursor,
720
- width,
721
- color,
722
- compose,
723
- view.rev,
724
- view.revShort,
725
- );
726
- const focus = focusLine(lines, cursor, compose);
727
- return paintLineWindow(
728
- lines,
729
- focus,
730
- width,
731
- color,
732
- bodyH,
733
- headerLines,
734
- view.listScroll,
735
- );
736
- };
737
-
738
- const paintBriefCommits = (
739
- commits,
740
- cursor,
741
- width,
742
- color,
743
- bodyH,
744
- headerLines,
745
- extra,
746
- offset,
747
- cols,
748
- rev,
749
- revShort,
750
- ) => {
751
- const paintRow = (entry, rowWidth, rowColor, selected) =>
752
- paintCommitRow(
753
- entry,
754
- rowWidth,
755
- rowColor,
756
- selected,
757
- cols,
758
- viewedCommit(entry, rev, revShort),
759
- );
760
- return paintCursorList(
761
- commits,
762
- cursor,
763
- width,
764
- color,
765
- bodyH,
766
- headerLines,
767
- paintRow,
768
- extra,
769
- offset,
770
- );
771
- };
772
-
773
- const paintBodyCommits = (view, width, color, bodyH, headerLines) => {
774
670
  if (view.commitView === 'full') {
775
- return paintFullCommits(view, width, color, bodyH, headerLines);
671
+ const lines = flattenCommits(view, commits, width, color);
672
+ const focus = focusLine(lines, cursor, view.compose);
673
+ return paintListWindow(lines.length, focus, pane, (i) => lines[i]);
776
674
  }
777
- const commits = view.commits ?? [];
778
675
  const cols = measureColumns(commits, commitRowFields, COMMIT_COL_ALIGN);
779
- const cursor = view.commitCursor ?? 0;
780
676
  const extra = commitEditRow(
781
677
  view.compose,
782
678
  commits,
@@ -785,19 +681,11 @@ const paintBodyCommits = (view, width, color, bodyH, headerLines) => {
785
681
  color,
786
682
  cols,
787
683
  );
788
- return paintBriefCommits(
789
- commits,
790
- cursor,
791
- width,
792
- color,
793
- bodyH,
794
- headerLines,
795
- extra,
796
- view.listScroll,
797
- cols,
798
- view.rev,
799
- view.revShort,
800
- );
684
+ const paintRow = (entry, selected) => {
685
+ const current = viewedCommit(entry, view.rev, view.revShort);
686
+ return paintCommitRow(entry, width, color, selected, cols, current);
687
+ };
688
+ return paintCursorList(commits, cursor, pane, paintRow, extra);
801
689
  };
802
690
 
803
691
  module.exports = {
@@ -332,6 +332,32 @@ const codeCursorInRows = (rows, split, width, cursor, scrollCol = 0) => {
332
332
  return null;
333
333
  };
334
334
 
335
+ const codeEditHits = (rows, split, width, scrollCol = 0) => {
336
+ const { leftW, rightW } = splitWidths(width);
337
+ const hits = [];
338
+ for (let i = 0; i < rows.length; i++) {
339
+ const line = split ? rows[i].right : rows[i];
340
+ if (!line || line.editStart === undefined) continue;
341
+ const lineW = split ? rightW : width;
342
+ const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
343
+ const pane = split ? leftW + 1 : 0;
344
+ const textX = pane + gutterW + 1;
345
+ const end = split ? pane + rightW : width;
346
+ hits.push({
347
+ row: i,
348
+ x0: pane + 1,
349
+ textX,
350
+ x1: end + 1,
351
+ scroll: scrollCol,
352
+ start: line.editStart,
353
+ text: line.text ?? '',
354
+ limit: line.editEnd,
355
+ live: true,
356
+ });
357
+ }
358
+ return hits;
359
+ };
360
+
335
361
  const codeInnerWidth = (width, layout = 'unified') => {
336
362
  const lineW = layout === 'side' ? splitWidths(width).rightW : width;
337
363
  const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
@@ -376,7 +402,10 @@ const paintBodyDiff = (view, width, color) => {
376
402
  }
377
403
  const edit = overlay ? overlay.cursor : null;
378
404
  const cursor = codeCursorInRows(lines, splitBody, width, edit, scrollCol);
379
- return paneResult({ body, cursor, splitBody });
405
+ const editHits = editing
406
+ ? codeEditHits(lines, splitBody, width, scrollCol)
407
+ : [];
408
+ return paneResult({ body, cursor, splitBody, editHits });
380
409
  };
381
410
 
382
411
  module.exports = {
@@ -386,5 +415,6 @@ module.exports = {
386
415
  codeInnerWidth,
387
416
  paintBodyDiff,
388
417
  codeCursorInRows,
418
+ codeEditHits,
389
419
  expandSoftRows,
390
420
  };
@@ -5,7 +5,7 @@ const files = require('../files.js');
5
5
  const primitives = require('./primitives.js');
6
6
  const { measureColumns, padColumns } = primitives;
7
7
 
8
- const { isTodosEntry, shortAge } = files;
8
+ const { isTasksEntry, shortAge } = files;
9
9
  const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
10
10
  const { fill, FILE_MARK, paintCursorList } = primitives;
11
11
 
@@ -30,7 +30,7 @@ const FILE_DATE_GAP = ' ';
30
30
  const signedCount = (sign, n) => `${sign}${n ?? 0}`;
31
31
 
32
32
  const fileRowFields = (entry) => {
33
- if (isTodosEntry(entry)) {
33
+ if (isTasksEntry(entry)) {
34
34
  const n = `${entry.staged ?? 0}`;
35
35
  const m = `${entry.remaining ?? 0}`;
36
36
  return { add: '', del: '', status: '', n, m, date: '' };
@@ -105,7 +105,7 @@ const paintTodosRow = (entry, width, color, selected, cols) => {
105
105
  };
106
106
 
107
107
  const paintFileRow = (entry, width, color, selected, cols) => {
108
- if (isTodosEntry(entry)) {
108
+ if (isTasksEntry(entry)) {
109
109
  return paintTodosRow(entry, width, color, selected, cols);
110
110
  }
111
111
  const inner = Math.max(1, width - 1);
@@ -132,19 +132,10 @@ const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
132
132
  const files = view.files ?? [];
133
133
  const cols = measureColumns(files, fileRowFields, FILE_COL_ALIGN);
134
134
  const cursor = view.fileCursor ?? 0;
135
- const paintRow = (entry, rowWidth, rowColor, selected) =>
136
- paintFileRow(entry, rowWidth, rowColor, selected, cols);
137
- return paintCursorList(
138
- files,
139
- cursor,
140
- width,
141
- color,
142
- bodyH,
143
- headerLines,
144
- paintRow,
145
- null,
146
- view.listScroll,
147
- );
135
+ const pane = { width, color, bodyH, headerLines, offset: view.listScroll };
136
+ const paintRow = (entry, selected) =>
137
+ paintFileRow(entry, width, color, selected, cols);
138
+ return paintCursorList(files, cursor, pane, paintRow);
148
139
  };
149
140
 
150
141
  module.exports = { paintBodyFiles };