IDE لشخص عادي أو لماذا اخترنا موناكو

ملاحظة المحرر


في المقالة الأخيرة ، تحدثنا عن إصدار لوحة تحكم Voximplant ، دون أن ننسى ذكر IDE المحدث. اليوم نكرس سلسلة طويلة منفصلة لهذه الأداة - وصف زميلنا Geloosa بعناية كل من عملية اختيار التكنولوجيا والتنفيذ مع علامات التبويب ، الإكمال التلقائي وأنماط مخصصة. اجلس أكثر ملاءمة ، وخصص بقية شؤونك وانتقل إلى حل المشكلات ، حيث ينتظر الشجاعة في موناكو فضوليًا - لا تنزلق ، فهناك الكثير منهم :) استمتع بالقراءة.


أي مكتبة تختارها لتحرير الكود؟


ينتج Npm 400+ نتائج لمحرر التعليمات البرمجية. بالنسبة للجزء الأكبر ، فهذه عبارة عن ملفات غلاف UI للعديد من libs الأكثر شيوعًا والتي تم إعدادها لإطار عمل أو مشروع معين ، ومكونات إضافية لنفس libs أو شوكاتهم مع تعديلات لأنفسهم ، وكذلك ليس لتحرير الكود في المستعرض ، فهم ببساطة دخلوا في الإخراج بالكلمات الرئيسية. لذلك ، لحسن الحظ ، فإن الخيار أضيق بكثير. عدد قليل من libs - la CodeFlask ، وخفيفة الوزن ، ولكن ليست وظيفية للغاية ، مصممة للقصاصات الصغيرة والأمثلة التفاعلية ، ولكن ليس ل IDE على شبكة الإنترنت كاملة مع الوظائف التي اعتدنا عليها في محرري سطح المكتب.

في النهاية ، لدينا 3 مكتبات للاختيار من بينها: Ace و CodeMirror و Monaco Editor . وأول هذه ، CodeMirror ، كانت مبادرة خاصة من قبل Berliner Marijn Haverbeke ، الذي كان بحاجة إلى محرر كود التمرين في برنامجه التعليمي عبر الإنترنت ، Eloquent JavaScript . تم إصدار النسخة الأولى من المحرر في عام 2007. في عام 2010 ، تم تقديم الإصدار الأول من Ace في JSConf.eu في نفس برلين ، التي طورتها Ajax.org بعد ذلك لسحابة IDE Cloud9 الخاصة بها (في الواقع ، يرمز Ace إلى Ajax.org Cloud9 Editor). في عام 2016 ، تم شراء Cloud9 بواسطة Amazon وهو الآن جزء من AWS. الأحدث ، Monaco Editor ، هو أحد مكونات VS Code وتم نشره بواسطة Microsoft في نهاية عام 2015.

كل محرر له نقاط القوة والضعف الخاصة به ؛ يتم استخدام كل محرر في أكثر من مشروع كبير. على سبيل المثال ، يتم استخدام CodeMirror في أدوات مطوري Chrome و Firefox ، و IDE في Bitbucket ، و RunKit في npm ؛ Ace - في Codecademy ، أكاديمية خان ، MODX ؛ موناكو - في GitLab IDE و CodeSandbox. فيما يلي مخطط مقارنة قد يساعدك في اختيار المكتبة الأنسب لمشروعك.

