#2 從零開始制作在線 代碼編輯器

終于有網(wǎng)了

上一篇
#1 從零開始制作在線 代碼編輯器

Line


harusame-line.js

serval/script/ 下創(chuàng)建 harusame-line.js

文件路徑 serval/script/harusame-line.js

;
/**
 * 1. 行 的高度顾瞻,同樣料身,這里先約(寫)定(死)
 */
(function () {
    var Line = {}
    var self = Line

    self.LINE_HEIGHT = 20 /* 1 */

    window.Line = Line
})()

同時修改掉serval/script/harusame-cursor.js 中的 LINE_HEIGHTLine.LINE_HEIGHT

,既然是 Line類,順便把 serval/script/harusame-serval.js 中的 _generateLine 改造為由 Line 生成翻翩,這樣會比較合適吧颤枪,也順手把生成行號的方法寫一下~

文件路徑 serval/script/harusame-serval.js

/**
 * 渲染一行
 */
_generateLine: function (v_content) {
    var $line = Line.generateLine(v_content)
    this.$line_container.appendChild($line)
}
文件路徑 serval/script/harusame-line.js

/**
 * 生成一行
 * @param content {string} 初始內(nèi)容
 */
self.generateLine = function (v_content) {
    var line_number = self.max_line_number + ""
    var initial_content = v_content || ''
    return Template.line({line_number: line_number, initial_content: initial_content})
},

/**
 * 生成最大行號
 */
var PROXY_max_line_number = 0
Object.defineProperty(self, 'max_line_number', {
    set: function (v_max_line_number) {
        PROXY_max_line_number = v_max_line_number
    },

    get: function () {
        return PROXY_max_line_number++
    }
})

這時候刷新了下瀏覽器發(fā)現(xiàn)報錯了慢宗,嗯嗯...別忘記引入harusame-line.js愁铺。

文件路徑 serval/index.html

...
<script src="script/harusame-dom.js"></script>
<script src="script/harusame-template.js"></script>
<script src="script/harusame-line.js"></script>
<script src="script/harusame-cursor.js"></script>
<script src="script/harusame-serval.js"></script>
...

引入位置別忘了只能放在 harusame-cursor.js && harusame-serval.js 以上,harusame-template 以下...

繼續(xù) #1 中計算 Cursor 位置的邏輯

嘛床绪,要計算 psysicalX logicalX客情,要比計算Y復(fù)雜一點...而且有各種各樣的因素會影響這個,比如letter-spacing 窗口resize 不知道是啥寬度的內(nèi)容 什么的癞己,不管那么多~先只管正常情況下的英文膀斋,中文。

大致的計算思路痹雅。

先確定一下最終目的是為了讓點擊時仰担,光標(biāo)會自動偏移到一個最適合的它位置,防止寫著寫著就寫到其他地方去了的這種事情......_(:3」∠)...

拿 圖2-1 為例绩社,這是 Sublime Text 3 中的放大了很多倍的第一行的內(nèi)容摔蓝,當(dāng)點擊到紅色圓圈處,觸發(fā)一個方法calcX愉耙,讓它去嘗試尋找一個可能是最適合的位置贮尉,這里是約定尋找一個總是大于且最靠近點擊處的位置M1,然后比較M1M1之前的一個字符處的位置朴沿,光標(biāo)靠近哪個就返回那個位置的字符位置(psysicalX)與它的索引(logicalX)猜谚。

