145 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			145 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|   | /*--------------------------------------------------------------------------------------------- | ||
|  |  *  Copyright (c) Microsoft Corporation. All rights reserved. | ||
|  |  *  Licensed under the MIT License. See License.txt in the project root for license information. | ||
|  |  *--------------------------------------------------------------------------------------------*/ | ||
|  | import { createScanner } from '../parser/htmlScanner'; | ||
|  | import * as strings from '../utils/strings'; | ||
|  | import { URI as Uri } from 'vscode-uri'; | ||
|  | import { TokenType, Range } from '../htmlLanguageTypes'; | ||
|  | function normalizeRef(url) { | ||
|  |     const first = url[0]; | ||
|  |     const last = url[url.length - 1]; | ||
|  |     if (first === last && (first === '\'' || first === '\"')) { | ||
|  |         url = url.substring(1, url.length - 1); | ||
|  |     } | ||
|  |     return url; | ||
|  | } | ||
|  | function validateRef(url, languageId) { | ||
|  |     if (!url.length) { | ||
|  |         return false; | ||
|  |     } | ||
|  |     if (languageId === 'handlebars' && /{{|}}/.test(url)) { | ||
|  |         return false; | ||
|  |     } | ||
|  |     return /\b(w[\w\d+.-]*:\/\/)?[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|\/?))/.test(url); | ||
|  | } | ||
|  | function getWorkspaceUrl(documentUri, tokenContent, documentContext, base) { | ||
|  |     if (/^\s*javascript\:/i.test(tokenContent) || /[\n\r]/.test(tokenContent)) { | ||
|  |         return undefined; | ||
|  |     } | ||
|  |     tokenContent = tokenContent.replace(/^\s*/g, ''); | ||
|  |     const match = tokenContent.match(/^(\w[\w\d+.-]*):/); | ||
|  |     if (match) { | ||
|  |         // Absolute link that needs no treatment
 | ||
|  |         const schema = match[1].toLowerCase(); | ||
|  |         if (schema === 'http' || schema === 'https' || schema === 'file') { | ||
|  |             return tokenContent; | ||
|  |         } | ||
|  |         return undefined; | ||
|  |     } | ||
|  |     if (/^\#/i.test(tokenContent)) { | ||
|  |         return documentUri + tokenContent; | ||
|  |     } | ||
|  |     if (/^\/\//i.test(tokenContent)) { | ||
|  |         // Absolute link (that does not name the protocol)
 | ||
|  |         const pickedScheme = strings.startsWith(documentUri, 'https://') ? 'https' : 'http'; | ||
|  |         return pickedScheme + ':' + tokenContent.replace(/^\s*/g, ''); | ||
|  |     } | ||
|  |     if (documentContext) { | ||
|  |         return documentContext.resolveReference(tokenContent, base || documentUri); | ||
|  |     } | ||
|  |     return tokenContent; | ||
|  | } | ||
|  | function createLink(document, documentContext, attributeValue, startOffset, endOffset, base) { | ||
|  |     const tokenContent = normalizeRef(attributeValue); | ||
|  |     if (!validateRef(tokenContent, document.languageId)) { | ||
|  |         return undefined; | ||
|  |     } | ||
|  |     if (tokenContent.length < attributeValue.length) { | ||
|  |         startOffset++; | ||
|  |         endOffset--; | ||
|  |     } | ||
|  |     const workspaceUrl = getWorkspaceUrl(document.uri, tokenContent, documentContext, base); | ||
|  |     if (!workspaceUrl || !isValidURI(workspaceUrl)) { | ||
|  |         return undefined; | ||
|  |     } | ||
|  |     return { | ||
|  |         range: Range.create(document.positionAt(startOffset), document.positionAt(endOffset)), | ||
|  |         target: workspaceUrl | ||
|  |     }; | ||
|  | } | ||
|  | function isValidURI(uri) { | ||
|  |     try { | ||
|  |         Uri.parse(uri); | ||
|  |         return true; | ||
|  |     } | ||
|  |     catch (e) { | ||
|  |         return false; | ||
|  |     } | ||
|  | } | ||
|  | export class HTMLDocumentLinks { | ||
|  |     constructor(dataManager) { | ||
|  |         this.dataManager = dataManager; | ||
|  |     } | ||
|  |     findDocumentLinks(document, documentContext) { | ||
|  |         const newLinks = []; | ||
|  |         const scanner = createScanner(document.getText(), 0); | ||
|  |         let token = scanner.scan(); | ||
|  |         let lastAttributeName = undefined; | ||
|  |         let lastTagName = undefined; | ||
|  |         let afterBase = false; | ||
|  |         let base = void 0; | ||
|  |         const idLocations = {}; | ||
|  |         while (token !== TokenType.EOS) { | ||
|  |             switch (token) { | ||
|  |                 case TokenType.StartTag: | ||
|  |                     lastTagName = scanner.getTokenText().toLowerCase(); | ||
|  |                     if (!base) { | ||
|  |                         afterBase = lastTagName === 'base'; | ||
|  |                     } | ||
|  |                     break; | ||
|  |                 case TokenType.AttributeName: | ||
|  |                     lastAttributeName = scanner.getTokenText().toLowerCase(); | ||
|  |                     break; | ||
|  |                 case TokenType.AttributeValue: | ||
|  |                     if (lastTagName && lastAttributeName && this.dataManager.isPathAttribute(lastTagName, lastAttributeName)) { | ||
|  |                         const attributeValue = scanner.getTokenText(); | ||
|  |                         if (!afterBase) { // don't highlight the base link itself
 | ||
|  |                             const link = createLink(document, documentContext, attributeValue, scanner.getTokenOffset(), scanner.getTokenEnd(), base); | ||
|  |                             if (link) { | ||
|  |                                 newLinks.push(link); | ||
|  |                             } | ||
|  |                         } | ||
|  |                         if (afterBase && typeof base === 'undefined') { | ||
|  |                             base = normalizeRef(attributeValue); | ||
|  |                             if (base && documentContext) { | ||
|  |                                 base = documentContext.resolveReference(base, document.uri); | ||
|  |                             } | ||
|  |                         } | ||
|  |                         afterBase = false; | ||
|  |                         lastAttributeName = undefined; | ||
|  |                     } | ||
|  |                     else if (lastAttributeName === 'id') { | ||
|  |                         const id = normalizeRef(scanner.getTokenText()); | ||
|  |                         idLocations[id] = scanner.getTokenOffset(); | ||
|  |                     } | ||
|  |                     break; | ||
|  |             } | ||
|  |             token = scanner.scan(); | ||
|  |         } | ||
|  |         // change local links with ids to actual positions
 | ||
|  |         for (const link of newLinks) { | ||
|  |             const localWithHash = document.uri + '#'; | ||
|  |             if (link.target && strings.startsWith(link.target, localWithHash)) { | ||
|  |                 const target = link.target.substring(localWithHash.length); | ||
|  |                 const offset = idLocations[target]; | ||
|  |                 if (offset !== undefined) { | ||
|  |                     const pos = document.positionAt(offset); | ||
|  |                     link.target = `${localWithHash}${pos.line + 1},${pos.character + 1}`; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         return newLinks; | ||
|  |     } | ||
|  | } |