
สิ่งผิดพลาดเล็กๆ จาก Chrome สู่ Visual Studio Code นำไปสู่การเรียนรู้ต่างๆ — Part 3 (จบ)
Electron framework
ก่อนจะกล่าวถึงสิ่งที่พูดใน Part2 เราต้องรู้ก่อนว่า VsCode นั้นถูกพัฒนาต่อยอดมาจาก Electron framework
Electron framework นั้นมีพื้นฐานมาจาก Node.js และ Google Chromium (โครงการ opensource ของ Google Chrome) ซึ่งแน่นอนภาษาที่เราใช้หนีไม่พ้น Javascript, TypeScript, CoffeeScript, HTML และ CSS — น่าสนใจไหมหละ ใช้ภาษาที่พัฒนา Web Application มาพัฒนา Desktop Application หรืออีกนัยนึง Offline Web Application นั้นแแหละ
ยังมีโครงการอื่นๆที่ใช้ Electron เช่น Atom — A hackable text editor ซึ่งเจ้าตัว Atom กลับไม่ปรากฏว่ามี bug cursor อยู่ผิดที่ผิดทางเมื่อใช้ภาษาไทย เฉกเช่นเดียวกันกับ VsCode
เริ่มต้นที่สิ่งเดียวกัน แต่เลือกทางเดินคนละทาง ผลลัพธ์ย่อมแตกต่าง แต่จุดหมายคือสิ่งเดียวกัน
การที่ VsCode นั้นพัฒนาต่อยอดของ Electron อะไรก็ตามที่เป็น bug ของ ตัว Electron ก็ต้องส่งมาถึง VsCode ไม่ทางใดก็ทางหนึ่ง ในที่เราจะกล่าวถึงคือ bug cursor อยู่ผิดที่ผิดทางเมื่อใช้ภาษาไทยนั้น เป็นผลมาจาก Google Chrome โดยตรง
กลับมาที่ Part 3
ผมแกะมาถึง Method _createRawVisibleRangesFromClientRects(...) ใน viewLine.ts ที่นี้ก็ใช้ดู code จากรูปด้านล่างได้เลย และทักษะบ้านๆของผม console.log(...) เพื่อใช้ในการ Debug จุดที่ผมสนใจ มี 3 จุด คือ clientRects[i], new VisibleRange(…) และ deltaLeft ที่มีค่าคงที่ 102

ส่วน class VisibleRange นั้นมี constructor ดังรูปด้านล่าง คือรับค่า top, left, width และ height อย่าลืมนะว่า ค่า left ต่ำสุดที่ 0 และ มาจากผลการคำนวณ
clientRects[i].left - deltaLeft


เริ่มที่ตัวแปร clientRects[i] ขั้นต้นทดลองพิมพ์ อักษร ‘d’ เพื่อดูค่า ของ clientRects[i] เราจะได้ตามรูปให้เราจำค่า left ไว้ 110

ที่นี้ทดลองเปลี่ยน พิมพ์อักษร ‘ก’ เราจะได้ค่า left ที่ 102 ทำไมค่ามันหายไป 8 นี้แหละ คือจุดที่ทำให้ cursor มันขึ้นไปข้างหน้าไงหละ หลังจากนั้นจึงทดลองพิมพ์ ‘ddd’ แทน เราได้ค่า left คือ 102 +8 + 8 +8 = 126 เพื่อความแน่ใจจึงลองพิมพ์ ‘กกก’ ลงไป ค่า left ก็ยังมีค่าเป็น 102 เหมือนเดิมไม่เปลี่ยนแปลง เราจึงไม่ต้องสนใจค่า top, bottom, height และทำให้ผมมั่นใจทันทีว่าตำแหน่งของ cursor นั้นขึ้นอยู่กับ ค่า left แน่นอน
จากที่อธิบายไปข้างบนนั้น ปัญหาอยู่กับตัวแแปร ‘clientRects’ และตัวแปรตัวนี้มาจากไหน เราย้อนกลับไปดู Method ก่อนหน้ากัน _readRawVisibleRangesFrom(…) ใน ViewLine.ts ดู code ได้จากรูปถัดไป

เริ่มที่บรรทัด 337 กดที่รูปเพื่อขยายได้นะ
var startElement = domNode.children[startChildIndex].firstChild;
var endElement = domNode.children[endChildIndex].firstChild;
สองบรรทัดนี้ ตัวแปร ‘endElement’ จะมีค่าเป็นของข้อความที่เราพิมพ์ลงบน VsCode ดูตัวอย่างจากรูปล่างได้เลยนะ

ตามต่อบรรทัดที่ 349
var clientRects = range.getClientRects(),...; ผมคิดว่า method getClientRects นี้แหละที่เป็นปัญหา ตัวนี้เป็น method ของ JavaScript พื้นฐาน ครับ โดย method นี้จะทำการตีกรอบข้อความให้ ดูตัวอย่างได้จากรูปด้านล่าง หรือศึกษาได้จาก ลิงค์นี้