المكتبات
الآسCodeMirrorموناكو
المطورCloud9 IDE (Ajax.org) ،
الآن جزء من AmazonMozilla
مارين هافربيكيمايكروسوفت
دعم المتصفحFirefox ^ 3.5
الكروم
Safari ^ 4.0
IE ^ 8.0
أوبرا ^ 11.5
Firefox ^ 3.0
الكروم
Safari ^ 5.2
IE ^ 8.0
أوبرا ^ 9.2
Firefox ^ 4.0
الكروم
Safari (v -؟)
IE ^ 11.0
أوبرا ^ 15.0
دعم اللغة
(تسليط الضوء على بناء الجملة)
> 120> 100> 20
عدد الشخصيات في
أحدث الإصدارات على
cndjs.com
366 608 (الإصدار 1.4.3)394269 (الإصدار 5.4.0)2،064،949 (v0.16.2)
وزن أحدث الإصدارات ،
غزيب
2.147 كيلو بايت1.411 كيلو بايت10.898 كيلوبايت
أداءDOMDOMDOM و <canvas> جزئيًا
(للتمرير والتصغير)
الوثائق7 من 10: لا بحث ، ليس واضحًا دائمًا
أن أساليب العودة ، وهناك شكوك
في اكتمال وأهمية
(ليست كل الروابط تعمل في قفص الاتهام)
6 من 10: تم دمجها مع دليل المستخدم ،
البحث عن طريق Ctrl + F ،
هناك شكوك حول الاكتمال
9 من 10: جميل ، مع البحث و
إشارة الصليب
-1 نقطة لعدم وجود تفسير
لبعض الأعلام التي طلبها
ليس واضحا تماما من الاسم
بدء تشغيل العروض التوضيحيةالمستندات النصية مع أمثلة التعليمات البرمجية ،
بشكل منفصل هناك عروض تجريبية مع أمثلة التعليمات البرمجية
(صحيح ، فهي منتشرة على صفحات مختلفة ،
لا يعمل الجميع ويتم تفتيشهم بسهولة أكبر عبر Google) ،
هناك عرض توضيحي حيث يمكنك لمس ميزات مختلفة ،
لكن يُقترح إدارتها من خلال عناصر تحكم واجهة المستخدم ،
وهذا هو ، ثم لا يزال يتعين علينا البحث بشكل منفصل عن الأساليب
لربطها
كيف لفقراء حقا
أساسا كل شيء متناثرة على جيثب
و stackoverflow ، ولكن هناك عروض تجريبية من الميزات مع أمثلة
رمز لتنفيذه
مجتمعة في شكل ملعب:
رمز مع التعليقات وعدد من العروض ، يمكنك
حاول على الفور وتقييم
احتمالات كثيرة
نشاط مجتمعيمركزيارتفاعمركزي
نشاط المطورمركزيمركزيارتفاع

ليس من المنطقي مقارنة المكتبات حسب الحجم ، لأن كل هذا يتوقف على ما وكيفية الاتصال بمشروع معين: قم بتحميل الملف النهائي بأحد البنيات (التي تختلف أيضًا) أو قم بتشغيل حزمة npm من خلال نوع من المجمّع. والأهم هو مقدار استخدام المحرر: ما إذا كان يتم تحميل جميع الأنماط والسمات ، وعدد الإضافات والوظائف الإضافية المستخدمة. على سبيل المثال ، في CodeMirror ، تتوفر معظم الوظائف التي تعمل في Monaco و Ace خارج الصندوق فقط مع الوظائف الإضافية. يعرض الجدول عدد الأحرف في الإصدارات الحديثة على CDN ووزن الملفات المضغوطة الخاصة بهم للحصول على فكرة عامة عن الطلبات المشاركة.

تحتوي جميع المكتبات على نفس مجموعة الميزات الأساسية تقريبًا: التنسيق التلقائي للكود ، وخطوط قابلة للطي ، وخفض / نسخ / لصق ، ومفاتيح التشغيل السريع ، والقدرة على إضافة صيغ جديدة للتمييز والترتيب ، والتحقق من بناء الجملة (في CodeMirror فقط من خلال الوظائف الإضافية ، في Ace حتى الآن فقط لجافا سكريبت) / CoffeeScript / CSS / XQuery) ، تلميحات الأدوات والإكمال التلقائي (في CodeMirror - من خلال الوظائف الإضافية) ، البحث المتقدم عن طريق الكود (في CodeMirror - من خلال الوظائف الإضافية) ، طرق لتنفيذ علامات التبويب ووضع الانقسام ، وضع الفرق وأداة دمج (في CodeMirror - إما مع إيجابيات وسلبيات في نافذة واحدة ، أو لوحة اثنين من خلال الملحق ، الآس - فصل ليب). نظرًا لعمرها ، تمت كتابة العديد من الإضافات لـ CodeMirror ، لكن عددها سيؤثر على وزن وسرعة المحرر. يمكن لموناكو القيام بالكثير من الأشياء خارج الصندوق ، وفي رأيي ، أفضل وفي حجم أكبر من Ace و CodeMirror.