圖 2-1 其實就是離哪個地方近就去哪
邏輯步驟
  1. 在初始化的時候败砂,計算并存儲英文字母和中文字母的寬度single_byte_width double_byte_width。由于之前就設(shè)置了等寬字體魏铅,所以按理來說昌犹,字符間都是寬度相等的,但是一眼就能看出英文字母的寬度是一類览芳,中文字母的寬度是一類斜姥。因為字符方面的知識并不充足,只能下意識地懷疑是單字節(jié)字符是一類寬度沧竟,雙字節(jié)字符是一類寬度...于是去找了相關(guān)的正則铸敏,做出來發(fā)現(xiàn)這么分類竟然沒什么問題..!?(之后再補習(xí)._(:3」∠)...

  2. 用戶點擊某處,得到event.layerX屯仗,作為參數(shù)v_psysicalX 傳入 能夠計算偏差后的psysicalX 的方法calcX中(因為要返回二個結(jié)果搞坝,所以不叫他calcPsysicalX or calcLogicalX

  3. 得到該行的字符串(textContent),轉(zhuǎn)化為數(shù)組content_array

  4. 聲明一個保存字符累加長度的變量current_width魁袜,創(chuàng)建一個循環(huán)體

  5. 在循環(huán)體中,當(dāng)current_width < v_psysicalX 的時候(也就是嘗試尋找在點擊處右邊 && 離點擊處最近光標(biāo)位置)敦第,執(zhí)行 5峰弹,否則執(zhí)行6

  6. 聲明一個變量char_width用來存儲當(dāng)前字符content_array[index]的寬度,它通過計算得到芜果。最終即char_width = calcCharWidth(content_array[index])

  7. 如果current_width >= v_psysicalX(找到了4中所說的該位置)鞠呈,這時候去判斷:點擊位置離左邊的光標(biāo)處更近一點還是右邊的光標(biāo)處更近一點,返回更近一點的光標(biāo)位置右钾。(也就是讓光標(biāo)進(jìn)行偏移到兩個字符之間的位置蚁吝。不然點哪光標(biāo)在哪,光標(biāo)會遮擋文字舀射,而且可能會讓用戶覺得懵逼..?!..
    不過說到這里窘茁,突然想到了一個沒什么用的模擬修改液的功能.XD...)

接下來又能寫代碼了...

各個地方的代碼
文件位置 serval/script/harusame-cursor.js
加的挺多的,直接貼完全了~

;
(function () {
    /**
     * 1. 光標(biāo)本身的元素節(jié)點
     */
    var Cursor = function (config) {
        this.$ref = null /* 1 */

        this._logicalY = 0
        this._logicalX = 0
        this._psysicalY = 0
        this._psysicalX = 0

        this.selection_start = null

        Cursor.preCheck()
        this._generateCursor()
        this._setObserver()
    }

    /**
     * 得到瀏覽器計算后的寬度(width)
     * getComputedStyle(v_node).width 是一個帶單位的字符串脆烟,用 parseFloat 隱式轉(zhuǎn)化為數(shù)字類型山林,并去除'px'單位,且保證精準(zhǔn)度
     */
    function getComputedWidth (v_node) {
        return parseFloat(getComputedStyle(v_node).width)
    }

    /**
     * 檢測字符寬度
     * a && 雨是隨便打的字符
     */
    Cursor.preCheck = function () {
        Line.line = 0
        Line.$ref.textContent = 'a'
        this.single_byte_width = getComputedWidth(Line.$ref)
        Line.$ref.textContent = '雨'
        this.double_byte_width = getComputedWidth(Line.$ref)

        // 這句話是測試用的邢羔,等能夠輸入了的時候驼抹,記得刪除
        Line.$ref.textContent = 'hello 你好'
        console.info('single_byte_width', this.single_byte_width)
        console.info('double_byte_width', this.double_byte_width)
    }

    /**
     * 判斷字符寬度
     * /[\x00-\xff]/ ASCII 編碼在 0-255 的字符哦
     */
    Cursor.calcCharWidth = function (v_char) {
        if (/[\x00-\xff]/.test(v_char)) {
            return this.single_byte_width
        } else {
            return this.double_byte_width
        }
    }


    Cursor.prototype = {
        constructor: Cursor,

        /**
         * 創(chuàng)建一個游標(biāo)對象
         */
        _generateCursor: function () {
            this.$ref = SatoriDom.compile(
                e('i', {'class': 'fake-cursor'})
            )
        },

        /**
         * 綁定 邏輯位置 與 物理位置 之間的關(guān)系
         */
        _setObserver: function () {
            /**
             * 這里的 self 由于也是 js關(guān)鍵字,所以會高亮
             * self 原本指向 window拜鹤,一般用不到
             */
            var self = this

            /**
             * 1. 這里賦值的是  _logicalY 哦框冀,下面也是
             * 2. 更新 psysicalY 的值
             * 3. 更新 DOM 位置

             * 4. 寫到這里發(fā)現(xiàn)有點問題......
             */
            Object.defineProperty(self, 'logicalY', {
                set: function (v_logicalY) {
                    self._logicalY = v_logicalY /* 1 */
                    self._psysicalY = self.calcPsysicalY(v_logicalY) /* 2 */
                    self._setY(self._psysicalY) /* 3 */

                    // self.$line = document.getElementById(LINE) /* 4 */
                },

                get: function () {
                    return self._logicalY
                }
            })

            Object.defineProperty(self, 'psysicalY', {
                set: function (v_psysicalY) {
                    self.logicalY = self.calcLogicalY(v_psysicalY)
                },

                get: function () {
                    return self._psysicalY
                }
            })

            Object.defineProperty(self, 'logicalX', {
                set: function (v_logicalX) {
                    self._logicalX = v_logicalX
                    self._psysicalX = self.calcPsysicalX(v_logicalX)
                    self._setX(self._psysicalX)
                },

                get: function () {
                    return self._logicalX
                }
            })


            Object.defineProperty(self, 'psysicalX', {
                set: function (v_psysicalX) {
                    var _proxy = self.calcX(v_psysicalX)
                    self._psysicalX = _proxy.psysicalX
                    self._logicalX = _proxy.logicalX
                    self._setX(self._psysicalX)
                },

                get: function () {
                    return self._psysicalX
                }
            })
        },

        _setX: function (v_psysicalX) {
            this.$ref.style.left = v_psysicalX + 'px'
        },

        _setY: function (v_psysicalY) {
            this.$ref.style.top = v_psysicalY + 'px'
        },

        /**
         * 計算 物理 Y
         */
        calcPsysicalY: function (v_logicalY) {
            return v_logicalY * Line.LINE_HEIGHT
        },

        /**
         * 計算 邏輯 Y
         */
        calcLogicalY: function (v_psysicalY) {
            return parseInt(v_psysicalY / Line.LINE_HEIGHT)
        },

        /**
         * 計算 物理 X
         */
        calcPsysicalX: function (v_logicalX) {
            var content_array = Line.$ref.textContent.split('')
            var current_width = 0

            for (var i = 0; i < v_logicalX; i++) {
                current_width += Cursor.calcCharWidth(content_array[i])
            }

            return current_width
        },

        /**
         * 用于計算 邏輯 X
         */
        calcX: function (v_psysicalX) {
            var psysicalX = getComputedWidth(Line.$ref)
            var textContent = Line.$ref.textContent
            /**
             * 如果點擊的位置大于該行長度,直接將光標(biāo)放在該行末尾
             */
            if (psysicalX <= v_psysicalX) {
                return {
                    psysicalX: psysicalX,
                    logicalX: textContent.length
                }
            }

            var content_array = textContent.split('')
            var current_width = 0

            for (var i = 0; i < content_array.length; i++) {
                var char_width = Cursor.calcCharWidth(content_array[i])
                current_width += char_width
                if (current_width >= v_psysicalX) {
                    var point_right = current_width
                    var point_left = current_width - char_width

                    var offset_right = point_right - v_psysicalX
                    var offset_left = v_psysicalX - point_left

                    if (offset_right < offset_left) {
                        return {
                            psysicalX: point_right,
                            logicalX: i + 1
                        }
                    } else {
                        return {
                            psysicalX: point_left,
                            logicalX: i
                        }
                    }
                }
            }
        }
    }

    window.Cursor = Cursor
})()

文件位置 serval/script/harusame-line.js
加的挺多的敏簿,直接貼完全了~

;
/**
 * 1. 行號 的元素節(jié)點的 id前綴
 * 2. 行內(nèi)容 的元素節(jié)點的 id前綴
 * 3. 初始行號
 * 4. 行 的高度明也,同樣,這里先約(寫)定(死),暴露給外面使用
 */
(function (config) {
    var Line = {}
    var self = Line

    self.LINE_HEIGHT = 20 /* 4 */

    var LINE_NUMBER_SIGN = 'LNS' /* 1 */
    var LINE_CONTENT_SIGN = 'LCS' /* 2 */
    var START_LINE = 1 /* 3 */

    /**
     * 生成一行
     * @param content {string} 初始內(nèi)容
     */
    self.generateLine = function (v_content) {
        var line_number = self.max_line_number
        var initial_content = v_content || ''
        return Template.line({
            line_number: line_number,
            initial_content: initial_content,
            LINE_CONTENT_SIGN: LINE_CONTENT_SIGN,
            LINE_NUMBER_SIGN: LINE_NUMBER_SIGN,
            START_LINE: START_LINE
        })
    }

    /**
     * 生成最大行號
     */
    var PROXY_max_line_number = 0
    Object.defineProperty(self, 'max_line_number', {
        set: function (v_max_line_number) {
            PROXY_max_line_number = v_max_line_number
        },

        get: function () {
            return PROXY_max_line_number++
        }
    })


    /**
     * set:
     * 1. 記錄當(dāng)前行
     * 2. 記錄當(dāng)前行的 DOM
     * get:
     * 1. 返回當(dāng)前行
     */
    var PROXY_line = 0
    Object.defineProperty(self, 'line', {
        set: function (v_logicalY) {
            PROXY_line = v_logicalY /* 1 */
            self.$ref = document.getElementById(LINE_CONTENT_SIGN + v_logicalY) /* 2 */
        },

        get: function () {
            return PROXY_line
        }
    })

    window.Line = Line
})()
文件位置 serval/script/harusame-template.js
只需修改 line诡右,其他就沒貼

/**
 * 行
 * @param line_number {string} 行號
 * @param initial_content {string} 該行初始內(nèi)容
 */
line: function (params) {
    console.info(params)
    var line_number = params.line_number
    return SatoriDom.compile(
        e('div', {'class': 'line'}, [
            e('div', {'class': 'line-number-wrap'}, [
                e('span', {'id': params.LINE_NUMBER_SIGN + line_number, 'class': 'line-number'}, line_number + params.START_LINE + '')
            ]),
            e('div', {'class': 'code-wrap'}, [
                e('code', {'id': params.LINE_CONTENT_SIGN + line_number, 'class': 'code-content'}, params.initial_content || '')
            ])
        ])
    )
},

來看看瀏覽器中的效果安岂,圖 2-2:

圖 2-2

目的是達(dá)到了,理論上來說不想有的也都有:

  1. 點在1所在的元素節(jié)點上帆吻,也會有光標(biāo)的定位效果域那;
  2. 點在沒有行的部分,光標(biāo)也會定位過去猜煮。而希望光標(biāo)能定位到最后一行次员,最后一列
  3. 點在 h 的前面的時候,不怎么容易點到王带,體驗比較差...關(guān)于體驗的話淑蔚,感覺能在后面單獨修正...

嘛..也想提供一些選項,讓用戶自定義行為愕撰,但是感覺反正我自己是不會用的...就不做了刹衫。

因為主要目標(biāo)已經(jīng)達(dá)到了..(可能)...其他的細(xì)節(jié)留給以后再說..
接下來是可能是輸入內(nèi)容與換行~


CHANGELOG

2017年7月12日 18:11
U 修改 calcPsysicalX 中的 <= 為 <
U 修改 calcX 中的 i 與 i - 1 為 i + 1 與 i

上一篇
#1 從零開始制作在線 代碼編輯器

下一篇
#3 從零開始制作在線 代碼編輯器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市搞挣,隨后出現(xiàn)的幾起案子带迟,更是在濱河造成了極大的恐慌,老刑警劉巖囱桨,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仓犬,死亡現(xiàn)場離奇詭異,居然都是意外死亡舍肠,警方通過查閱死者的電腦和手機搀继,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翠语,“玉大人叽躯,你說我怎么就攤上這事》茸ǎ” “怎么了险毁?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長们童。 經(jīng)常有香客問我畔况,道長,這世上最難降的妖魔是什么慧库? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任跷跪,我火速辦了婚禮,結(jié)果婚禮上齐板,老公的妹妹穿的比我還像新娘吵瞻。我一直安慰自己葛菇,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布橡羞。 她就那樣靜靜地躺著眯停,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卿泽。 梳的紋絲不亂的頭發(fā)上莺债,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音签夭,去河邊找鬼齐邦。 笑死,一個胖子當(dāng)著我的面吹牛第租,可吹牛的內(nèi)容都是我干的措拇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼慎宾,長吁一口氣:“原來是場噩夢啊……” “哼丐吓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趟据,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤汰蜘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后之宿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡苛坚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年比被,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泼舱。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡等缀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出娇昙,到底是詐尸還是另有隱情尺迂,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布冒掌,位于F島的核電站噪裕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏股毫。R本人自食惡果不足惜膳音,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铃诬。 院中可真熱鬧祭陷,春花似錦苍凛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至想罕,卻和暖如春悠栓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弧呐。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工闸迷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俘枫。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓腥沽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鸠蚪。 傳聞我的和親對象是個殘疾皇子今阳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 上一篇#0 從零開始制作在線 代碼編輯器 目錄結(jié)構(gòu)和說明 目錄初始結(jié)構(gòu) 創(chuàng)建一些目錄以及文件,如圖1-1 所示茅信。這...
    春雨棲姬閱讀 3,548評論 0 6
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案盾舌? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補...
    _Yfling閱讀 13,759評論 1 92
  • 上一篇#4 從零開始制作在線 代碼編輯器 刪除 與 BackSpace 與 Delete BackSpace 為了...
    春雨棲姬閱讀 1,470評論 0 0
  • 選擇qi:是表達(dá)式 標(biāo)簽選擇器 類選擇器 屬性選擇器 繼承屬性: color,font蘸鲸,text-align妖谴,li...
    wzhiq896閱讀 1,760評論 0 2
  • 選擇qi:是表達(dá)式 標(biāo)簽選擇器 類選擇器 屬性選擇器 繼承屬性: color,font酌摇,text-align膝舅,li...
    love2013閱讀 2,316評論 0 11