rn-shiki 0.0.35 → 0.0.37-0

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rn-shiki",
3
3
  "type": "module",
4
- "version": "0.0.35",
4
+ "version": "0.0.37-0",
5
5
  "description": "Shiki syntax highlighter for React Native.",
6
6
  "author": "Ryan Skinner <hello@ryanskinner.com>",
7
7
  "license": "MIT",
@@ -1,6 +1,7 @@
1
1
  import type { SyntaxHighlighterProps } from 'src/types/shiki'
2
2
  import React from 'react'
3
- import { Platform, Text, View } from 'react-native'
3
+ import { Platform, ScrollView, StyleSheet, Text, View } from 'react-native'
4
+ import { convertShikiTheme } from 'src/utils/style-transformer'
4
5
  import { useSyntaxHighlighter } from '../../hooks/useSyntaxHighlighter'
5
6
  import SyntaxLine from './SyntaxLine'
6
7
 
@@ -10,7 +11,31 @@ const monospaceFont = Platform.select({
10
11
  default: 'monospace',
11
12
  })
12
13
 
13
- function SyntaxHighlighter({ text, language, languageId, theme, fontSize = 14 }: SyntaxHighlighterProps & { fontSize?: number }) {
14
+ const styles = StyleSheet.create({
15
+ container: {
16
+ flex: 1,
17
+ minHeight: 20,
18
+ },
19
+ scrollContent: {
20
+ flexGrow: 1,
21
+ padding: 8,
22
+ },
23
+ contentContainer: {
24
+ minWidth: '100%',
25
+ },
26
+ errorText: {
27
+ padding: 8,
28
+ color: '#ff6b6b',
29
+ },
30
+ loadingText: {
31
+ padding: 8,
32
+ color: '#666666',
33
+ },
34
+ })
35
+
36
+ function SyntaxHighlighter({ text, language, languageId, theme, fontSize = 14 }: SyntaxHighlighterProps) {
37
+ // Convert theme on mount
38
+ const themeStyles = React.useMemo(() => convertShikiTheme(theme ?? {}), [theme])
14
39
  const { tokens, error, isLoading } = useSyntaxHighlighter({
15
40
  text,
16
41
  language,
@@ -21,12 +46,13 @@ function SyntaxHighlighter({ text, language, languageId, theme, fontSize = 14 }:
21
46
  if (error) {
22
47
  return (
23
48
  <Text
24
- style={{
25
- color: '#ff6b6b',
26
- fontFamily: monospaceFont,
27
- fontSize,
28
- padding: 4,
29
- }}
49
+ style={[
50
+ styles.errorText,
51
+ {
52
+ fontFamily: monospaceFont,
53
+ fontSize,
54
+ },
55
+ ]}
30
56
  >
31
57
  Error:
32
58
  {' '}
@@ -38,12 +64,13 @@ function SyntaxHighlighter({ text, language, languageId, theme, fontSize = 14 }:
38
64
  if (isLoading) {
39
65
  return (
40
66
  <Text
41
- style={{
42
- color: '#666666',
43
- fontFamily: monospaceFont,
44
- fontSize,
45
- padding: 4,
46
- }}
67
+ style={[
68
+ styles.loadingText,
69
+ {
70
+ fontFamily: monospaceFont,
71
+ fontSize,
72
+ },
73
+ ]}
47
74
  >
48
75
  Loading...
49
76
  </Text>
@@ -51,18 +78,14 @@ function SyntaxHighlighter({ text, language, languageId, theme, fontSize = 14 }:
51
78
  }
52
79
 
53
80
  return (
54
- <View style={{ flex: 1 }}>
55
- {tokens.map((line, lineIndex) => (
56
- <View
57
- key={`line-${lineIndex}`}
58
- style={{
59
- minHeight: fontSize * 1.5, // Ensure consistent line height
60
- paddingVertical: 2,
61
- }}
62
- >
63
- <SyntaxLine line={line} fontSize={fontSize} />
81
+ <View style={[styles.container, { backgroundColor: themeStyles.backgroundColor }]}>
82
+ <ScrollView horizontal showsHorizontalScrollIndicator={Platform.OS !== 'web'} contentContainerStyle={{ flexGrow: 1 }}>
83
+ <View style={styles.contentContainer}>
84
+ {tokens.map((line, lineIndex) => (
85
+ <SyntaxLine key={`line-${lineIndex}`} line={line} fontSize={fontSize} isLastLine={lineIndex === tokens.length - 1} themeStyles={themeStyles} />
86
+ ))}
64
87
  </View>
65
- ))}
88
+ </ScrollView>
66
89
  </View>
67
90
  )
68
91
  }
@@ -1,6 +1,7 @@
1
+ import type { RNTokenStyle } from 'src/utils/style-transformer'
1
2
  import type { TokenType } from '../../types'
2
3
  import React from 'react'
3
- import { Platform, Text, View } from 'react-native'
4
+ import { Platform, StyleSheet, Text, View } from 'react-native'
4
5
 
5
6
  const monospaceFont = Platform.select({
6
7
  ios: 'Menlo',
@@ -8,56 +9,71 @@ const monospaceFont = Platform.select({
8
9
  default: 'monospace',
9
10
  })
10
11
 
12
+ const styles = StyleSheet.create({
13
+ lineContainer: {
14
+ flexDirection: 'row',
15
+ flexWrap: 'wrap',
16
+ },
17
+ token: {
18
+ includeFontPadding: false,
19
+ },
20
+ })
21
+
11
22
  interface SyntaxLineProps {
12
23
  line: TokenType[]
13
24
  fontSize?: number
25
+ isLastLine?: boolean
26
+ themeStyles: {
27
+ backgroundColor: string
28
+ defaultColor: string
29
+ styles: Record<string, RNTokenStyle>
30
+ }
14
31
  }
15
32
 
16
- function SyntaxLine({ line, fontSize = 14 }: SyntaxLineProps) {
33
+ function SyntaxLine({ line, fontSize = 14, isLastLine = false, themeStyles }: SyntaxLineProps) {
34
+ const lineHeight = Math.floor(fontSize * 1.5)
35
+
17
36
  return (
18
37
  <View
19
- style={{
20
- flexDirection: 'row',
21
- flexWrap: 'wrap',
22
- minHeight: fontSize * 1.5, // Match parent container
23
- }}
38
+ style={[
39
+ styles.lineContainer,
40
+ {
41
+ minHeight: lineHeight,
42
+ paddingBottom: !isLastLine ? 4 : 0,
43
+ },
44
+ ]}
24
45
  >
25
46
  {line.map((token, tokenIndex) => {
26
47
  const content = token.content.replace(/ /g, '\u00A0')
48
+ // Use converted theme styles
49
+ const tokenStyle = token.color ? { color: token.color } : { color: themeStyles.defaultColor }
50
+
27
51
  return (
28
52
  <Text
29
53
  key={`${tokenIndex}-${content.slice(0, 8)}`}
30
- style={{
31
- color: token.color || '#FFFFFF',
32
- fontFamily: monospaceFont,
33
- fontSize,
34
- fontStyle: token.fontStyle as 'normal' | 'italic',
35
- fontWeight: token.fontWeight as 'normal' | 'bold',
36
- includeFontPadding: false,
37
- textAlignVertical: 'center',
38
- lineHeight: fontSize * 1.5,
39
- ...(Platform.OS === 'ios'
40
- ? {
41
- paddingTop: 2, // Fine-tune iOS vertical alignment
42
- }
43
- : {}),
44
- }}
54
+ style={[
55
+ styles.token,
56
+ {
57
+ ...tokenStyle,
58
+ fontFamily: monospaceFont,
59
+ fontSize,
60
+ fontStyle: token.fontStyle as 'normal' | 'italic',
61
+ fontWeight: token.fontWeight as 'normal' | 'bold',
62
+ lineHeight,
63
+ height: lineHeight,
64
+ textAlignVertical: 'center',
65
+ ...(Platform.OS === 'ios'
66
+ ? {
67
+ paddingTop: 2,
68
+ }
69
+ : {}),
70
+ },
71
+ ]}
45
72
  >
46
73
  {content}
47
74
  </Text>
48
75
  )
49
76
  })}
50
- <Text
51
- style={{
52
- fontFamily: monospaceFont,
53
- fontSize,
54
- color: 'transparent',
55
- lineHeight: fontSize * 1.5,
56
- height: fontSize * 1.5,
57
- }}
58
- >
59
- {'\n'}
60
- </Text>
61
77
  </View>
62
78
  )
63
79
  }
@@ -0,0 +1,71 @@
1
+ import type { TextStyle } from 'react-native'
2
+ import type { ThemeRegistrationAny } from 'shiki'
3
+
4
+ export interface RNTokenStyle {
5
+ color?: string
6
+ backgroundColor?: string
7
+ fontWeight?: TextStyle['fontWeight']
8
+ fontStyle?: TextStyle['fontStyle']
9
+ }
10
+
11
+ const ALLOWED_STYLE_PROPERTIES = {
12
+ 'color': true,
13
+ 'background': true,
14
+ 'background-color': true,
15
+ 'font-weight': true,
16
+ 'font-style': true,
17
+ } as const
18
+
19
+ function transformValue(key: string, value: string): any {
20
+ switch (key) {
21
+ case 'background':
22
+ case 'background-color':
23
+ return ['backgroundColor', value]
24
+ case 'font-weight':
25
+ return ['fontWeight', value === 'bold' ? 'bold' : 'normal']
26
+ case 'font-style':
27
+ return ['fontStyle', value === 'italic' ? 'italic' : 'normal']
28
+ default:
29
+ return [key, value]
30
+ }
31
+ }
32
+
33
+ export function convertShikiTheme(theme: ThemeRegistrationAny): {
34
+ backgroundColor: string
35
+ defaultColor: string
36
+ styles: Record<string, RNTokenStyle>
37
+ } {
38
+ // Extract theme colors
39
+ const backgroundColor = theme.bg || '#1e1e1e'
40
+ const defaultColor = theme.fg || '#d4d4d4'
41
+
42
+ // Convert token colors
43
+ const styles: Record<string, RNTokenStyle> = {}
44
+
45
+ theme.settings?.forEach((setting) => {
46
+ if (!setting.scope || !setting.settings)
47
+ return
48
+
49
+ const scopes = Array.isArray(setting.scope) ? setting.scope : [setting.scope]
50
+ const style: RNTokenStyle = {}
51
+
52
+ // Convert CSS-style properties to RN style properties
53
+ Object.entries(setting.settings).forEach(([key, value]) => {
54
+ if (ALLOWED_STYLE_PROPERTIES[key as keyof typeof ALLOWED_STYLE_PROPERTIES]) {
55
+ const [rnKey, rnValue] = transformValue(key, value as string)
56
+ style[rnKey as keyof RNTokenStyle] = rnValue
57
+ }
58
+ })
59
+
60
+ // Apply style to each scope
61
+ scopes.forEach((scope) => {
62
+ styles[scope] = style
63
+ })
64
+ })
65
+
66
+ return {
67
+ backgroundColor,
68
+ defaultColor,
69
+ styles,
70
+ }
71
+ }