rn-shiki 0.0.37-1 → 0.0.37-10

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.37-1",
4
+ "version": "0.0.37-10",
5
5
  "description": "Shiki syntax highlighter for React Native.",
6
6
  "author": "Ryan Skinner <hello@ryanskinner.com>",
7
7
  "license": "MIT",
@@ -34,21 +34,17 @@
34
34
  "shiki": "^1.1.7"
35
35
  },
36
36
  "dependencies": {
37
- "esbuild": "^0.24.0",
37
+ "css-to-react-native": "^3.2.0",
38
38
  "shiki": "^1.1.7"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@antfu/eslint-config": "^3.8.0",
42
- "@babel/core": "^7.24.0",
43
- "@babel/preset-env": "^7.26.0",
44
42
  "@eslint-react/eslint-plugin": "^1.15.0",
45
43
  "@types/react": "^18.2.0",
46
44
  "@types/react-native": "^0.73.0",
47
- "babel-plugin-module-resolver": "^5.0.2",
48
45
  "eslint": "^9.13.0",
49
46
  "eslint-plugin-react-hooks": "^5.0.0",
50
47
  "eslint-plugin-react-refresh": "^0.4.13",
51
- "metro-react-native-babel-preset": "^0.77.0",
52
48
  "react": "^18.2.0",
53
49
  "react-native": "^0.73.0",
54
50
  "react-native-builder-bob": "^0.30.2",
@@ -1,9 +1,9 @@
1
1
  import type { SyntaxHighlighterProps } from 'src/types/shiki'
2
- import React from 'react'
3
- import { Platform, ScrollView, StyleSheet, Text, View } from 'react-native'
2
+ import type { ReactStyle } from '../../utils/style-transformer'
3
+ import React, { useMemo } from 'react'
4
+ import { Platform, ScrollView, StyleSheet, Text, type TextStyle, View } from 'react-native'
4
5
  import { useSyntaxHighlighter } from '../../hooks/useSyntaxHighlighter'
5
- import { convertShikiTheme } from '../../utils/style-transformer'
6
- import SyntaxLine from './SyntaxLine'
6
+ import { getRNStylesFromShikiStyle } from '../../utils/style-transformer'
7
7
 
8
8
  const monospaceFont = Platform.select({
9
9
  ios: 'Menlo',
@@ -12,81 +12,65 @@ const monospaceFont = Platform.select({
12
12
  })
13
13
 
14
14
  const styles = StyleSheet.create({
15
- container: {
15
+ scrollView: {
16
16
  flex: 1,
17
17
  minHeight: 20,
18
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',
19
+ lineContainer: {
20
+ flexDirection: 'row',
21
+ flexWrap: 'wrap',
33
22
  },
34
23
  })
35
24
 
36
- function SyntaxHighlighter({ text, language, languageId, theme, fontSize = 14 }: SyntaxHighlighterProps) {
37
- // Convert theme on mount
38
- const themeStyles = React.useMemo(() => convertShikiTheme(theme ?? {}), [theme])
39
- const { tokens, error, isLoading } = useSyntaxHighlighter({
25
+ const SyntaxHighlighter: React.FC<SyntaxHighlighterProps> = ({ text, language, languageId, theme }) => {
26
+ console.log('Received theme:', theme)
27
+
28
+ const stylesheet = useMemo(() => {
29
+ const styles = getRNStylesFromShikiStyle(theme as ReactStyle)
30
+ console.log('Generated stylesheet:', styles)
31
+ return styles
32
+ }, [theme])
33
+
34
+ const { tokens } = useSyntaxHighlighter({
40
35
  text,
41
36
  language,
42
37
  languageId,
43
38
  theme,
44
39
  })
45
40
 
46
- if (error) {
41
+ const renderToken = (token: any, index: number, lineIndex: number) => {
42
+ const tokenStyles: TextStyle = StyleSheet.flatten([
43
+ stylesheet.token,
44
+ {
45
+ fontFamily: monospaceFont,
46
+ ...(token.color && { color: token.color }),
47
+ ...(token.fontStyle === 'italic' && stylesheet.italic),
48
+ ...(token.fontWeight === 'bold' && stylesheet.bold),
49
+ },
50
+ ])
51
+
52
+ console.log(`Rendering token: ${token.content}, styles:`, tokenStyles)
53
+
47
54
  return (
48
- <Text
49
- style={[
50
- styles.errorText,
51
- {
52
- fontFamily: monospaceFont,
53
- fontSize,
54
- },
55
- ]}
56
- >
57
- Error:
58
- {' '}
59
- {error}
55
+ <Text key={`${lineIndex}-${index}`} style={tokenStyles}>
56
+ {token.content.replace(/ /g, '\u00A0')}
60
57
  </Text>
61
58
  )
62
59
  }
63
60
 
64
- if (isLoading) {
61
+ const renderLine = (line: any[], lineIndex: number) => {
62
+ console.log(`Rendering line ${lineIndex}:`, line)
65
63
  return (
66
- <Text
67
- style={[
68
- styles.loadingText,
69
- {
70
- fontFamily: monospaceFont,
71
- fontSize,
72
- },
73
- ]}
74
- >
75
- Loading...
76
- </Text>
64
+ <View key={lineIndex} style={styles.lineContainer}>
65
+ {line.map((token, tokenIndex) => renderToken(token, tokenIndex, lineIndex))}
66
+ </View>
77
67
  )
78
68
  }
79
69
 
80
70
  return (
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
- ))}
87
- </View>
88
- </ScrollView>
89
- </View>
71
+ <ScrollView horizontal showsHorizontalScrollIndicator={Platform.OS !== 'web'} style={styles.scrollView}>
72
+ <View onStartShouldSetResponder={() => true}>{tokens.map((line, index) => renderLine(line, index))}</View>
73
+ </ScrollView>
90
74
  )
91
75
  }
92
76
 
@@ -1,6 +1,7 @@
1
- import type { SyntaxHighlighterProps, ThemedToken, TokensResult } from '../types/shiki'
1
+ import type { ThemedToken } from 'shiki'
2
+ import type { SyntaxHighlighterProps, TokensResult } from '../types/shiki'
2
3
  import { useEffect, useState } from 'react'
3
- import { createHighlighter } from '../syntax'
4
+ import { createHighlighter } from '../syntax/highlighter'
4
5
 
5
6
  export function useSyntaxHighlighter({ text, language, languageId, theme }: SyntaxHighlighterProps) {
6
7
  const [tokens, setTokens] = useState<ThemedToken[][]>([])
@@ -19,14 +20,22 @@ export function useSyntaxHighlighter({ text, language, languageId, theme }: Synt
19
20
  }
20
21
 
21
22
  try {
23
+ // First create the highlighter
22
24
  const highlighter = await createHighlighter({
23
25
  langs: [language],
24
- themes: theme ? [theme] : [],
26
+ themes: [theme],
25
27
  })
26
28
 
29
+ // Get resolved theme name
30
+ const themeResolved = highlighter.getLoadedThemes()?.[0]
31
+ if (!themeResolved) {
32
+ throw new Error('Failed to resolve theme')
33
+ }
34
+
35
+ // Use resolved theme for tokenization
27
36
  const result = highlighter.codeToTokens(text, {
28
37
  lang: languageId,
29
- theme: theme || 'none',
38
+ theme: themeResolved,
30
39
  }) as TokensResult
31
40
 
32
41
  if (mounted && result.tokens) {
package/src/index.ts CHANGED
@@ -1,6 +1,3 @@
1
1
  import SyntaxHighlighter from './components/syntax/SyntaxHighlighter'
2
- import SyntaxLine from './components/syntax/SyntaxLine'
3
2
 
4
- export * from './types/shiki'
5
-
6
- export { SyntaxHighlighter, SyntaxLine }
3
+ export { SyntaxHighlighter }
@@ -1,19 +1,31 @@
1
1
  import type { LanguageInput, ThemedToken, ThemeInput } from 'shiki'
2
- import type { TokenType } from '../../types'
2
+ import type { TokenType } from '../types/shiki'
3
3
  import { createHighlighterCore } from 'shiki/dist/core.mjs'
4
4
  import { createJavaScriptRegexEngine } from 'shiki/dist/engine-javascript.mjs'
5
5
 
6
- async function initializeHighlighter(langs: LanguageInput[], themes: ThemeInput[]): Promise<Awaited<ReturnType<typeof createHighlighterCore>>> {
7
- return createHighlighterCore({
8
- engine: createJavaScriptRegexEngine(),
9
- langs,
10
- themes,
11
- })
6
+ const engine = createJavaScriptRegexEngine({
7
+ forgiving: true,
8
+ simulation: true,
9
+ cache: new Map(),
10
+ })
11
+
12
+ async function initializeHighlighter(langs: LanguageInput[], themes: ThemeInput[]) {
13
+ try {
14
+ return await createHighlighterCore({
15
+ langs,
16
+ themes,
17
+ engine,
18
+ })
19
+ }
20
+ catch (err) {
21
+ console.error('Error initializing highlighter:', err)
22
+ throw err
23
+ }
12
24
  }
13
25
 
14
- export async function createHighlighter({ langs, themes }: { langs: LanguageInput[], themes: ThemeInput[] }): Promise<Awaited<ReturnType<typeof createHighlighterCore>>> {
15
- if (!langs?.length || !themes?.length) {
16
- throw new Error('Please provide both `langs` and `themes` when creating a highlighter.')
26
+ export async function createHighlighter({ langs, themes }: { langs: LanguageInput[], themes: ThemeInput[] }) {
27
+ if (!langs?.length) {
28
+ throw new Error('Please provide languages when creating a highlighter.')
17
29
  }
18
30
 
19
31
  try {
@@ -1,20 +1,19 @@
1
- import type { LanguageInput, ThemeRegistrationAny } from 'shiki'
1
+ import type { LanguageInput, ThemedToken, ThemeInput } from 'shiki'
2
2
 
3
- export interface ThemedToken {
3
+ export interface TokenType {
4
4
  content: string
5
5
  color?: string
6
6
  fontStyle?: string
7
- fontWeight?: string
8
- }
9
-
10
- export interface TokensResult {
11
- tokens: ThemedToken[][]
12
7
  }
13
8
 
14
9
  export interface SyntaxHighlighterProps {
15
10
  text: string
16
11
  language: LanguageInput
17
12
  languageId: string
18
- theme?: ThemeRegistrationAny
13
+ theme: ThemeInput
19
14
  fontSize?: number
20
15
  }
16
+
17
+ export interface TokensResult {
18
+ tokens: ThemedToken[][]
19
+ }
@@ -1,71 +1,30 @@
1
+ import type { CSSProperties } from 'react'
1
2
  import type { TextStyle } from 'react-native'
2
- import type { ThemeRegistrationAny } from 'shiki'
3
+ import transform, { type StyleTuple } from 'css-to-react-native'
3
4
 
4
- export interface RNTokenStyle {
5
- color?: string
6
- backgroundColor?: string
7
- fontWeight?: TextStyle['fontWeight']
8
- fontStyle?: TextStyle['fontStyle']
5
+ export interface HighlighterStyleSheet {
6
+ [key: string]: TextStyle
9
7
  }
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
- }
8
+ export type ReactStyle = Record<string, CSSProperties>
9
+
10
+ const ALLOWED_STYLE_PROPERTIES: Record<string, boolean> = {
11
+ color: true,
12
+ background: true,
13
+ backgroundColor: true,
14
+ fontWeight: true,
15
+ fontStyle: true,
31
16
  }
32
17
 
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 = {}
18
+ export function getRNStylesFromShikiStyle(shikiStyle: ReactStyle): HighlighterStyleSheet {
19
+ return Object.fromEntries(Object.entries(shikiStyle).map(([className, style]) => [className, cleanStyle(style)]))
20
+ }
51
21
 
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
- })
22
+ export function cleanStyle(style: CSSProperties) {
23
+ const styles = Object.entries(style)
24
+ .filter(([key]) => ALLOWED_STYLE_PROPERTIES[key])
25
+ .map<StyleTuple>(([key, value]) => [key, value])
59
26
 
60
- // Apply style to each scope
61
- scopes.forEach((scope) => {
62
- styles[scope] = style
63
- })
64
- })
27
+ console.log('Filtered styles:', styles) // Add this line
65
28
 
66
- return {
67
- backgroundColor,
68
- defaultColor,
69
- styles,
70
- }
29
+ return transform(styles)
71
30
  }
@@ -1,81 +0,0 @@
1
- import type { RNTokenStyle } from 'src/utils/style-transformer'
2
- import type { TokenType } from '../../types'
3
- import React from 'react'
4
- import { Platform, StyleSheet, Text, View } from 'react-native'
5
-
6
- const monospaceFont = Platform.select({
7
- ios: 'Menlo',
8
- android: 'monospace',
9
- default: 'monospace',
10
- })
11
-
12
- const styles = StyleSheet.create({
13
- lineContainer: {
14
- flexDirection: 'row',
15
- flexWrap: 'wrap',
16
- },
17
- token: {
18
- includeFontPadding: false,
19
- },
20
- })
21
-
22
- interface SyntaxLineProps {
23
- line: TokenType[]
24
- fontSize?: number
25
- isLastLine?: boolean
26
- themeStyles: {
27
- backgroundColor: string
28
- defaultColor: string
29
- styles: Record<string, RNTokenStyle>
30
- }
31
- }
32
-
33
- function SyntaxLine({ line, fontSize = 14, isLastLine = false, themeStyles }: SyntaxLineProps) {
34
- const lineHeight = Math.floor(fontSize * 1.5)
35
-
36
- return (
37
- <View
38
- style={[
39
- styles.lineContainer,
40
- {
41
- minHeight: lineHeight,
42
- paddingBottom: !isLastLine ? 4 : 0,
43
- },
44
- ]}
45
- >
46
- {line.map((token, tokenIndex) => {
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
-
51
- return (
52
- <Text
53
- key={`${tokenIndex}-${content.slice(0, 8)}`}
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
- ]}
72
- >
73
- {content}
74
- </Text>
75
- )
76
- })}
77
- </View>
78
- )
79
- }
80
-
81
- export default SyntaxLine
@@ -1,2 +0,0 @@
1
- export { default as SyntaxHighlighter } from './SyntaxHighlighter'
2
- export { default as SyntaxLine } from './SyntaxLine'
@@ -1 +0,0 @@
1
- export { createHighlighter, processTokens } from './highlighter'
@@ -1,6 +0,0 @@
1
- export interface TokenType {
2
- content: string
3
- color?: string
4
- fontStyle?: string
5
- fontWeight?: string
6
- }
@@ -1 +0,0 @@
1
- export * from './string'