CodeBlock.svelte 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <script lang="ts">
  2. import { copyToClipboard } from '$lib/utils';
  3. import hljs from 'highlight.js';
  4. import 'highlight.js/styles/github-dark.min.css';
  5. import { loadPyodide } from 'pyodide';
  6. import { tick } from 'svelte';
  7. export let id = '';
  8. export let lang = '';
  9. export let code = '';
  10. let executed = false;
  11. let stdout = null;
  12. let stderr = null;
  13. let result = null;
  14. let copied = false;
  15. const copyCode = async () => {
  16. copied = true;
  17. await copyToClipboard(code);
  18. setTimeout(() => {
  19. copied = false;
  20. }, 1000);
  21. };
  22. const checkPythonCode = (str) => {
  23. // Check if the string contains typical Python keywords, syntax, or functions
  24. const pythonKeywords = [
  25. 'def',
  26. 'class',
  27. 'import',
  28. 'from',
  29. 'if',
  30. 'else',
  31. 'elif',
  32. 'for',
  33. 'while',
  34. 'try',
  35. 'except',
  36. 'finally',
  37. 'return',
  38. 'yield',
  39. 'lambda',
  40. 'assert',
  41. 'pass',
  42. 'break',
  43. 'continue',
  44. 'global',
  45. 'nonlocal',
  46. 'del',
  47. 'True',
  48. 'False',
  49. 'None',
  50. 'and',
  51. 'or',
  52. 'not',
  53. 'in',
  54. 'is',
  55. 'as',
  56. 'with'
  57. ];
  58. for (let keyword of pythonKeywords) {
  59. if (str.includes(keyword)) {
  60. return true;
  61. }
  62. }
  63. // Check if the string contains typical Python syntax characters
  64. const pythonSyntax = [
  65. 'def ',
  66. 'class ',
  67. 'import ',
  68. 'from ',
  69. 'if ',
  70. 'else:',
  71. 'elif ',
  72. 'for ',
  73. 'while ',
  74. 'try:',
  75. 'except:',
  76. 'finally:',
  77. 'return ',
  78. 'yield ',
  79. 'lambda ',
  80. 'assert ',
  81. 'pass',
  82. 'break',
  83. 'continue',
  84. 'global ',
  85. 'nonlocal ',
  86. 'del ',
  87. 'True',
  88. 'False',
  89. 'None',
  90. ' and ',
  91. ' or ',
  92. ' not ',
  93. ' in ',
  94. ' is ',
  95. ' as ',
  96. ' with ',
  97. ':',
  98. '=',
  99. '==',
  100. '!=',
  101. '>',
  102. '<',
  103. '>=',
  104. '<=',
  105. '+',
  106. '-',
  107. '*',
  108. '/',
  109. '%',
  110. '**',
  111. '//',
  112. '(',
  113. ')',
  114. '[',
  115. ']',
  116. '{',
  117. '}'
  118. ];
  119. for (let syntax of pythonSyntax) {
  120. if (str.includes(syntax)) {
  121. return true;
  122. }
  123. }
  124. // If none of the above conditions met, it's probably not Python code
  125. return false;
  126. };
  127. const executePython = async (code) => {
  128. executed = true;
  129. let pyodide = await loadPyodide({
  130. indexURL: '/pyodide/',
  131. stderr: (text) => {
  132. console.log('An error occured:', text);
  133. if (stderr) {
  134. stderr += `${text}\n`;
  135. } else {
  136. stderr = `${text}\n`;
  137. }
  138. },
  139. stdout: (text) => {
  140. console.log('Python output:', text);
  141. if (stdout) {
  142. stdout += `${text}\n`;
  143. } else {
  144. stdout = `${text}\n`;
  145. }
  146. }
  147. });
  148. result = pyodide.runPython(code);
  149. console.log(result);
  150. console.log(stderr);
  151. console.log(stdout);
  152. };
  153. $: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
  154. </script>
  155. {#if code}
  156. <div class="mb-4">
  157. <div
  158. class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
  159. >
  160. <div class="p-1">{@html lang}</div>
  161. <div class="flex items-center">
  162. {#if lang === 'python' || checkPythonCode(code)}
  163. <button
  164. class="copy-code-button bg-none border-none p-1"
  165. on:click={() => {
  166. executePython(code);
  167. }}>Run</button
  168. >
  169. {/if}
  170. <button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
  171. >{copied ? 'Copied' : 'Copy Code'}</button
  172. >
  173. </div>
  174. </div>
  175. <pre
  176. class=" hljs p-4 px-5 overflow-x-auto"
  177. style="border-top-left-radius: 0px; border-top-right-radius: 0px; {executed &&
  178. 'border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;'}"><code
  179. class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
  180. ></pre>
  181. {#if executed}
  182. <div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
  183. <div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
  184. <div class="text-sm">
  185. {#if stdout}
  186. {stdout}
  187. {:else if result}
  188. {result}
  189. {:else if stderr}
  190. {stderr}
  191. {:else}
  192. Running...
  193. {/if}
  194. </div>
  195. </div>
  196. {/if}
  197. </div>
  198. {/if}