@parca/profile 0.16.347 → 0.16.348

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,10 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.16.348](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.347...@parca/profile@0.16.348) (2024-02-27)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
6
10
  ## [0.16.347](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.346...@parca/profile@0.16.347) (2024-02-22)
7
11
 
8
12
  **Note:** Version bump only for package @parca/profile
@@ -1,3 +1,4 @@
1
+ import { MouseEventHandler } from 'react';
1
2
  import { Vector } from 'apache-arrow';
2
3
  import { type createElementProps } from 'react-syntax-highlighter';
3
4
  interface RendererProps {
@@ -11,6 +12,6 @@ interface HighlighterProps {
11
12
  content: string;
12
13
  renderer?: Renderer;
13
14
  }
14
- export declare const profileAwareRenderer: (cumulative: Vector | null, flat: Vector | null, total: bigint, filtered: bigint) => Renderer;
15
+ export declare const profileAwareRenderer: (cumulative: Vector | null, flat: Vector | null, total: bigint, filtered: bigint, onContextMenu: MouseEventHandler<HTMLDivElement>) => Renderer;
15
16
  export declare const Highlighter: ({ file, content, renderer }: HighlighterProps) => JSX.Element;
16
17
  export {};
@@ -17,10 +17,11 @@ import { scaleLinear } from 'd3-scale';
17
17
  import SyntaxHighlighter, { createElement } from 'react-syntax-highlighter';
18
18
  import { atomOneDark, atomOneLight } from 'react-syntax-highlighter/dist/esm/styles/hljs';
19
19
  import { Tooltip } from 'react-tooltip';
20
- import { useParcaContext, useURLState } from '@parca/components';
20
+ import { useParcaContext } from '@parca/components';
21
21
  import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
22
22
  import { LineNo } from './LineNo';
23
23
  import { langaugeFromFile } from './lang-detector';
24
+ import useLineRange from './useSelectedLineRange';
24
25
  // cannot make this a function on the number as we need the classes to be static for tailwind
25
26
  const charsToWidthMap = {
26
27
  1: 'w-3',
@@ -55,17 +56,29 @@ const LineProfileMetadata = ({ value, total, filtered, }) => {
55
56
  const charsToWidth = (chars) => {
56
57
  return charsToWidthMap[chars];
57
58
  };
58
- export const profileAwareRenderer = (cumulative, flat, total, filtered) => {
59
+ export const profileAwareRenderer = (cumulative, flat, total, filtered, onContextMenu) => {
59
60
  return function ProfileAwareRenderer({ rows, stylesheet, useInlineStyles, }) {
60
61
  const lineNumberWidth = charsToWidth(rows.length.toString().length);
61
- const [sourceLine, setSourceLine] = useURLState({ param: 'source_line', navigateTo: () => { } });
62
+ const { startLine, endLine, setLineRange } = useLineRange();
62
63
  return (_jsx(_Fragment, { children: rows.map((node, i) => {
63
64
  const lineNumber = node.children[0].children[0].value;
64
- const isCurrentLine = sourceLine === lineNumber.toString();
65
+ const isCurrentLine = lineNumber >= startLine && lineNumber <= endLine;
65
66
  node.children = node.children.slice(1);
66
- return (_jsxs("div", { className: "flex gap-1", children: [_jsx("div", { className: cx('shrink-0 overflow-hidden border-r border-r-gray-200 text-right dark:border-r-gray-700', lineNumberWidth), children: _jsx(LineNo, { value: lineNumber, isCurrent: isCurrentLine, setCurrentLine: () => setSourceLine(lineNumber.toString()) }) }), _jsx(LineProfileMetadata, { value: cumulative?.get(i) ?? 0n, total: total, filtered: filtered }), _jsx(LineProfileMetadata, { value: flat?.get(i) ?? 0n, total: total, filtered: filtered }), _jsx("div", { className: cx('w-full flex-grow-0 border-l border-gray-200 pl-1 dark:border-gray-700', {
67
+ return (_jsxs("div", { className: "flex gap-1", children: [_jsx("div", { className: cx('shrink-0 overflow-hidden border-r border-r-gray-200 text-right dark:border-r-gray-700', lineNumberWidth), children: _jsx(LineNo, { value: lineNumber, isCurrent: isCurrentLine, selectLine: (isShiftDown = false) => {
68
+ if (!isShiftDown) {
69
+ setLineRange(lineNumber, lineNumber);
70
+ }
71
+ if (isShiftDown && startLine != null) {
72
+ if (startLine > lineNumber) {
73
+ setLineRange(lineNumber, startLine);
74
+ }
75
+ else {
76
+ setLineRange(startLine, lineNumber);
77
+ }
78
+ }
79
+ } }) }), _jsx(LineProfileMetadata, { value: cumulative?.get(i) ?? 0n, total: total, filtered: filtered }), _jsx(LineProfileMetadata, { value: flat?.get(i) ?? 0n, total: total, filtered: filtered }), _jsx("div", { className: cx('w-full flex-grow-0 border-l border-gray-200 pl-1 dark:border-gray-700', {
67
80
  'bg-yellow-200 dark:bg-yellow-700': isCurrentLine,
68
- }), children: createElement({
81
+ }), onContextMenu: onContextMenu, children: createElement({
69
82
  key: `source-line-${i}`,
70
83
  node,
71
84
  stylesheet,
@@ -1,7 +1,7 @@
1
1
  interface Props {
2
2
  value: number;
3
3
  isCurrent?: boolean;
4
- setCurrentLine?: () => void;
4
+ selectLine?: (isShiftDown?: boolean) => void;
5
5
  }
6
- export declare const LineNo: ({ value, isCurrent, setCurrentLine }: Props) => JSX.Element;
6
+ export declare const LineNo: ({ value, isCurrent, selectLine }: Props) => JSX.Element;
7
7
  export {};
@@ -13,7 +13,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
13
13
  // limitations under the License.
14
14
  import { useEffect, useRef } from 'react';
15
15
  import cx from 'classnames';
16
- export const LineNo = ({ value, isCurrent = false, setCurrentLine }) => {
16
+ export const LineNo = ({ value, isCurrent = false, selectLine }) => {
17
17
  const ref = useRef(null);
18
18
  useEffect(() => {
19
19
  if (isCurrent && ref.current !== null) {
@@ -26,7 +26,7 @@ export const LineNo = ({ value, isCurrent = false, setCurrentLine }) => {
26
26
  ref.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
27
27
  }
28
28
  }, [isCurrent]);
29
- return (_jsx("code", { ref: ref, onClick: setCurrentLine, className: cx('cursor-pointer px-1', {
29
+ return (_jsx("code", { ref: ref, onClick: e => typeof selectLine === 'function' && selectLine(e.shiftKey), className: cx('cursor-pointer px-1 select-none', {
30
30
  'border-l border-l-amber-900 bg-yellow-200 dark:bg-yellow-700': isCurrent,
31
31
  }), children: value.toString() + '\n' }));
32
32
  };
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // Copyright 2022 The Parca Authors
3
3
  // Licensed under the Apache License, Version 2.0 (the "License");
4
4
  // you may not use this file except in compliance with the License.
@@ -11,16 +11,42 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
11
11
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
- import React, { useEffect } from 'react';
14
+ import React, { useEffect, useMemo } from 'react';
15
15
  import { tableFromIPC } from 'apache-arrow';
16
16
  import { AnimatePresence, motion } from 'framer-motion';
17
+ import { Item, Menu, useContextMenu } from 'react-contexify';
17
18
  import { SourceSkeleton, useParcaContext, useURLState } from '@parca/components';
18
19
  import { ExpandOnHover } from '../GraphTooltipArrow/ExpandOnHoverValue';
19
20
  import { truncateStringReverse } from '../utils';
20
21
  import { Highlighter, profileAwareRenderer } from './Highlighter';
22
+ import useLineRange from './useSelectedLineRange';
23
+ const MENU_ID = 'source-view-context-menu';
21
24
  export const SourceView = React.memo(function SourceView({ data, loading, total, filtered, setActionButtons, }) {
22
25
  const [sourceFileName] = useURLState({ param: 'source_filename', navigateTo: () => { } });
23
- const { isDarkMode } = useParcaContext();
26
+ const { isDarkMode, sourceViewContextMenuItems = [] } = useParcaContext();
27
+ const sourceCode = useMemo(() => {
28
+ if (data === undefined) {
29
+ return [''];
30
+ }
31
+ return data.source.split('\n');
32
+ }, [data]);
33
+ const { show } = useContextMenu({
34
+ id: MENU_ID,
35
+ });
36
+ const { startLine, endLine } = useLineRange();
37
+ const selectedCode = useMemo(() => {
38
+ if (startLine === -1 && endLine === -1) {
39
+ return '';
40
+ }
41
+ if (startLine === endLine) {
42
+ return sourceCode[startLine - 1];
43
+ }
44
+ let code = '';
45
+ for (let i = startLine - 1; i < endLine; i++) {
46
+ code += sourceCode[i] + '\n';
47
+ }
48
+ return code;
49
+ }, [startLine, endLine, sourceCode]);
24
50
  useEffect(() => {
25
51
  setActionButtons?.(_jsx("div", { className: "px-2", children: _jsx(ExpandOnHover, { value: sourceFileName, displayValue: truncateStringReverse(sourceFileName, 50) }) }));
26
52
  }, [sourceFileName, setActionButtons]);
@@ -30,9 +56,15 @@ export const SourceView = React.memo(function SourceView({ data, loading, total,
30
56
  if (data === undefined) {
31
57
  return _jsx(_Fragment, { children: "Source code not uploaded for this build." });
32
58
  }
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ const onContextMenu = (event) => {
61
+ show({
62
+ event,
63
+ });
64
+ };
33
65
  const table = tableFromIPC(data.record);
34
66
  const cumulative = table.getChild('cumulative');
35
67
  const flat = table.getChild('flat');
36
- return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "h-full w-full", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: _jsx(Highlighter, { file: sourceFileName, content: data.source, renderer: profileAwareRenderer(cumulative, flat, total, filtered) }) }, "source-view-loaded") }));
68
+ return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "h-full w-full", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: [_jsx(Highlighter, { file: sourceFileName, content: data.source, renderer: profileAwareRenderer(cumulative, flat, total, filtered, onContextMenu) }), _jsx(Menu, { id: MENU_ID, children: sourceViewContextMenuItems.map(item => (_jsx(Item, { onClick: () => item.action(selectedCode), children: item.label }, item.id))) })] }, "source-view-loaded") }));
37
69
  });
38
70
  export default SourceView;
@@ -0,0 +1,7 @@
1
+ interface LineRange {
2
+ startLine: number;
3
+ endLine: number;
4
+ setLineRange: (start: number, end: number) => void;
5
+ }
6
+ declare const useLineRange: () => LineRange;
7
+ export default useLineRange;
@@ -0,0 +1,29 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ import { useMemo } from 'react';
14
+ import { useURLState } from '@parca/components';
15
+ const useLineRange = () => {
16
+ const [sourceLine, setSourceLine] = useURLState({ param: 'source_line', navigateTo: () => { } });
17
+ const [startLine, endLine] = useMemo(() => {
18
+ if (sourceLine == null) {
19
+ return [-1, -1];
20
+ }
21
+ const [start, end] = sourceLine.split('-');
22
+ return [parseInt(start, 10), parseInt(end, 10)];
23
+ }, [sourceLine]);
24
+ const setLineRange = (start, end) => {
25
+ setSourceLine(`${start}-${end}`);
26
+ };
27
+ return { startLine, endLine, setLineRange };
28
+ };
29
+ export default useLineRange;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.347",
3
+ "version": "0.16.348",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@parca/client": "^0.16.105",
7
- "@parca/components": "^0.16.256",
7
+ "@parca/components": "^0.16.257",
8
8
  "@parca/dynamicsize": "^0.16.60",
9
9
  "@parca/hooks": "^0.0.43",
10
10
  "@parca/parser": "^0.16.68",
@@ -50,5 +50,5 @@
50
50
  "access": "public",
51
51
  "registry": "https://registry.npmjs.org/"
52
52
  },
53
- "gitHead": "441def38d4a760355493c72ad65470ba37955b9a"
53
+ "gitHead": "c6fee2eafe92d9e2b9733ddfed32f93964338f6f"
54
54
  }
@@ -11,7 +11,7 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useId, useMemo} from 'react';
14
+ import {MouseEventHandler, useId, useMemo} from 'react';
15
15
 
16
16
  import {Vector} from 'apache-arrow';
17
17
  import cx from 'classnames';
@@ -20,11 +20,12 @@ import SyntaxHighlighter, {createElement, type createElementProps} from 'react-s
20
20
  import {atomOneDark, atomOneLight} from 'react-syntax-highlighter/dist/esm/styles/hljs';
21
21
  import {Tooltip} from 'react-tooltip';
22
22
 
23
- import {useParcaContext, useURLState} from '@parca/components';
23
+ import {useParcaContext} from '@parca/components';
24
24
 
25
25
  import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
26
26
  import {LineNo} from './LineNo';
27
27
  import {langaugeFromFile} from './lang-detector';
28
+ import useLineRange from './useSelectedLineRange';
28
29
 
29
30
  interface RendererProps {
30
31
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -110,7 +111,8 @@ export const profileAwareRenderer = (
110
111
  cumulative: Vector | null,
111
112
  flat: Vector | null,
112
113
  total: bigint,
113
- filtered: bigint
114
+ filtered: bigint,
115
+ onContextMenu: MouseEventHandler<HTMLDivElement>
114
116
  ): Renderer => {
115
117
  return function ProfileAwareRenderer({
116
118
  rows,
@@ -118,12 +120,13 @@ export const profileAwareRenderer = (
118
120
  useInlineStyles,
119
121
  }: RendererProps): JSX.Element {
120
122
  const lineNumberWidth = charsToWidth(rows.length.toString().length);
121
- const [sourceLine, setSourceLine] = useURLState({param: 'source_line', navigateTo: () => {}});
123
+ const {startLine, endLine, setLineRange} = useLineRange();
124
+
122
125
  return (
123
126
  <>
124
127
  {rows.map((node, i) => {
125
128
  const lineNumber: number = node.children[0].children[0].value as number;
126
- const isCurrentLine = sourceLine === lineNumber.toString();
129
+ const isCurrentLine = lineNumber >= startLine && lineNumber <= endLine;
127
130
  node.children = node.children.slice(1);
128
131
  return (
129
132
  <div className="flex gap-1" key={`${i}`}>
@@ -136,7 +139,18 @@ export const profileAwareRenderer = (
136
139
  <LineNo
137
140
  value={lineNumber}
138
141
  isCurrent={isCurrentLine}
139
- setCurrentLine={() => setSourceLine(lineNumber.toString())}
142
+ selectLine={(isShiftDown = false) => {
143
+ if (!isShiftDown) {
144
+ setLineRange(lineNumber, lineNumber);
145
+ }
146
+ if (isShiftDown && startLine != null) {
147
+ if (startLine > lineNumber) {
148
+ setLineRange(lineNumber, startLine);
149
+ } else {
150
+ setLineRange(startLine, lineNumber);
151
+ }
152
+ }
153
+ }}
140
154
  />
141
155
  </div>
142
156
  <LineProfileMetadata
@@ -152,6 +166,7 @@ export const profileAwareRenderer = (
152
166
  'bg-yellow-200 dark:bg-yellow-700': isCurrentLine,
153
167
  }
154
168
  )}
169
+ onContextMenu={onContextMenu}
155
170
  >
156
171
  {createElement({
157
172
  key: `source-line-${i}`,
@@ -18,10 +18,10 @@ import cx from 'classnames';
18
18
  interface Props {
19
19
  value: number;
20
20
  isCurrent?: boolean;
21
- setCurrentLine?: () => void;
21
+ selectLine?: (isShiftDown?: boolean) => void;
22
22
  }
23
23
 
24
- export const LineNo = ({value, isCurrent = false, setCurrentLine}: Props): JSX.Element => {
24
+ export const LineNo = ({value, isCurrent = false, selectLine}: Props): JSX.Element => {
25
25
  const ref = useRef<HTMLDivElement>(null);
26
26
 
27
27
  useEffect(() => {
@@ -41,8 +41,8 @@ export const LineNo = ({value, isCurrent = false, setCurrentLine}: Props): JSX.E
41
41
  return (
42
42
  <code
43
43
  ref={ref}
44
- onClick={setCurrentLine}
45
- className={cx('cursor-pointer px-1', {
44
+ onClick={e => typeof selectLine === 'function' && selectLine(e.shiftKey)}
45
+ className={cx('cursor-pointer px-1 select-none', {
46
46
  'border-l border-l-amber-900 bg-yellow-200 dark:bg-yellow-700': isCurrent,
47
47
  })}
48
48
  >
@@ -11,10 +11,11 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {useEffect} from 'react';
14
+ import React, {useEffect, useMemo} from 'react';
15
15
 
16
16
  import {tableFromIPC} from 'apache-arrow';
17
17
  import {AnimatePresence, motion} from 'framer-motion';
18
+ import {Item, Menu, useContextMenu} from 'react-contexify';
18
19
 
19
20
  import {Source} from '@parca/client';
20
21
  import {SourceSkeleton, useParcaContext, useURLState} from '@parca/components';
@@ -22,6 +23,7 @@ import {SourceSkeleton, useParcaContext, useURLState} from '@parca/components';
22
23
  import {ExpandOnHover} from '../GraphTooltipArrow/ExpandOnHoverValue';
23
24
  import {truncateStringReverse} from '../utils';
24
25
  import {Highlighter, profileAwareRenderer} from './Highlighter';
26
+ import useLineRange from './useSelectedLineRange';
25
27
 
26
28
  interface SourceViewProps {
27
29
  loading: boolean;
@@ -31,6 +33,8 @@ interface SourceViewProps {
31
33
  setActionButtons?: (buttons: JSX.Element) => void;
32
34
  }
33
35
 
36
+ const MENU_ID = 'source-view-context-menu';
37
+
34
38
  export const SourceView = React.memo(function SourceView({
35
39
  data,
36
40
  loading,
@@ -39,7 +43,34 @@ export const SourceView = React.memo(function SourceView({
39
43
  setActionButtons,
40
44
  }: SourceViewProps): JSX.Element {
41
45
  const [sourceFileName] = useURLState({param: 'source_filename', navigateTo: () => {}});
42
- const {isDarkMode} = useParcaContext();
46
+ const {isDarkMode, sourceViewContextMenuItems = []} = useParcaContext();
47
+
48
+ const sourceCode = useMemo(() => {
49
+ if (data === undefined) {
50
+ return [''];
51
+ }
52
+ return data.source.split('\n');
53
+ }, [data]);
54
+
55
+ const {show} = useContextMenu({
56
+ id: MENU_ID,
57
+ });
58
+
59
+ const {startLine, endLine} = useLineRange();
60
+
61
+ const selectedCode = useMemo(() => {
62
+ if (startLine === -1 && endLine === -1) {
63
+ return '';
64
+ }
65
+ if (startLine === endLine) {
66
+ return sourceCode[startLine - 1];
67
+ }
68
+ let code = '';
69
+ for (let i = startLine - 1; i < endLine; i++) {
70
+ code += sourceCode[i] + '\n';
71
+ }
72
+ return code;
73
+ }, [startLine, endLine, sourceCode]);
43
74
 
44
75
  useEffect(() => {
45
76
  setActionButtons?.(
@@ -64,6 +95,13 @@ export const SourceView = React.memo(function SourceView({
64
95
  return <>Source code not uploaded for this build.</>;
65
96
  }
66
97
 
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ const onContextMenu = (event: any): void => {
100
+ show({
101
+ event,
102
+ });
103
+ };
104
+
67
105
  const table = tableFromIPC(data.record);
68
106
  const cumulative = table.getChild('cumulative');
69
107
  const flat = table.getChild('flat');
@@ -80,8 +118,15 @@ export const SourceView = React.memo(function SourceView({
80
118
  <Highlighter
81
119
  file={sourceFileName as string}
82
120
  content={data.source}
83
- renderer={profileAwareRenderer(cumulative, flat, total, filtered)}
121
+ renderer={profileAwareRenderer(cumulative, flat, total, filtered, onContextMenu)}
84
122
  />
123
+ <Menu id={MENU_ID}>
124
+ {sourceViewContextMenuItems.map(item => (
125
+ <Item key={item.id} onClick={() => item.action(selectedCode)}>
126
+ {item.label}
127
+ </Item>
128
+ ))}
129
+ </Menu>
85
130
  </motion.div>
86
131
  </AnimatePresence>
87
132
  );
@@ -0,0 +1,41 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {useMemo} from 'react';
15
+
16
+ import {useURLState} from '@parca/components';
17
+
18
+ interface LineRange {
19
+ startLine: number;
20
+ endLine: number;
21
+ setLineRange: (start: number, end: number) => void;
22
+ }
23
+
24
+ const useLineRange = (): LineRange => {
25
+ const [sourceLine, setSourceLine] = useURLState({param: 'source_line', navigateTo: () => {}});
26
+ const [startLine, endLine] = useMemo(() => {
27
+ if (sourceLine == null) {
28
+ return [-1, -1];
29
+ }
30
+ const [start, end] = (sourceLine as string).split('-');
31
+ return [parseInt(start, 10), parseInt(end, 10)];
32
+ }, [sourceLine]);
33
+
34
+ const setLineRange = (start: number, end: number): void => {
35
+ setSourceLine(`${start}-${end}`);
36
+ };
37
+
38
+ return {startLine, endLine, setLineRange};
39
+ };
40
+
41
+ export default useLineRange;