بقينا في موناكو لعدة أسباب:

  1. الأدوات الأكثر تطوراً التي اعتبرناها مهمة لمشروعنا:
    • التحسس الذكي - النصائح والإكمال التلقائي ؛
    • الملاحة رمز الذكية في قائمة السياق ومن خلال minimap.
    • وضع فرق اثنين من لوحة خارج منطقة الجزاء.

  2. مكتوب في TypeScript. لوحة التحكم الخاصة بنا مكتوبة بلغة Vue + Typescript ، لذلك كان دعم TS مهمًا. بالمناسبة ، تدعم Ace مؤخرًا أيضًا TS ، ولكن تمت كتابته في الأصل في JS. ل CodeMirror ، هناك أنواع في definitelyTyped.
  3. يتم تطويره بشكل أكثر فاعلية (ربما لأنه تم إصداره منذ وقت ليس ببعيد) ، ويتم تصحيح الأخطاء بشكل أسرع ويتم مواجهة طلبات التجميع. للمقارنة ، مع CodeMirror كان لدينا تجربة حزينة ، عندما لم يتم تصحيح الأخطاء لسنوات ووضعنا عكاز على عكاز وقادنا عكاز.
  4. وثائق مريحة تم إنشاؤها تلقائيًا (والتي تعطي الأمل في اكتمالها) مع مراجع تبادلية بين الواجهات والأساليب.
  5. حسب ذوقنا ، أجمل واجهة المستخدم (ربما تتعلق أيضًا بوقت الإنشاء) وواجهة برمجة تطبيقات موجزة.
  6. بعد سؤال أصدقاء المطورين عن المحررين الذين تسببوا في معظم الصداع ، كان Ace و CodeMirror هم القادة.