ผมได้ดัดแปลง code จาก คุณ edg2s เพื่อทดสอบใช้ getClientRects ว่าจะเกิดปัญหาใน google chrome และก็เกิดปัญหาขึ้นเช่นกัน สามารถ ทดลองได้ที่นี้ — ถ้าเขาไปลองพิมพ์อักษรไทย เช่น สวัสดี ต่อจากคำว่า hello เราจะเห็นว่า cursor จะเพี้ยนทำงานผิดพลาด แต่พอเป็นภาษาอังกฤษกลับไม่มีปัญหาได้ ผมใช้ภาษาเกาหลี (안녕하세요) ญี่ปุ่น (こんにちは) ก็ได้ผลเช่นเดียวกับภาษาอังกฤษ ที่น่าสนใจคือไม่พบปัญหาบน Microsoft Edge และ Firefox
คิดวิธีการแก้ไขปัญหา
ตอนแรกผมคิดว่าถ้าเรารู้ความยาวของข้อความ (string.length) เราก็ 8 * string.length + 102 ก็น่าจะแก้ไขได้แล้ว แต่คิดถี่ถ้วนแล้ว แบบนี้ไม่ได้ครับ เพราะว่า font ภาษาไทย ที่ใช้นั้นขนาดไม่เท่ากันทุกตัวอักษร เรามาดูตัวอย่างจากรูปด้านล่าง

จากรูปด้านซ้ายจะเห็นเลยว่า ทั้งทีทุกบรรทัดนั้น มี 6 ตัวอักษรเท่าๆกัน แต่ font ภาษาไทยที่ใช้จะมีขนาดไม่เท่ากันทุกตัวอักษรเหมือนกับภาษาอังกฤษแล้วเราจะวัดความยาวของข้อความอย่างไร ?
ในภาษา C# นั้นมี method TextRenderer.MeasureText ที่ทำหน้าที่คล้ายๆกับ getClientRects ของ JavaScript ผมเลยคิดว่าในภาษา JavaScript คงมี method MeasureText เช่นเดียวกัน จึงทำการค้นหาจาก JavaScript API ที่ Mozilla เตรียมไว้ผมได้เจอ method CanvasRenderingContext2D.measureText(…) ซึ่งคืนค่า width ของ text พอดี ในที่นี้คือ ค่าความยาวหรือค่า clientRects.left งั้นเรามาลองดูว่าจะได้ผลหรือไม่ อย่างไร
ลงมือแก้ไขปัญหา
เริ่มต้นจากสร้าง private method ชื่อ _measureTextWidth(text:String) ขึ้นมาเพื่อให้คืนค่าความยาวของข้อความดังรูปด้านล่าง โดยเรากำหนด font เป็น Consolas คงที่ไว้ก่อนนะ เอาง่ายๆก่อน ขนาดของ font (font size) ให้ตั้งค่าตาม editor ละกัน สุดท้ายผม console.log debug เพื่อค่า setting ต่างๆมานะ ใช่เวลาพอควร

เรามาปรับปรุงเล็ก Method _readRawVisibleRangesFrom(…) โดยเราจะแก้ค่า result ที่ได้ ทั้งหมด ก่อนที่ จะ return result ออกไป ค่าของ result นั้นอยู่ในลักษณะ array objects เพราะฉะนั้น เราต้อง for loop แล้วแก้ที่ละตัวครับผม

หลังจากแก้ไขแล้วเราจะได้ผลลัพธ์ตามรูปด่านล่างนี้ cursor อยู่ถูกที่แล้วเมื่อเราพิมพ์ภาษาไทย


ปัญหา cursor เพี้ยนหายไปแล้ว แน่ใจหรือ ? ไม่แน่ใจสิ เรายังต้องทดสอบส่วนอื่นๆอีก วิธีแก้นี้เป็นเพียง Prototype เท่านั้นและยังไม่สมบูรณ์ ผมเจอ bug ใหม่ด้วย
เช่น ผมลองลากข้อความดังรูปด่านล่าง cursor มันกลับเพี้ยนอีกครั้ง แต่เอาเป็นว่าหยุดไว้เท่านี้ก่อน (ที่จริงแก้ได้แล้วนะแต่หยุดไว้ตรงนี้ดีกว่า)

สุดท้ายและท้ายสุดจากนั่งทดลองแก้ไขปัญหาเบื้องต้น วิธีการเบื้องต้น ทำให้ cursor แสดงตำแหน่งที่ถูกต้อง เมื่อเราพิมพ์ข้อความต่อกันๆ แต่ปัญหานั้นยังไม่หมดจากตัวอย่างด้านบน และยังต้องทดสอบกรณีอื่นเพิ่มเติมด้วย
และผมคิดว่าเราควรรอให้ทาง ผู้พัฒนา Google Chrome พัฒนาให้ method Element.getClientRects() รองรับภาษาไทย เช่นเดียวกับภาษาเกาหลี และญี่ปุ่น หลังจากนั้นหวังว่าให้ทาง Electron framework นำ Google Chrome รุ่นที่แก้ไขเข้ามาแทนรุ่นที่ใข้อยู่ในปัจจุบัน หรือ open issue ให้กับทาง VsCode โดยตรงดีกว่า และผมเชื่อว่าทางทีม VsCode คงแก้ปัญหาได้ดีกว่าที่ผมทำ
ถ้าใครอ่านตั้งแต่ Part 1 ตามต่อที่ Part 2 จนสุดท้ายที่ Part 3 จะเห็นได้ว่า ตัวผมเริ่มจากไม่รู้อะไรเลยใน VsCode แล้วค่อยๆเดินทีละก้าว สองก้าว เพื่อรวบรวมความรู้ และนำความรู้ที่มี มาทดลองแก้ไขปัญหา ถึงแม้จะไม่สามารถแก้ได้ทั้งหมด แต่ผมก็ได้ทดลอง เขียน TypeScript และศึกษาโครงสร้างของ Project VsCode ต่างๆ ก็ถือว่าเป็นอะไรที่สนุกสนาม ระหว่างรอหางานทำ
This is life.