CodeBlock.svelte 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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 { tick } from 'svelte';
  6. export let id = '';
  7. export let lang = '';
  8. export let code = '';
  9. let executed = false;
  10. let copied = false;
  11. const copyCode = async () => {
  12. copied = true;
  13. await copyToClipboard(code);
  14. setTimeout(() => {
  15. copied = false;
  16. }, 1000);
  17. };
  18. const checkPythonCode = (str) => {
  19. // Check if the string contains typical Python keywords, syntax, or functions
  20. const pythonKeywords = [
  21. 'def',
  22. 'class',
  23. 'import',
  24. 'from',
  25. 'if',
  26. 'else',
  27. 'elif',
  28. 'for',
  29. 'while',
  30. 'try',
  31. 'except',
  32. 'finally',
  33. 'return',
  34. 'yield',
  35. 'lambda',
  36. 'assert',
  37. 'pass',
  38. 'break',
  39. 'continue',
  40. 'global',
  41. 'nonlocal',
  42. 'del',
  43. 'True',
  44. 'False',
  45. 'None',
  46. 'and',
  47. 'or',
  48. 'not',
  49. 'in',
  50. 'is',
  51. 'as',
  52. 'with'
  53. ];
  54. for (let keyword of pythonKeywords) {
  55. if (str.includes(keyword)) {
  56. return true;
  57. }
  58. }
  59. // Check if the string contains typical Python syntax characters
  60. const pythonSyntax = [
  61. 'def ',
  62. 'class ',
  63. 'import ',
  64. 'from ',
  65. 'if ',
  66. 'else:',
  67. 'elif ',
  68. 'for ',
  69. 'while ',
  70. 'try:',
  71. 'except:',
  72. 'finally:',
  73. 'return ',
  74. 'yield ',
  75. 'lambda ',
  76. 'assert ',
  77. 'pass',
  78. 'break',
  79. 'continue',
  80. 'global ',
  81. 'nonlocal ',
  82. 'del ',
  83. 'True',
  84. 'False',
  85. 'None',
  86. ' and ',
  87. ' or ',
  88. ' not ',
  89. ' in ',
  90. ' is ',
  91. ' as ',
  92. ' with ',
  93. ':',
  94. '=',
  95. '==',
  96. '!=',
  97. '>',
  98. '<',
  99. '>=',
  100. '<=',
  101. '+',
  102. '-',
  103. '*',
  104. '/',
  105. '%',
  106. '**',
  107. '//',
  108. '(',
  109. ')',
  110. '[',
  111. ']',
  112. '{',
  113. '}'
  114. ];
  115. for (let syntax of pythonSyntax) {
  116. if (str.includes(syntax)) {
  117. return true;
  118. }
  119. }
  120. // If none of the above conditions met, it's probably not Python code
  121. return false;
  122. };
  123. const executePython = async (text) => {
  124. executed = true;
  125. await tick();
  126. const outputDiv = document.getElementById(`code-output-${id}`);
  127. if (outputDiv) {
  128. outputDiv.innerText = 'Running...';
  129. }
  130. text = text
  131. .split('\n')
  132. .map((line, index) => (index === 0 ? line : ' ' + line))
  133. .join('\n');
  134. // pyscript
  135. let div = document.createElement('div');
  136. let html = `
  137. <py-script type="py" worker>
  138. import js
  139. import sys
  140. import io
  141. # Create a StringIO object to capture the output
  142. output_capture = io.StringIO()
  143. # Save the current standard output
  144. original_stdout = sys.stdout
  145. # Replace the standard output with the StringIO object
  146. sys.stdout = output_capture
  147. try:
  148. ${text}
  149. except Exception as e:
  150. # Capture any errors and write them to the output capture
  151. print(f"Error: {e}", file=output_capture)
  152. # Restore the original standard output
  153. sys.stdout = original_stdout
  154. # Retrieve the captured output
  155. captured_output = "[NO OUTPUT]"
  156. captured_output = output_capture.getvalue()
  157. # Print the captured output
  158. print(captured_output)
  159. def display_message():
  160. output_div = js.document.getElementById("code-output-${id}")
  161. output_div.innerText = captured_output
  162. display_message()
  163. </py-script>`;
  164. div.innerHTML = html;
  165. const pyScript = div.firstElementChild;
  166. try {
  167. document.body.appendChild(pyScript);
  168. setTimeout(() => {
  169. document.body.removeChild(pyScript);
  170. }, 0);
  171. } catch (error) {
  172. console.error('Python error:');
  173. console.error(error);
  174. }
  175. };
  176. $: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
  177. </script>
  178. {#if code}
  179. <div class="mb-4">
  180. <div
  181. class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
  182. >
  183. <div class="p-1">{@html lang}</div>
  184. <div class="flex items-center">
  185. {#if lang === 'python' || checkPythonCode(code)}
  186. <button
  187. class="copy-code-button bg-none border-none p-1"
  188. on:click={() => {
  189. executePython(code);
  190. }}>Run</button
  191. >
  192. {/if}
  193. <button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
  194. >{copied ? 'Copied' : 'Copy Code'}</button
  195. >
  196. </div>
  197. </div>
  198. <pre
  199. class=" hljs p-4 px-5 overflow-x-auto"
  200. style="border-top-left-radius: 0px; border-top-right-radius: 0px; {executed &&
  201. 'border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;'}"><code
  202. class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
  203. ></pre>
  204. {#if executed}
  205. <div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
  206. <div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
  207. <div id="code-output-{id}" class="text-sm" />
  208. </div>
  209. {/if}
  210. </div>
  211. {/if}