بشكل منفصل ، ينبغي أن يقال عن سرعة العمل. تحليل مكلفة يحدث في مؤشر ترابط العامل المتوازي. بالإضافة إلى ذلك ، يتم تقييد جميع العمليات الحسابية من خلال حجم منفذ العرض (يتم احتساب جميع الأنواع والألوان والتجسيد فقط لتلك الخطوط المرئية). يبدأ في الفرامل فقط إذا كان الكود يحتوي على 100،000 سطر - يمكن حساب المطالبات لعدة ثوان. اتضح أن Ace ، التي تستخدم أيضًا العاملين في مجال الحوسبة الثقيلة ، أسرع: في التعليمات البرمجية من نفس الطول ، تظهر المطالبات على الفور تقريبًا ، وتتكيف بسرعة مع 200000 سطر (على الموقع الرسمي ، يُذكر أنه حتى 4 ملايين خط يجب ألا تكون مشكلة ، على الرغم من تم تسريع المسامير ، وبدأ الإدخال في التباطؤ واختفت المطالبات بعد المليون. يتعذر على CodeMirror ، حيث لا توجد حسابات متوازية ، سحب مثل هذه المجلدات: يمكن أن تومض كل من النص وتمييز بناء الجملة. نظرًا لأن 100،000 سطر في ملف نادر في العالم الواقعي ، فقد تغاضينا عن هذا. حتى مع 40-50 ألف خط موناكو يقوم بعمل ممتاز.

توصيل Monaco واستخدام الميزات الأساسية (على سبيل المثال ، التكامل مع Vue)


صلة


هنا سأقدم أمثلة على الكود من مكونات الصوت وأستخدم المصطلحات المناسبة. ولكن كل هذا يتم نقله بسهولة إلى أي إطار آخر أو JS خالص.

يمكن تنزيل شفرة مصدر موناكو على الموقع الرسمي ووضعها في مشروعك ، ويمكنك استلامه من CDN ، ويمكنك الاتصال بالمشروع عبر npm. سأتحدث عن الخيار الثالث وبناء باستخدام webpack.

نضع محرر موناكو ومكون إضافي للتجميع:

npm i -S monaco-editor npm i -D monaco-editor-webpack-plugin 

في تهيئة webpack ، أضف:

 const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); module.exports = { // ... plugins: [ // ... new MonacoWebpackPlugin() ] }; 

إذا كنت تستخدم Vue و vue-cli-service في الإنشاء ، فأضف إلى vue.config.js:

 const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); module.exports = { // ... configureWebpack: (config) => { // ... config.plugins.push(new MonacoWebpackPlugin()); } }; 

إذا كنت لا تحتاج إلى جميع لغات وميزات Monaco ، لتقليل حجم الحزمة ، يمكنك نقل كائن من الإعدادات إلى MonacoWebpackPlugin :

 new MonacoWebpackPlugin({ output: '', // ,     languages: ['markdown'], //     ,     features: ['format', 'contextmenu'] //      }) 

قائمة كاملة من الميزات واللغات للمكون الإضافي هنا .

إنشاء وتخصيص محرر


نقوم باستيراد editor editor.create(el: HTMLElement, config?: IEditorConstructionOptions) ، بتمرير عنصر DOM الذي نريد إنشاء المحرر فيه كوسيطة أولى.

في مكون المحرر:

 <template> <div ref='editor' class='editor'></div> </template> <script> import {editor} from 'monaco-editor'; import {Component, Prop, Vue} from 'vue-property-decorator'; @Component() export default class Monaco extends Vue { private editor = null; mounted() { this.editor = editor.create(this.$refs.editor); } } </script> <style> .editor { margin: auto; width: 60vw; height: 200px; } </style> 

يجب أن تقوم الحاوية الخاصة بالمحرر بالضرورة بتعيين الارتفاع بحيث لا يتحول إلى صفر. إذا قمت بإنشاء المحرر في div فارغ (مع ارتفاع صفر - K.O.) ، ستكتب Monaco نفس الارتفاع بأسلوب مضمّن في نافذة المحرر.

الوسيطة الاختيارية الثانية إلى editor.create هي تهيئة المحرر. يوجد أكثر من مائة خيار فيه ، يوجد وصف كامل لواجهة IEditorConstructionOptions في الوثائق.

على سبيل المثال ، سنقوم بتعيين اللغة والسمة والنص الأولي وتمكين التفاف السطر (بشكل افتراضي ، لا يتم لفهم):

 const config = { value: `function hello() { alert('Hello world!'); }`, language: 'javascript', theme: 'vs-dark', wordWrap: 'on' }; this.editor = editor.create(this.$refs.editor, config); 

ترجع الدالة editor.create كائنًا بواجهة IStandaloneCodeEditor . من خلاله ، يمكنك الآن التحكم في كل ما يحدث في المحرر ، بما في ذلك تغيير الإعدادات الأولية:

 //        read-only  this.editor.updateOptions({wordWrap: 'off', readOnly: true}); 

الآن من أجل الألم: يقبل updateOptions كائنًا بواجهة IEditorOptions ، وليس IEditorConstructionOptions. تختلف قليلاً: IEditorConstructionOptions أوسع ، ويشمل خصائص مثيل المحرر هذا وبعض الخصائص العامة. يتم تغيير خصائص updateOptions من خلال updateOptions ، وعالمية - من خلال أساليب editor العالمي. وبالتالي ، فإن تلك التي تتغير على مستوى العالم تتغير لجميع الحالات. من بين هذه الخيارات هو theme . إنشاء مثيلات 2 مع سمات مختلفة. ذ كلاهما سيكون واحد في الماضي (الظلام هنا). ستقوم طريقة editor.setTheme('vs') أيضًا بتغيير الموضوع لكليهما. سيؤثر هذا حتى على تلك النوافذ الموجودة في صفحة أخرى من SPA الخاص بك. هناك عدد قليل من هذه الأماكن ، ولكن عليك متابعتها.

 <template> <div ref='editor1' class='editor'></div> <div ref='editor2' class='editor'></div> </template> <script> // ... this.editor1 = editor.create(this.$refs.editor1, {theme: 'vs'}); this.editor2 = editor.create(this.$refs.editor2, {theme: 'vs-dark'}); // ... </script> 


حذف المحرر


عندما تقوم بتدمير نافذة Monaco ، يجب عليك استدعاء طريقة dispose ، وإلا فلن يتم مسح جميع المستمعين ولن تعمل النوافذ التي تم إنشاؤها بعد ذلك بشكل صحيح ، وتتفاعل مع بعض الأحداث عدة مرات:

 beforeDestroy() { this.editor && this.editor.dispose(); } 

علامات التبويب


تستخدم علامات التبويب المفتوحة في محرر الملف نفس نافذة موناكو. للتبديل بينهما ، يتم استخدام أساليب IStandaloneCodeEditor: getModel و setModel لتحديث نموذج المحرر. يقوم النموذج بتخزين النص وموضع المؤشر وتاريخ الإجراء للتراجع عن الإعادة. لإنشاء نموذج لملف جديد ، يتم استخدام الطريقة العالمية editor.createModel(text: string, language: string) . إذا كان الملف فارغًا ، فلا يمكنك إنشاء نموذج وتمرير setModel إلى setModel :

عرض الكود
 <template> <div class='tabs'> <div class='tab' v-for="tab in tabs" :key'tab.id' @click='() => switchTab(tab.id)'> {{tab.name}} </div> </div> <div ref='editor' class='editor'></div> </template> <script> import {editor} from 'monaco-editor'; import {Component, Prop, Vue} from 'vue-property-decorator'; @Component() export default class Monaco extends Vue { private editor = null; private tabs: [ {id: 1, name: 'tab 1', text: 'const tab = 1;', model: null, active: true}, {id: 2, name: 'tab 2', text: 'const tab = 2;', model: null, active: false} ]; mounted() { this.editor = editor.create(this.$refs.editor); } private switchTab(id) { const activeTab = this.tabs.find(tab => tab.id === id); if (!activeTab.active) { //    (     )    const model = !activeTab.model && activeTab.text ? editor.createModel(activeTab.text, 'javascript') : activeTab.model; //          this.tabs = this.tabs.map(tab => ({ ...tab, model: tab.active ? this.editor.getModel() : tab.model, active: tab.id === id })); //    this.editor.setModel(model); } } </script> 


وضع الفرق


لوضع الفرق ، تحتاج إلى استخدام طريقة editor أخرى عند إنشاء نافذة المحرر - createDiffEditor :

 <template> <div ref='diffEditor' class='editor'></div> </template> // ... mounted() { this.diffEditor = editor.createDiffEditor(this.$refs.diffEditor, config); } // ... 

يستغرق نفس المعلمات مثل editor.create ، لكن يجب أن يحتوي التكوين على واجهة IDiffEditorConstructionOptions ، والتي تختلف قليلاً عن تهيئة المحرر العادي ، على وجه الخصوص ، ليس لها value . يتم تعيين النصوص للمقارنة بعد إنشاء IStandaloneDiffEditor التي تم إرجاعها عبر setModel :

 this.diffEditor.setModel({ original: editor.createModel('const a = 1;', 'javascript'), modified: editor.createModel('const a = 2;', 'javascript') }); 


قائمة السياق ، لوحة القيادة والمفاتيح الساخنة


تستخدم Monaco قائمة السياق الخاصة بها ، غير المستعرضية ، حيث يوجد تنقل ذكي ، ومؤشر متعدد لتغيير كل الأحداث ، ولوحة أمر كما هو الحال في VS Code (لوحة الأوامر) مع مجموعة من الأوامر والاختصارات المفيدة التي تسرع كتابة التعليمات البرمجية:

  قائمة السياق موناكو 


  لوحة قيادة موناكو 


يتم توسيع قائمة السياق من خلال طريقة addAction (وهي متوفرة في كل من IStandaloneCodeEditor و IStandaloneDiffEditor ) ، والتي تقبل كائن IActionDescriptor :

عرض الكود
 // ... <div ref='diffEditor' :style='{display: isDiffOpened ? "block" : "none"}'></div> // ... //  KeyCode  KeyMod     import {editor, KeyCode, KeyMod} from "monaco-editor"; // ... private editor = null; private diffEditor = null; private isDiffOpened = false; private get activeTab() { return this.tabs.find(tab => tab.active); } mounted() { this.diffEditor = editor.createDiffEditor(this.$refs.diffEditor); this.editor = editor.create(this.$refs.editor); this.editor.addAction({ //  ,     . contextMenuGroupId: '1_modification', //   : 1 - 'navigation', 2 - '1_modification', 3 - '9_cutcopypaste'; //    contextMenuOrder: 3, //       label: 'Show diff', id: 'showDiff', keybindings: [KeyMod.CtrlCmd + KeyMod.Shift + KeyCode.KEY_D], //   // ,     //    run: this.showDiffEditor }); } //      private showDiffEditor() { this.diffEditor.setModel({ original: this.activeTab.initialText, modified: this.activeTab.editedText }); this.isDiffOpened = true; } 


من أجل ربط اختصار بإجراء ما فقط دون إظهاره في قائمة السياق ، يتم استخدام نفس الطريقة ، لم يتم تحديد contextMenuGroupId العمل فقط:

عرض الكود
 // ... //   private myActions = [ { contextMenuGroupId: '1_modification', contextMenuOrder: 3, label: <string>this.$t('scenarios.showDiff'), id: 'showDiff', keybindings: [KeyMod.CtrlCmd + KeyMod.Shift + KeyCode.KEY_D], run: this.showDiffEditor }, // ,   Ctrl + C + L      { label: 'Get content length', id: 'getContentLength', keybindings: [KeyMod.CtrlCmd + KeyCode.Key_C + KeyCode.Key_L], run: () => this.editor && alert(this.editor.getValue().length) } ]; mounted() { this.editor = editor.create(this.$refs.editor); this.myActions.forEach(this.editor.addAction); //     } 


ستتضمن لوحة الأوامر جميع الإجراءات المضافة.

نصائح والإكمال التلقائي


لهذه الأغراض ، تستخدم Monaco التحسس الذكي ، وهو أمر رائع. يمكنك أن تقرأ على لقطات الشاشة الرابط مقدار المعلومات المفيدة التي يمكنه عرضها. إذا لم يكن لغتك بعد الإكمال التلقائي ، يمكنك إضافتها من خلال registerCompletionItemProvider . وبالنسبة إلى JS و TS ، يوجد بالفعل أسلوب addExtraLib الذي يسمح لك بتحميل تعريفات TypeScript لأدوات تلميحات وإكمال تلقائي:

 // ... import {languages} from "monaco-editor"; // ... // ,          private myAddedLib = null; mounted() { // languages     Monaco this.myAddedLib = languages.typescript.javascriptDefaults.addExtraLib('interface MyType {prop: string}', 'myLib'); } beforeDestroy() { //  ,   this.myAddedLib && this.myAddedLib.dispose(); } 

في المعلمة الأولى ، يمرر السطر التعريف ، في الثاني ، اختياري ، اسم lib.

لغات مخصصة والسمات


لدى Monaco وحدة نمطية Monarch لتحديد بناء جملة لغاتها. يتم وصف بناء الجملة بشكل قياسي تمامًا: يتم تعيين المراسلات بين النظامي والرموز المميزة لهذه اللغة.

عرض الكود
 // ... //  ,    : private myLanguage = { defaultToken: 'text', //  , brackets: [{ open: '(', close: ')', token: 'bracket.parenthesis' }], // ,   , keywords: [ 'autumn', 'winter', 'spring', 'summer' ], //     tokenizer: { root: [{ regex: /\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}/, action: { token: 'date' } }, { regex: /(boy|girl|man|woman|person)(\s[A-Za-z]+)/, action: ['text', 'variable'] } ] } }; mounted() { //     languages.register({ id: 'myLanguage' }); //      languages.setMonarchTokensProvider('myLanguage', this.myLanguage); // ... } 


يمكنك أيضًا إنشاء سمة لرموزك - كائن بواجهة IStandaloneThemeData - وتثبيته في editor :

 // ... private myTheme = { base: 'vs', // ,      inherit: true, //       rules: [ {token: 'date', foreground: '22aacc'}, {token: 'variable', foreground: 'ff6600'}, {token: 'text', foreground: 'd4d4d4'}, {token: 'bracket', foreground: 'd4d4d4'} ] }; mounted() { editor.defineTheme('myTheme', this.myTheme); // ... } 

الآن سيبدو النص باللغة الموضحة كما يلي:


يمكنك تطبيق هذه الميزة ، طالما أن لديك ما يكفي من الخيال. على سبيل المثال ، قمنا بإنشاء عارض سجل المكالمات في لوحة المطورين الخاصة بنا. غالبًا ما تكون السجلات طويلة وغير مفهومة ، ولكن عندما يتم عرضها بتمييز بناء الجملة ، والبحث الذكي ، وخطوط قابلة للطي / التمديد ، والأوامر الضرورية (على سبيل المثال ، Prettify params) ، وتسليط الضوء على جميع خطوط الاتصال بمعرفها أو ترجمة الوقت في السجل إلى منطقة زمنية مختلفة ، ثم الحفر يصبح الأمر أكثر سهولة فيها (لقطة الشاشة قابلة للنقر):


استنتاج


باختصار ، سأقول إن موناكو هي النار. بعد أشهر من العمل معه ، لدي ذكريات ممتعة للغاية. إذا اخترت محررًا للرمز ، فتأكد من الانتقال إلى Playground الخاص به والتشغيل مع الكود ، ومعرفة ما يمكن القيام به. ربما هذا هو بالضبط ما تبحث عنه.

Source: https://habr.com/ru/post/ar445390/


All Articles